10

On iOS8 I'm using a UIActivityViewController to share a UIImage to Facebook/Twitter etc. It seemed to be working fine, but today it suddenly started crashing when running the code on my iPad. However, it still works as expected in the simulator.

My code:

UIActivityViewController *controller =
[[UIActivityViewController alloc]
 initWithActivityItems:@[text, url, myImage]
 applicationActivities:nil];

[self presentViewController:controller animated:YES completion:nil];

Upon crashing, Xcode spits out:

Discovered extensions: {( {id = com.apple.share.Facebook.post}, {id = com.apple.share.Twitter.post}, {id = com.apple.share.TencentWeibo.post}, {id = com.apple.share.SinaWeibo.post} )} for attributes: { NSExtensionActivationRule = { extensionItems = ( { attachments = ( { registeredTypeIdentifiers = ( "public.image" ); }, { registeredTypeIdentifiers = ( "public.plain-text" ); }, { registeredTypeIdentifiers = ( "public.url" ); } ); } ); }; NSExtensionPointName = ( "com.apple.share-services", "com.apple.ui-services", "com.apple.services" ); } 2014-08-07 21:38:59.208 collageTest[279:11021] LaunchServices: invalidationHandler called 2014-08-07 21:38:59.212 collageTest[279:11016] Discovered extensions: {( {id = com.apple.share.Flickr.post}, {id = com.apple.mobileslideshow.StreamShareService}, {id = com.apple.share.Twitter.post}, {id = com.apple.share.Facebook.post}, {id = com.apple.share.Vimeo.post}, {id = com.apple.share.SinaWeibo.post}, {id = com.apple.share.TencentWeibo.post} )} for attributes: { NSExtensionPointName = "com.apple.share-services"; } 2014-08-07 21:38:59.216 collageTest[279:11021] LaunchServices: invalidationHandler called

Christian J. B.
  • 491
  • 1
  • 5
  • 16

5 Answers5

14

Looking at the docs, I needed to define a source view for the popover controller

UIActivityViewController *controller =
[[UIActivityViewController alloc]
 initWithActivityItems:@[text,url,myImage]
 applicationActivities:nil];

[self presentViewController:controller animated:YES completion:nil];

UIPopoverPresentationController *presentationController =
[controller popoverPresentationController];

presentationController.sourceView = self.view;
Christian J. B.
  • 491
  • 1
  • 5
  • 16
  • Same issue I got in my project. Have you find the reason behind of it? – S1U Sep 17 '14 at 05:16
  • I've tried the same thing in Swift, but Xcode force unwraps popoverPresentationController, and when I build and run I still see the invalidationHandler issue and it crashes upon finding popoverPresentationController nil. :/ – davidrayowens Sep 17 '14 at 06:06
  • 1
    This worked for me, I also like to set presentationController.sourceRect so it doesn't show from the screen corner. – Siegfoult Sep 18 '14 at 17:15
14

popoverPresentationController was new to iOS 8 and will crash on iOS 7. It'll also be nil on iPhone because it's only in a UIPopover on iPad. Here's Christian's answer in Swift, with those facts taken into account:

Swift 2.0 (Xcode 7)

let controller = UIActivityViewController(activityItems: [text, url, myImage], applicationActivities: nil)

presentViewController(controller, animated: true, completion: nil)

if #available(iOS 8.0, *) {
    let presentationController = controller.popoverPresentationController
    presentationController?.sourceView = view
}

Swift 1.2 (Xcode 6)

let controller = UIActivityViewController(activityItems: [text, url, myImage], applicationActivities: nil)

presentViewController(controller, animated: true, completion: nil)

if controller.respondsToSelector("popoverPresentationController") {
    // iOS 8+
    let presentationController = controller.popoverPresentationController
    presentationController?.sourceView = view
}
Andrew
  • 2,438
  • 1
  • 19
  • 19
  • This answer is important if you're trying to use Swift on iOS 7 and 8. There's not a great way to check the version but this one works. – robertfiorentino Dec 14 '14 at 17:25
  • So I'm using the respondsToSelector("popoverPresentationController"), however when I Validate, iTunes rejects it saying it's a non-public selector. I'm guessing because it doesn't exist on iOS 7 (i.e. "non-public"), and our app is supposed to work on both 7 and 8. What's the solution here? Check iOS version? – phreakhead Jan 31 '15 at 01:58
  • Were you unable to submit something to the App Store? – Crashalot Jun 05 '15 at 19:43
  • 3
    In Swift 2.0 you can now use `if #available(iOS 8.0, *) { ... }` – Patrick Pijnappel Aug 28 '15 at 10:23
0

As @mmccomb told here, on iPad the activity view controller will be displayed as a popover using the new UIPopoverPresentationController. You need to specify at least the source view:

activityViewController.popoverPresentationController.sourceView = YOURDESIREDVIEW;

If you want to show the popover anchored to any point to that view, specify it with the sourceRect property of the popoverPresentationController.

Community
  • 1
  • 1
jomafer
  • 2,655
  • 1
  • 32
  • 47
0

Here is how I solved with swift:

    let someText:String = "shareText"
    let google:NSURL = NSURL(string:"http://google.com")!

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

    if respondsToSelector("popoverPresentationController") {
        self.senderView.presentViewController(activityViewController, animated: true, completion: nil)
        activityViewController.popoverPresentationController?.sourceView = sender
    }else{
        senderView.presentViewController(activityViewController, animated: true, completion: nil)
    }
Esqarrouth
  • 38,543
  • 21
  • 161
  • 168
  • Were you able to submit to the App Store and support iOS 7? – Crashalot Jun 05 '15 at 19:46
  • 1
    Yup, Swift is backwards compatible since it bundles the entire language runtime in the app itself. Apps that contain even a single line of Swift are ~5MB larger in size. – Andrew Jun 05 '15 at 20:37
  • Damn, I didn't know they were larger. Is there a resource you could link to me about this details? – Esqarrouth Jun 05 '15 at 20:52
0

Swift 5 - Copy from Working Project

Problem

If you present UIActivityViewController for sharing in the following way, your app will work correctly but it will crash on iPad devices.

func shareSiteURL()
{
    let title = "My app"
    let url = URL(string: "https://myWebSite.com")

    let activityController = UIActivityViewController(activityItems: [url!, title], applicationActivities: nil)
    self.present(activityController, animated: true, completion: nil)
}

Explanation

When you presented UIActivityViewController on the iPad, the iPad presents the activity controller as a popover controller and hence it needs to know and use the source view. On the previous code, we did not pass the source view in case if the device is an iPad and that is why the app crashed when running on iPad.

Solution

To solve this issue, we are going to check if the controller is presented as a popover (the app is running on iPad) and if so, we will pass the source view otherwise the app will crash.

func shareSiteURL()
{
    let title = "My app"
    let url = URL(string: "https://myWebSite.com")

    // check if the controller is presented as a popover (the app is running on iPad)
    // and if so, we will pass the source view otherwise the app will crash.

    if let popoverController = activityController.popoverPresentationController
    {
        popoverController.sourceView = viewController.view
    }

    let activityController = UIActivityViewController(activityItems: [url!, title], applicationActivities: nil)
    self.present(activityController, animated: true, completion: nil)
}
Essam Fahmi
  • 1,920
  • 24
  • 31