UIActivityViewController plante sur les iPad iOS 8

288

Je teste actuellement mon application avec Xcode 6 (Beta 6). UIActivityViewController fonctionne correctement avec les appareils et simulateurs iPhone mais se bloque avec les simulateurs et appareils iPad (iOS 8) avec les journaux suivants

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

J'utilise le code suivant pour iPhone et iPad pour iOS 7 ainsi que 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];

Je reçois également un crash similaire dans l'une de mes autres applications. Pouvez-vous me guider s'il vous plaît? est-ce que quelque chose a changé avec UIActivityViewController dans iOS 8? J'ai vérifié mais je n'ai rien trouvé à ce sujet

Bhumit Mehta
la source
Les réponses ci-dessous testent l'idiome. Vous devez utiliser la réponse de @ Galen qui ne fonctionne pas.
doozMen

Réponses:

462

Sur iPad, le contrôleur de vue d'activité sera affiché en tant que popover en utilisant le nouveau UIPopoverPresentationController , il nécessite que vous spécifiez un point d'ancrage pour la présentation du popover en utilisant l'une des trois propriétés suivantes:

Afin de spécifier le point d'ancrage, vous devrez obtenir une référence à l'UIPopoverPresentationController de UIActivityController et définir l'une des propriétés comme suit:

if ( [activityViewController respondsToSelector:@selector(popoverPresentationController)] ) { 
// iOS8
 activityViewController.popoverPresentationController.sourceView =
parentView;
 }
mmccomb
la source
4
@Daljeet un moyen simple serait de placer une vue transparente 1x1 où vous voulez que le point du popover apparaisse et de l'utiliser comme vue d'ancrage.
Mason Cloud
3
@Merci mmccomb, j'ai appliqué votre code ci-dessus, cela fonctionne bien sur iOS 8, mais mon application se bloque sur iOS 7.J'ai posté un même problème sur stackoverflow voici le lien: stackoverflow.com/questions/26034149/… l' aide est appréciée
Daljeet
48
@Daljeet Cela va planter sur iOS7 car UIActivityController n'a pas de propriété popoverPresentationController là-bas. Utilisez ce code pour le faire fonctionner sur iOS8 et iOS7: if ( [activityViewController respondsToSelector:@selector(popoverPresentationController)] ) { // iOS8 activityViewController.popoverPresentationController.sourceView = _shareItem; }
bluebamboo
4
dans iOS8, sourceRect ne fonctionnait pas pour moi. j'ai dû créer un souceView avec ce rect, et cela a bien fonctionné.
Harris
10
@bluebamboo Pourquoi n'ajoutez-vous pas votre suggestion en tant que modification à la réponse. Il est assez facile de manquer vos informations importantes dans ces commentaires.
Ayush Goel
204

Le même problème est arrivé à mon projet, j'ai trouvé la solution pour ouvrir l' UIActivityViewControlleriPad que nous devons utiliserUIPopoverController

Voici un code pour l'utiliser à la fois sur iPhone et 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];
}

Pour swift 4.2 / swift 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
la source
5
Pour la syntaxe Swift "plus récente", UIPopoverArrowDirectionAny doit être UIPopoverArrowDirection.Any et UIUserInterfaceIdiomPhone est UIUserInterfaceIdiom.Phone
EPage_Ed
1
Merci beaucoup, ceci, combiné avec les commentaires d'EPage_Ed, fonctionne sur XCode 7 et SWIFT 2. J'ai voté pour.
Oliver Zhang
1
UIPopoverController est obsolète avec iOS 9.
cbartel
3
UIPopOverController est déconseillé dans iOS 9.
Leena
1
Tout le monde devrait répondre de cette façon car il est plus facile de naviguer pour les réponses ios que pour les réponses rapides. Merci mec, tu as fait ma journée.
MBH
42

Je rencontrais ce problème exact récemment (la question d'origine) dans Swift 2.0, où cela UIActivityViewControllerfonctionnait bien pour les iPhones, mais provoquait des plantages lors de la simulation des iPads.

Je veux juste ajouter à ce fil de réponses ici que, au moins dans Swift 2.0, vous n'avez pas besoin d'une instruction if. Vous pouvez simplement faire l' popoverPresentationControlleroption.

En bref, la réponse acceptée semble dire que vous pourriez avoir juste une sourceView, juste un sourceRect ou simplement un barButtonItem, mais selon la documentation d' Apple pour UIPopoverPresentationController, vous avez besoin de l'un des éléments suivants:

  • barButtonItem
  • SourceView et sourceRect

L'exemple particulier sur lequel je travaillais est ci-dessous, où je crée une fonction qui prend un UIView(pour le sourceView et sourceRect) et String(le seul activityItem de 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)
}

Ce code fonctionne sur iPhone et iPad (et même tvOS je pense) - si l'appareil ne prend pas en charge popoverPresentationController, les deux lignes de code qui le mentionnent sont essentiellement ignorées.

Un peu sympa que tout ce que vous devez faire pour que cela fonctionne pour les iPad est d'ajouter simplement deux lignes de code, ou une seule si vous utilisez un barButtonItem!

Galen
la source
2
Cette solution semble beaucoup plus propre que les autres solutions qui doivent être utilisées respondsToSelectorou UIUserInterfaceIdiom, elle semble également mieux convenir à Swift en tant que langue. Bien que cela respondsToSelectorsemble nécessaire pour iOS7, si vous utilisez des versions plus récentes d'iOS, cela semble définitivement être la voie à suivre.
Marcus
18

Je vois beaucoup de gens coder en dur l'iPhone / iPad, etc. lors de l'utilisation du code Swift.

Ce n'est pas nécessaire, vous devez utiliser les fonctionnalités linguistiques. Le code suivant suppose que vous utiliserez un UIBarButtonItem et travaillerez sur les deux iPhone et 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)
 }

Remarquez qu'il n'y a pas d'instructions If ou toute autre chose folle. Le déballage en option sera nul sur iPhone, donc la ligne vc.popoverPresentationController?ne fera rien sur les iPhones.

Martin Marconcini
la source
à quoi cela ressemblerait dans le contexte de ce tutoriel: hackingwithswift.com/read/3/2/…
Dave Kliman
1
le didacticiel ne mentionne pas le popoverPresentationController, de sorte que le code se bloque sur iPad iOS 9.x. La solution serait d'ajouter vc.popoverPresentationController?.barButtonItem = navigationItem.rightBarButtonItemavant de présenter le contrôleur de vue.
Martin Marconcini
salut Martin ... J'avais déjà une ligne ressemblant à ça, dans shareTapped(): les vc.popoverPresentationController?.barButtonItem = sender as? UIBarButtonItemdeux se sont toujours écrasés. Qu'est-ce que je fais mal? pendant que je suis ici, comment puis-je remplir le popover avec les différents services de partage tels que Facebook, twitter, etc.?
Dave Kliman
Et quel est le crash? Vous voudrez peut-être publier votre propre question. Vérifiez qu'il senders'agit bien d'un UIBarButtonItem. Vérifiez que vc ne l'est pas nil. Vérifiez que vous pouvez présenter un ViewController… sans regarder le plantage / le code est difficile à dire. Les services seront automatiquement renseignés si l'utilisateur a installé l'application (sauf si vous avez explicitement décidé d'en exclure certains).
Martin Marconcini
1
@DaveKliman oui, Xcode est très mauvais en ce qui concerne les IBOutlets et IBActions qui ont été supprimés / renommés et il se bloquera au démarrage. Il n'y a vraiment pas de manière différente de le faire sur iOS, vous devez utiliser Xcode et InterfaceBuilder. Vous pouvez faire beaucoup de choses "dans le code" mais certaines nécessitent le "glisser-déposer". Apple veut que vous utilisiez autant que possible les storyboards et InterfaceBuilder… alors habituez-vous :)
Martin Marconcini
10

Solution utilisant Xamarin.iOS.

Dans mon exemple, je fais une capture d'écran, produisant une image et permettant à l'utilisateur de partager l'image. La fenêtre contextuelle sur l'iPad est placée au milieu de l'écran.

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
la source
Merci beaucoup :) fonctionne avec Xamarin Forms en utilisant un service de dépendance
Ricardo Romo
7

Swift, iOS 9/10 (après UIPopoverController obsolète)

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
la source
3
Swift 3 ne prend pas en charge cela
Maksim Kniazev
5

Dans Swift pour résoudre ce problème sur iPad, le meilleur moyen est de faire comme cela que j'ai trouvé.

    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
la source
Utilisez la réponse @Galen. Pas de niet pour la vérification de l'idiome lors du ciblage d'iOS8 +
doozMen
5

Si vous affichez UIActivityViewControllerlorsque vous cliquez sur un, UIBarButtonItemutilisez le code suivant:

activityViewController.popoverPresentationController?.barButtonItem = sender

Sinon, si vous utilisez un autre contrôle, par exemple un UIButton, utilisez le code suivant:

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

De la documentation au UIPopoverPresentationController:

var barButtonItem: UIBarButtonItem? { get set }

Attribuez une valeur à cette propriété pour ancrer le popover à l'élément de bouton de barre spécifié. Lorsqu'elle est présentée, la flèche du popover pointe vers l'élément spécifié. Vous pouvez également spécifier l'emplacement d'ancrage pour le popover à l'aide des propriétés sourceView et sourceRect.

DronPop
la source
4

Correctif pour 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)
    }
ouais
la source
2
UIPopoverController est obsolète.
LevinsonTechnologies
1
Je vous remercie!! Cela m'a beaucoup aidé.
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
la source
3

Rapide:

    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
la source
2

Solution pour Objective-C et avec utilisation 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
la source
1

J'ai essayé le code suivant et ça marche:

placez d'abord un élément de bouton de barre dans votre View Controller puis créez un IBOutlet:

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

suivant dans le fichier .m: yourUIActivityViewController.popoverPresentationController.barButtonItem = self.barButtonItem;

Mike
la source
1

rapide = 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
la source
0

J'ai trouvé cette solution Premièrement, votre contrôleur de vue qui présente le popover devrait implémenter le <UIPopoverPresentationControllerDelegate>protocole.

Ensuite, vous devrez définir le popoverPresentationControllerdélégué de.

Ajoutez ces fonctions:

- (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
la source
0

Dans swift 4, le code suivant fonctionne sur iPhone et iPad. Selon la documentation

Il est de votre responsabilité de présenter et de rejeter le contrôleur de vue en utilisant les moyens appropriés pour l'idiome de périphérique donné. Sur iPad, vous devez présenter le contrôleur de vue dans une fenêtre contextuelle. Sur les autres appareils, vous devez le présenter de façon modale.

 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
la source
0

J'utilise Swift 5. J'ai eu le même problème de plantage lorsque je clique sur le "bouton Partager" dans mon application sur iPad. J'ai trouvé cette solution. étape 1: Ajoutez l'objet "view" (recherchez "UIView" dans la bibliothèque d'objets) au Main.storyboard. étape 2: créer un @IBOutlet dans ViewController.swift et attribuer un nom (par exemple: view1)

étape 3: ajoutez le nom ci-dessus (par exemple: view1) comme sourceView. c'est mon action "bouton Partager".

@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)


}

Je suis très nouveau pour swift et je suis resté coincé là-dessus pendant une semaine. J'espère que cela aidera quelqu'un. afin de partager cette solution.

Dishara
la source
Merci, cela a fonctionné!
Jnguyen22
-1

Pour Swift 2.0. J'ai trouvé que cela fonctionne si vous essayez d'ancrer le popover à un bouton de partage sur iPad. Cela suppose que vous avez créé une sortie pour le bouton de partage dans votre barre d'outils.

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
la source
-5

Soyez prudent si vous développez pour iPad en utilisant swift, cela fonctionnera bien dans le débogage, mais plantera lors de la sortie. Afin de le faire fonctionner avec testFlight et AppStore, désactivez l'optimisation pour swift en utilisant -nonepour la version.

ingconti
la source