UIActivityViewController crashing on iOS 8 iPads
Asked Answered
A

21

308

I am currently testing my app with Xcode 6 (Beta 6). UIActivityViewController works fine with iPhone devices and simulators but crashes with iPad simulators and devices (iOS 8) with following logs

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

I am using following code for iPhone and iPad for both iOS 7 as well as 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];

I am getting a similar crash in of one my other app as well. Can you please guide me ? has anything changed with UIActivityViewController in iOS 8? I checked but i did not find anything on this

Armyworm answered 3/9, 2014 at 12:15 Comment(1)
Answers below test for the idiom. You should use @Galen's answer that does not.Savina
M
470

On iPad the activity view controller will be displayed as a popover using the new UIPopoverPresentationController, it requires that you specify an anchor point for the presentation of the popover using one of the three following properties:

In order to specify the anchor point you will need to obtain a reference to the UIActivityController's UIPopoverPresentationController and set one of the properties as follows:

if ( [activityViewController respondsToSelector:@selector(popoverPresentationController)] ) { 
// iOS8
 activityViewController.popoverPresentationController.sourceView =
parentView;
 }
Meteoritics answered 3/9, 2014 at 12:19 Comment(9)
@thanks mmccomb its working fine,could you please tell me how can i change the position of UIActivityViewController.Biggerstaff
@Biggerstaff a simple way would be to place a 1x1 transparent view wherever you want the point of the popover to appear and use that as the anchor view.Suu
@Thanks mmccomb,i have applied your code above it works fine on iOS 8,but my app crashes on iOS 7.I have posted a same problem on stackoverflow here is the link:#26034649 help is appreciatedBiggerstaff
@Biggerstaff This will crash on iOS7 since UIActivityController doesn't have a property popoverPresentationController there. Use this code to make it work on iOS8 and iOS7: if ( [activityViewController respondsToSelector:@selector(popoverPresentationController)] ) { // iOS8 activityViewController.popoverPresentationController.sourceView = _shareItem; }Kylakylah
in iOS8, sourceRect didn't work for me. i had to create a souceView with that rect, and that worked fine.Mesoglea
@Kylakylah Why don't you add your suggestion as an edit to the answer. It is fairly easy to miss your important information in these comments.Nympholepsy
So I have done all that and it sort of works except I'm getting a a lot of constrain errors. I'm not really doing much except calling that activityviewcontroller within a form sheet view controller on ipadDunghill
I am amazed by the day how stupid these APIs are created. All these things should be inferred by the API. Why we have to pass the souceView, sourceRect and other stuff when the button has all these informations. Showing a popover should be [UIPopoverView showPopoverUsingViewController:myVC anchoredOn:myButton withSize:mysize withArrowDirection:myArrow]; and that should be all. All other parameters are poor design.Montserrat
What should the parent view be?Ianiana
F
229

Same problem is come to my project then i found the solution that to open the UIActivityViewController in iPad we have to use UIPopoverController

Here is a code to use it in iPhone and iPad both:

//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];
}

For 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)
}
Foreworn answered 28/11, 2014 at 13:43 Comment(12)
For "newer" Swift syntax, UIPopoverArrowDirectionAny should be UIPopoverArrowDirection.Any and UIUserInterfaceIdiomPhone is UIUserInterfaceIdiom.PhoneLoadstar
Thanks a lot, this, combined with EPage_Ed's comments, works on XCode 7 and SWIFT 2. I've upvoted it.Browder
UIPopoverController is deprecated with iOS 9.Glisten
@Glisten first read the question it clearly says 'UIActivityViewController crashing on iOS8 iPads' so why you give minus ? do you have any problem and if you have a suggestion related to iOS9 then give answer i don't mind.Foreworn
you don't need the if statement… check my answer (https://mcmap.net/q/99362/-uiactivityviewcontroller-crashing-on-ios-8-ipads)Atrophied
no, we need if condition because for iPad UIActivityViewController need UIPopoverController to open option.. without it will crash app. read question for which i give answer. i am sure about objective C.Foreworn
UIPopOverController is deprecated in iOS 9.Ticonderoga
Everybody should answer this way as it is easier to navigate for ios answers than for swift.. Thank you man you made my day.Merrillmerrily
This answer has a lot of problems for Swift. See @Galen's answer below. It solves the deprecation issue and the need for an if statement.Coatbridge
You should use answer from @Galen when you are targeting iOS8 +Savina
This was the only solution that worked for me Swift 4.2/5Holoenzyme
UIPopoverController' is deprecated: first deprecated in iOS 9.0 - UIPopoverController is deprecated. Popovers are now implemented as UIViewController presentations. Use a modal presentation style of UIModalPresentationPopover and UIPopoverPresentationController.Evenson
N
48

I was encountering this exact problem recently (the original question) in Swift 2.0, where UIActivityViewController worked fine for iPhones, but caused crashes when simulating iPads.

I just want to add to this thread of answers here that, at least in Swift 2.0, you don't need an if statement. You can just make the popoverPresentationController optional.

As a quick aside, the accepted answer appears to be saying that you could have just a sourceView, just a sourceRect, or just a barButtonItem, but according to Apple's documentation for UIPopoverPresentationController you need one of the following:

  • barButtonItem
  • sourceView and sourceRect

The particular example I was working on is below, where I am creating a function that takes in a UIView (for the sourceView and sourceRect) and String (the UIActivityViewController's sole activityItem).

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

This code works on iPhone and iPad (and even tvOS I think) -- if the device does not support popoverPresentationController, the two lines of code that mention it are essentially ignored.

Kinda nice that all you need to do to make it work for iPads is just add two lines of code, or just one if you're using a barButtonItem!

Neptunium answered 4/1, 2016 at 16:51 Comment(1)
This solution seems much cleaner than the other solutions that are having to use respondsToSelector or UIUserInterfaceIdiom, also it seems to fit Swift as a language much better. Although the respondsToSelector seems like it's necessary for iOS7, if using newer versions of iOS this definitely seems to be the way to go.Micropyle
A
22

I see a lot of people hardcoding iPhone/iPad etc. while using Swift code.

This is not needed, you have to use the language features. The following code assumes you will use a UIBarButtonItem and will work on both iPhone and 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)
 }

Notice how there are no If statements or any other crazy thing. The optional unwrapping will be nil on iPhone, so the line vc.popoverPresentationController? will not do anything on iPhones.

Atrophied answered 22/1, 2016 at 1:42 Comment(12)
how would that look in the context of this tutorial: hackingwithswift.com/read/3/2/…Dulcet
the tutorial doesn't mention the popoverPresentationController, so that code will crash on iPad iOS 9.x. The solution would be to add vc.popoverPresentationController?.barButtonItem = navigationItem.rightBarButtonItem before you present the view controller.Atrophied
hi Martin... I already had a line looking like this, in shareTapped(): vc.popoverPresentationController?.barButtonItem = sender as? UIBarButtonItem both still crashed. What am I doing wrong? while i'm here, how do i populate the popover with the different share services such as Facebook, twitter, etc?Dulcet
And what is the crash? You might want to post your own question. Check that sender is indeed a UIBarButtonItem. Check that vc is not nil. Check that you can present a ViewController… without looking at the crash/code is hard to tell. The services will be automatically populated if the user has the app installed (unless you explicitly decided to exclude some).Atrophied
hi again, Martin! Thanks for your answer. you're right. I should post a question on it... I've just started learning this version of Xcode and it seems to be notorious for reporting errors that don't necessarily have much to do with what's really the matter.Dulcet
Actually, before going to do that, I looked it over more carefully, and found i had, in NooB fashion, introduced a typo that i simply wasn't noticing because there were no compiler errors for it... i had put @IBAction func shareTapped() instead of func shareTapped()Dulcet
I'm curious, what's the typo? the func shareTapped you typed is the same :D You meant you added @IBAction where it didn't belong? (that shouldn't matter since it's an Xcode thing, but you can call the method anyway) (I think).Atrophied
Well, apparently, I couldn't make sharetapped() into an IBAction... And there isn't a sender... I had just copied your initial answer in and put the new line in without reverting the function line... As a beginner, it's weird for me to see all these arbitrary things... I think the seasoned programmers just get used to all the idiosyncratic ways of this system without even noticing how crazy it is...the other thing is I'm wondering if there is a more generic way to accomplish this same result... Xcode doesn't mind but it crashes in runtime..Dulcet
@DaveKliman yes, Xcode is very bad when it comes to IBOutlets and IBActions that have been removed/renamed and it will crash upon startup. There's really not a different way to do it on iOS, you need to use Xcode and InterfaceBuilder. You can do a lot of things "in code" but some require the "drag and dropping". Apple wants you to use Storyboards and InterfaceBuilder as much as possible… so get used to it :)Atrophied
I'm not a fan of hardwiring in general heh :-xDulcet
@MartínMarconcini Great find, simpler code, happier self :)Unleavened
Same answer as @NeptuniumSavina
O
11

Solution using Xamarin.iOS.

In my example I'm doing a screen capture, producing an image, and allowing the user to share the image. The pop up on the iPad is placed about in the middle of the screen.

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);
Omphale answered 2/12, 2014 at 13:15 Comment(1)
Thanks you so much :) works with Xamarin Forms using a Dependency ServiceVaduz
P
9

SwiftUI Version

func presentActivityView(items: [Any]){
    let activityController = UIActivityViewController(activityItems: items, applicationActivities: nil)
    if UIDevice.current.userInterfaceIdiom == .pad{
        activityController.popoverPresentationController?.sourceView = UIApplication.shared.windows.first
        activityController.popoverPresentationController?.sourceRect = CGRect(x:  UIScreen.main.bounds.width / 3, y:  UIScreen.main.bounds.height / 1.5, width: 400, height: 400)
    }
    UIApplication.shared.windows.first?.rootViewController?.present(activityController, animated: true, completion: nil)
}
Phidippides answered 19/1, 2021 at 4:22 Comment(2)
It's working but Cannot find 'ScreenSize' in the scope that's why use UIScreen.main.bounds.height and width.Nautical
Oh! I've forgotten it. ScreenSize is a custom class that I use for sizing but its so simple. ScreenSize.width is equal to UIScreen.main.bounds.height. I created that class to make it more clear and shorter. I'll rearrange the answer asapPhidippides
R
7

Swift, iOS 9/10 (after UIPopoverController deprecated)

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)
Renatorenaud answered 10/8, 2016 at 4:14 Comment(1)
Swift 3 does not support thisPianola
S
6

If you show UIActivityViewController when you click on an UIBarButtonItem use the following code:

activityViewController.popoverPresentationController?.barButtonItem = sender

Otherwise, if you use another control, for example a UIButton, use the following code:

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

From the documentation to the UIPopoverPresentationController:

var barButtonItem: UIBarButtonItem? { get set }

Assign a value to this property to anchor the popover to the specified bar button item. When presented, the popover’s arrow points to the specified item. Alternatively, you may specify the anchor location for the popover using the sourceView and sourceRect properties.

Sitting answered 3/2, 2019 at 14:26 Comment(0)
B
5

In Swift to fix this for iPad, best way is to do like this I found.

    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
    }
Bruell answered 29/6, 2015 at 17:1 Comment(1)
Use @Neptunium answer. No niet for idiom check when targeting iOS8+Savina
H
5

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)
}
Henshaw answered 19/2, 2017 at 19:17 Comment(0)
D
5

Solution for Objective-C and with use 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];
        }
    }
Downgrade answered 11/1, 2019 at 4:16 Comment(0)
S
4

Fix for 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)
    }
Suntan answered 23/10, 2015 at 0:3 Comment(2)
UIPopoverController has been deprecated.Parturient
Thank you!! It helped me lots.Rosenblum
V
4

Just for reference.

I share the following code, this one uses Swift 5.0. Also, it avoids deprecated windows.

UIApplication.shared.windows.first?.rootViewController!.present(activityController, animated: true, completion: nil)

The snippet is as follows:

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

let scenes = UIApplication.shared.connectedScenes 
let windowScene = scenes.first as? UIWindowScene 
let window = windowScene?.windows.first 

if UIDevice.current.userInterfaceIdiom == .pad{
    activityController.popoverPresentationController?.sourceView = window
    activityController.popoverPresentationController?.sourceRect = CGRect(x:  UIScreen.main.bounds.width / 3, y:  UIScreen.main.bounds.height / 1.5, width: 400, height: 400)
}

window?.rootViewController!.present(activityController, animated: true)

It has been tested on iPhone and iPad.

Hope it helps to some one.

Cheers!

Vicinage answered 16/6, 2022 at 23:29 Comment(0)
D
3

Swift:

    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)

    }
Deltoro answered 6/6, 2015 at 18:50 Comment(0)
G
2

In swift 4 following code working in iphone and ipad. According documentation

It is your responsibility to present and dismiss the view controller using the appropriate means for the given device idiom. On iPad, you must present the view controller in a popover. On other devices, you must present it modally.

 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)
Galitea answered 15/11, 2018 at 6:4 Comment(0)
S
1

I tried the next code and it works:

first put a bar button item in your View Controller then create an IBOutlet:

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

next in the .m file: yourUIActivityViewController.popoverPresentationController.barButtonItem = self.barButtonItem;

Scrooge answered 10/4, 2015 at 9:35 Comment(0)
W
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)
Wallen answered 18/6, 2015 at 15:15 Comment(0)
M
1

Im using Swift 5. I had the same issue of crashing when I click "Share button" in my app on iPad. Found a this solution. step 1: Add "view" object ( search "UIView" in the object library) to the Main.storyboard. step 2: Create a @IBOutlet in ViewController.swift and assign any name( eg: view1)

step 3: add the above name ( eg: view1) as the sourceView. this is my "Share button" action.

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


}

I am very new to swift and was stuck on this for a week. hope this will help someone. so sharing this solution.

Mozza answered 9/5, 2020 at 18:5 Comment(0)
P
0

I found this solution Firstly, your view controller that's presenting the popover should implement the <UIPopoverPresentationControllerDelegate> protocol.

Next, you'll need to set the popoverPresentationController's delegate.

Add these functions:

- (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;
}
Physique answered 7/10, 2014 at 10:48 Comment(0)
P
-1

For Swift 2.0. I found that this works if you are trying to anchor the popover to a share button on iPad. This assumes that you have created an outlet for the share button in your tool bar.

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

    }
}
Perce answered 14/1, 2016 at 19:4 Comment(0)
W
-5

Be careful if you are developing for iPad using swift, it will work fine in debug, but will crash in release. In order to make it work with testFlight and AppStore, disable optimization for swift using -none for release.

Wallen answered 19/6, 2015 at 11:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.