UIActivityViewController travando em iPads iOS 8


288

Atualmente, estou testando meu aplicativo com o Xcode 6 (Beta 6). O UIActivityViewController funciona bem com dispositivos e simuladores do iPhone, mas falha com simuladores e dispositivos do iPad (iOS 8) com os seguintes registros

Terminating app due to uncaught exception 'NSGenericException', 
reason: 'UIPopoverPresentationController 
(<_UIAlertControllerActionSheetRegularPresentationController: 0x7fc7a874bd90>) 
should have a non-nil sourceView or barButtonItem set before the presentation occurs.

Estou usando o seguinte código para iPhone e iPad para iOS 7 e iOS 8

NSData *myData = [NSData dataWithContentsOfFile:_filename];
NSArray *activityItems = [NSArray arrayWithObjects:myData, nil];
UIActivityViewController *activityViewController = [[UIActivityViewController alloc] initWithActivityItems:nil applicationActivities:nil];
activityViewController.excludedActivityTypes = @[UIActivityTypeCopyToPasteboard];
[self presentViewController:activityViewController animated:YES completion:nil];

Estou tendo uma falha semelhante em um outro meu aplicativo também. Você pode me guiar, por favor ? alguma coisa mudou com o UIActivityViewController no iOS 8? Eu verifiquei, mas não encontrei nada sobre isso


As respostas abaixo testam o idioma. Você deve usar a resposta de @ Galen que não.
— precisa saber é o seguinte

Respostas:


462

No iPad, o controlador de exibição de atividade será exibido como um popover usando o novo UIPopoverPresentationController , requer que você especifique um ponto de ancoragem para a apresentação do popover usando uma das três propriedades a seguir:

Para especificar o ponto de ancoragem, você precisará obter uma referência ao UIPopoverPresentationController do UIActivityController e definir uma das propriedades da seguinte maneira:

if ( [activityViewController respondsToSelector:@selector(popoverPresentationController)] ) { 
// iOS8
 activityViewController.popoverPresentationController.sourceView =
parentView;
 }

4
O @Daljeet de uma maneira simples seria colocar uma visualização transparente 1x1 onde quer que o ponto do popover apareça e usá-la como visualização âncora.
— Mason Cloud

3
@Obrigado mmccomb, eu apliquei seu código acima, ele funciona bem no iOS 8, mas meu aplicativo trava no iOS 7.Eu publiquei o mesmo problema no stackoverflow aqui está o link: stackoverflow.com/questions/26034149/… ajuda é apreciada
— Daljeet 25/09

48
@ Daljeet Isso travará no iOS7, pois o UIActivityController não possui uma propriedade popoverPresentationController lá. Utilize este código para fazê-lo funcionar em iOS8 e iOS7: if ( [activityViewController respondsToSelector:@selector(popoverPresentationController)] ) { // iOS8 activityViewController.popoverPresentationController.sourceView = _shareItem; }
— bluebamboo

4
no iOS8, o sourceRect não funcionou para mim. Eu tive que criar um souceView com esse rect, e que funcionou bem.
— Harris

10
@bluebamboo Por que você não adiciona sua sugestão como edição à resposta. É bastante fácil perder suas informações importantes nesses comentários.
— precisa saber é o seguinte

203

Mesmo problema chegou ao meu projeto, então eu encontrei a solução que para abrir o UIActivityViewControlleriPad nós temos que usarUIPopoverController

Aqui está um código para usá-lo no iPhone e no iPad:

//to attach the image and text with sharing 
UIImage *image=[UIImage imageNamed:@"giraffe.png"];
NSString *str=@"Image form My app";
NSArray *postItems=@[str,image];

UIActivityViewController *controller = [[UIActivityViewController alloc] initWithActivityItems:postItems applicationActivities:nil];

//if iPhone
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
    [self presentViewController:controller animated:YES completion:nil];
}
//if iPad
else {
    // Change Rect to position Popover
    UIPopoverController *popup = [[UIPopoverController alloc] initWithContentViewController:controller];
    [popup presentPopoverFromRect:CGRectMake(self.view.frame.size.width/2, self.view.frame.size.height/4, 0, 0)inView:self.view permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
}

Para velozes 4.2 / velozes 5

func openShareDilog() {
    let text = "share text will goes here"

    // set up activity view controller
    let textToShare = [text]
    let activityViewController = UIActivityViewController(activityItems: textToShare, applicationActivities: nil)
    activityViewController.excludedActivityTypes = [.airDrop]

    if let popoverController = activityViewController.popoverPresentationController {
        popoverController.sourceRect = CGRect(x: UIScreen.main.bounds.width / 2, y: UIScreen.main.bounds.height / 2, width: 0, height: 0)
        popoverController.sourceView = self.view
        popoverController.permittedArrowDirections = UIPopoverArrowDirection(rawValue: 0)
    }

    self.present(activityViewController, animated: true, completion: nil)
}

5
Por "mais recente" sintaxe Swift, UIPopoverArrowDirectionAny deve ser UIPopoverArrowDirection.Any e UIUserInterfaceIdiomPhone é UIUserInterfaceIdiom.Phone
— EPage_Ed

1
Muito obrigado, isso, combinado com os comentários de EPage_Ed, funciona no XCode 7 e no SWIFT 2. Fiz uma votação positiva.
— Oliver Zhang

1
UIPopoverController está obsoleta com iOS 9.
— cbartel

3
UIPopOverController está obsoleta no iOS 9.
— Leena

1
Todos devem responder dessa maneira, pois é mais fácil navegar pelas respostas do iOS do que pelas rápidas. Obrigado, cara, você fez o meu dia.
— MBH

42

Eu estava encontrando esse problema exato recentemente (a pergunta original) no Swift 2.0, onde UIActivityViewControllerfuncionou bem para iPhones, mas causou falhas ao simular iPads.

Eu só quero adicionar a este tópico de respostas aqui que, pelo menos no Swift 2.0, você não precisa de uma declaração if. Você pode apenas fazer o popoverPresentationControlleropcional.

Em resumo, a resposta aceita parece estar dizendo que você poderia ter apenas um sourceView, apenas um sourceRect ou apenas um barButtonItem, mas de acordo com a documentação da Apple para UIPopoverPresentationController, você precisa de um dos seguintes:

  • barButtonItem
  • sourceView e sourceRect

O exemplo específico em que eu estava trabalhando está abaixo, onde estou criando uma função que UIViewutiliza um (para o sourceView e sourceRect) e String(o único activityItem do UIActivityViewController).

func presentActivityViewController(sourceView: UIView, activityItem: String ) {

    let activityViewController = UIActivityViewController(activityItems: [activityItem], applicationActivities: [])

    activityViewController.popoverPresentationController?.sourceView = sourceView
    activityViewController.popoverPresentationController?.sourceRect = sourceView.bounds

    self.presentViewController(activityViewController, animated: true, completion: nil)
}

Esse código funciona no iPhone e iPad (e até no tvOS, eu acho) - se o dispositivo não suportar popoverPresentationController, as duas linhas de código que o mencionam serão essencialmente ignoradas.

Meio legal que tudo que você precisa fazer para que funcione para iPads é apenas adicionar duas linhas de código, ou apenas uma, se você estiver usando um barButtonItem!


2
Essa solução parece muito mais limpa do que as outras soluções que estão sendo usadas respondsToSelectorou UIUserInterfaceIdiomtambém parece se encaixar muito melhor no Swift. Embora respondsToSelectorpareça necessário para o iOS7, se estiver usando versões mais recentes do iOS, esse definitivamente parece ser o caminho.
— Marcus

18

Eu vejo muitas pessoas codificando o iPhone / iPad etc. enquanto usam o código Swift.

Isso não é necessário, você deve usar os recursos de idioma. O código a seguir assume que você vai usar um UIBarButtonItem e irá funcionar em ambos iPhone e iPad.

@IBAction func share(sender: AnyObject) {
    let vc = UIActivityViewController(activityItems: ["hello"], applicationActivities: nil)
    vc.popoverPresentationController?.barButtonItem = sender as? UIBarButtonItem
    self.presentViewController(vc, animated: true, completion: nil)
 }

Observe como não há declarações If ou qualquer outra coisa louca. O desembrulhar opcional será nulo no iPhone, portanto a linha vc.popoverPresentationController?não fará nada nos iPhones.


como seria esse olhar no contexto deste tutorial: hackingwithswift.com/read/3/2/...
— Dave Kliman

1
o tutorial não menciona o popoverPresentationController, para que o código falhe no iPad iOS 9.x. A solução seria adicionar vc.popoverPresentationController?.barButtonItem = navigationItem.rightBarButtonItemantes de apresentar o controlador de exibição.
— Martin Marconcini 29/02

oi Martin ... Eu já tinha uma linha parecida com esta, em shareTapped(): vc.popoverPresentationController?.barButtonItem = sender as? UIBarButtonItemambas ainda caíram. O que estou fazendo de errado? Enquanto estou aqui, como faço para preencher o popover com os diferentes serviços de compartilhamento, como Facebook, Twitter, etc.?
— precisa

E qual é o acidente? Você pode postar sua própria pergunta. Verifique se senderé realmente a UIBarButtonItem. Verifique se vc não é nil. Verifique se você pode apresentar um ViewController ... sem olhar para a falha / código é difícil de dizer. Os serviços serão preenchidos automaticamente se o usuário tiver o aplicativo instalado (a menos que você decida explicitamente excluir alguns).
— Martin Marconcini

1
@DaveKliman sim, o Xcode é muito ruim quando se trata de IBOutlets e IBActions que foram removidos / renomeados e trava na inicialização. Realmente não há uma maneira diferente de fazer isso no iOS, você precisa usar o Xcode e o InterfaceBuilder. Você pode fazer muitas coisas "no código", mas algumas exigem o "arrastar e soltar". A Apple quer que você use Storyboards e InterfaceBuilder, tanto quanto possível ... então se acostumar com isso :)
— Martin Marconcini

10

Solução usando o Xamarin.iOS.

No meu exemplo, estou fazendo uma captura de tela, produzindo uma imagem e permitindo que o usuário compartilhe a imagem. O pop-up no iPad é colocado no meio da tela.

var activityItems = new NSObject[] { image };
var excludedActivityTypes = new NSString[] {
    UIActivityType.PostToWeibo,
    UIActivityType.CopyToPasteboard,
    UIActivityType.AddToReadingList,
    UIActivityType.AssignToContact,
    UIActivityType.Print,
};
var activityViewController = new UIActivityViewController(activityItems, null);

//set subject line if email is used
var subject = new NSString("subject");
activityViewController.SetValueForKey(NSObject.FromObject("Goal Length"), subject);

activityViewController.ExcludedActivityTypes = excludedActivityTypes;
//configure for iPad, note if you do not your app will not pass app store review
if(null != activityViewController.PopoverPresentationController)
{
    activityViewController.PopoverPresentationController.SourceView = this.View;
    var frame = UIScreen.MainScreen.Bounds;
    frame.Height /= 2;
    activityViewController.PopoverPresentationController.SourceRect = frame;
}
this.PresentViewController(activityViewController, true, null);

Graças tanto de você :) funciona com formulários Xamarin usando um serviço de dependência
— Ricardo Romo

7

Swift, iOS 9/10 (após o UIPopoverController descontinuado)

let activityViewController = UIActivityViewController(activityItems: sharingItems, applicationActivities: nil)

    if UIDevice.currentDevice().userInterfaceIdiom == .Pad {

       if activityViewController.respondsToSelector(Selector("popoverPresentationController")) {
          activityViewController.popoverPresentationController?.sourceView = self.view
        }
    }

    self.presentViewController(activityViewController, animated: true, completion: nil)

3
O Swift 3 não suporta isso #
— Maksim Kniazev

5

No Swift para corrigir isso no iPad, a melhor maneira é fazer isso que eu encontrei.

    let things = ["Things to share"]
    let avc = UIActivityViewController(activityItems:things, applicationActivities:nil)
    avc.setValue("Subject title", forKey: "subject")
    avc.completionWithItemsHandler = {
        (s: String!, ok: Bool, items: [AnyObject]!, err:NSError!) -> Void in
    }

    self.presentViewController(avc, animated:true, completion:nil)
    if let pop = avc.popoverPresentationController {
        let v = sender as! UIView // sender would be the button view tapped, but could be any view
        pop.sourceView = v
        pop.sourceRect = v.bounds
    }

Use a resposta @Galen. Sem niet para verificação idioma quando o direcionamento iOS8
— doozMen

5

Se você mostrar UIActivityViewControllerquando clicar em um, UIBarButtonItemuse o seguinte código:

activityViewController.popoverPresentationController?.barButtonItem = sender

Caso contrário, se você usar outro controle, por exemplo UIButton, a , use o seguinte código:

activityViewController.popoverPresentationController?.sourceView = sender
activityViewController.popoverPresentationController?.sourceRect = sender.bounds

Da documentação ao UIPopoverPresentationController:

var barButtonItem: UIBarButtonItem? { get set }

Atribua um valor a essa propriedade para ancorar o popover ao item do botão de barra especificado. Quando apresentada, a seta do popover aponta para o item especificado. Como alternativa, você pode especificar o local da âncora para o popover usando as propriedades sourceView e sourceRect.


4

Correção para Swift 2.0

    if UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiom.Phone {
        self.presentViewController(activityVC, animated: true, completion: nil)
    }
    else {
        let popup: UIPopoverController = UIPopoverController(contentViewController: activityVC)
        popup.presentPopoverFromRect(CGRectMake(self.view.frame.size.width / 2, self.view.frame.size.height / 4, 0, 0), inView: self.view, permittedArrowDirections: UIPopoverArrowDirection.Any, animated: true)
    }

2
UIPopoverController foi preterido.
— LevinsonTechnologies

1
Obrigado!! Isso me ajudou muito.
— Oscar

4

Swift 3:

class func openShareActions(image: UIImage, vc: UIViewController) {
    let activityVC = UIActivityViewController(activityItems: [image], applicationActivities: nil)
    if UIDevice.current.userInterfaceIdiom == .pad {
        if activityVC.responds(to: #selector(getter: UIViewController.popoverPresentationController)) {
            activityVC.popoverPresentationController?.sourceView = vc.view
        }
    }
    vc.present(activityVC, animated: true, completion: nil)
}

3

Rápido:

    let activityViewController = UIActivityViewController(activityItems: sharingItems, applicationActivities: nil)

    //if iPhone
    if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiom.Phone) {
        self.presentViewController(activityViewController, animated: true, completion: nil)
    } else { //if iPad
        // Change Rect to position Popover
        var popoverCntlr = UIPopoverController(contentViewController: activityViewController)
        popoverCntlr.presentPopoverFromRect(CGRectMake(self.view.frame.size.width/2, self.view.frame.size.height/4, 0, 0), inView: self.view, permittedArrowDirections: UIPopoverArrowDirection.Any, animated: true)

    }

2

Solução para Objective-C e com o uso UIPopoverPresentationController

    UIActivityViewController *controller = /*Init your Controller*/;
    //if iPhone
    if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
        [self presentViewController:controller animated:YES completion:nil];
    }
    //if iPad
    else {
        UIPopoverPresentationController* popOver = controller.popoverPresentationController
        if(popOver){
            popOver.sourceView = controller.view;
            popOver.sourceRect = CGRectMake(self.view.frame.size.width/2, self.view.frame.size.height/4, 0, 0);
            [self presentViewController:controller animated:YES completion:nil];
        }
    }

1

Eu tentei o próximo código e funciona:

primeiro, coloque um item de botão de barra no seu View Controller e crie um IBOutlet:

@property(weak,nonatomic)IBOutlet UIBarButtonItem *barButtonItem;

próximo no arquivo .m: yourUIActivityViewController.popoverPresentationController.barButtonItem = self.barButtonItem;


1

swift = ios7 / ios8

let activityViewController = UIActivityViewController(activityItems: sharingItems, applicationActivities: nil)

//if iPhone
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiom.Phone) {
    // go on..
} else {
    //if iPad
    if activityViewController.respondsToSelector(Selector("popoverPresentationController")) {
        // on iOS8
        activityViewController.popoverPresentationController!.barButtonItem = self.shareButtonItem;
    }
}
self.presentViewController(activityViewController, animated: true, completion: nil)

0

Encontrei esta solução Em primeiro lugar, o seu controlador de exibição que está apresentando o popover deve implementar o <UIPopoverPresentationControllerDelegate>protocolo.

Em seguida, você precisará definir o popoverPresentationControllerdelegado.

Adicione estas funções:

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// Assuming you've hooked this all up in a Storyboard with a popover presentation style
    if ([segue.identifier isEqualToString:@"showPopover"]) {
        UINavigationController *destNav = segue.destinationViewController;
        PopoverContentsViewController *vc = destNav.viewControllers.firstObject;

        // This is the important part
        UIPopoverPresentationController *popPC = destNav.popoverPresentationController;
        popPC.delegate = self;
    }
}

- (UIModalPresentationStyle)adaptivePresentationStyleForPresentationController: (UIPresentationController *)controller {
    return UIModalPresentationNone;
}

0

No swift 4 seguinte código trabalhando no iphone e ipad. De acordo com a documentação

É de sua responsabilidade apresentar e dispensar o controlador de exibição usando os meios apropriados para o idioma do dispositivo. No iPad, você deve apresentar o controlador de exibição em um popover. Em outros dispositivos, você deve apresentá-lo modalmente.

 let activityViewController = UIActivityViewController(activityItems: activityitems, applicationActivities: nil)

    if UIDevice.current.userInterfaceIdiom == .pad {

        if activityViewController.responds(to: #selector(getter: UIViewController.popoverPresentationController)) {
            activityViewController.popoverPresentationController?.sourceView = self.view
        }
    }

    self.present(activityViewController, animated: true, completion: nil)

0

Estou usando o Swift 5. Tive o mesmo problema de travar ao clicar em "botão Compartilhar" no meu aplicativo no iPad. Encontrei uma solução. etapa 1: adicione o objeto "view" (pesquise "UIView" na biblioteca de objetos) ao Main.storyboard. Etapa 2: Crie um @IBOutlet no ViewController.swift e atribua qualquer nome (por exemplo: view1)

Etapa 3: adicione o nome acima (por exemplo: view1) como o sourceView. esta é a minha ação "botão Compartilhar".

@IBAction func Share(_ sender: Any) {
    let activityVC = UIActivityViewController(activityItems: ["www.google.com"], applicationActivities: nil)
    activityVC.popoverPresentationController?.sourceView = view1

    self.present(activityVC, animated: true, completion: nil)


}

Eu sou muito novo no rápido e fiquei preso nisso por uma semana. espero que isso ajude alguém. então, compartilhando esta solução.


Graças isso funcionou!
— Jnguyen22

-1

Para o Swift 2.0. Descobri que isso funciona se você estiver tentando ancorar o popover em um botão de compartilhamento no iPad. Isso pressupõe que você criou uma saída para o botão de compartilhamento na barra de ferramentas.

func share(sender: AnyObject) {
    let firstActivityItem = "test"

    let activityViewController = UIActivityViewController(activityItems: [firstActivityItem], applicationActivities: nil)

    if UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiom.Phone {
        self.presentViewController(activityViewController, animated: true, completion: nil)
    }
    else {            
        if activityViewController.respondsToSelector("popoverPresentationController") {
            activityViewController.popoverPresentationController!.barButtonItem = sender as? UIBarButtonItem
            self.presentViewController(activityViewController, animated: true, completion: nil)
        }

    }
}

-5

Tenha cuidado se você estiver desenvolvendo para o iPad usando o swift, ele funcionará bem na depuração, mas falhará no lançamento. Para fazê-lo funcionar com testFlight e AppStore, desative a otimização para uso rápido -nonepara liberação.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.