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

Bhumit Mehta
fonte
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;
 }
mmccomb
fonte
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)
}
Hardik Thakkar
fonte
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!

Galen
fonte
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.

Martin Marconcini
fonte
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);
ben
fonte
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)
MPaulo
fonte
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
    }
Niklas
fonte
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.

DronPop
fonte
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)
    }
datayeah
fonte
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)
}
Daniel McLean
fonte
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)

    }
kalpeshdeo
fonte
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];
        }
    }
kurono267
fonte
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;

Mike
fonte
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)
ingconti
fonte
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;
}
Mongo db
fonte
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)
Imran Khan
fonte
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.

Dishara
fonte
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)
        }

    }
}
iosdevlangley
fonte
-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.

ingconti
fonte