UIActivityViewController在iOS 8的iPad上崩溃

309

我目前正在使用Xcode 6(Beta 6)测试我的应用程序。 UIActivityViewController在iPhone设备和模拟器上运行良好,但在iPad模拟器和设备(iOS 8)上崩溃,以下是日志:

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

我正在使用以下代码,适用于iPhone和iPad,可同时在iOS 7和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];

我在我的另一个应用程序中也遇到了类似的崩溃。您能否指导我?iOS 8中的UIActivityViewController是否有任何更改?我已经检查过,但没有发现任何更改。


以下是关于编程的内容,请将其翻译为中文。请仅返回已翻译的文本:答案下面测试短语的成语。您应该使用@Galen的答案,它不会。 - doozMen
21个回答

471

iPad上的活动视图控制器将以弹出窗口的形式显示,使用新的UIPopoverPresentationController,它需要您使用以下三个属性之一指定弹出窗口的锚点:

为了指定锚点,您需要获取对UIActivityController的UIPopoverPresentationController的引用,并设置其中一个属性如下:

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

5
一种简单的方法是在您想要弹出窗口点出现的位置放置一个 1x1 的透明视图,并将其用作锚视图。 - Mason Cloud
4
谢谢 mmccomb,我已经应用了你上面的代码,它在 iOS 8 上运行良好,但我的应用在 iOS 7 上崩溃了。我在stackoverflow上发布了同样的问题,这是链接:https://dev59.com/_oPba4cB1Zd3GeqPv7av 希望能得到帮助。 - Daljeet
49
在iOS7上,由于UIActivityController没有popoverPresentationController属性,所以这段代码会导致崩溃。使用以下代码可以在iOS8和iOS7上运行:`if ( [activityViewController respondsToSelector:@selector(popoverPresentationController)] ) { // iOS8 activityViewController.popoverPresentationController.sourceView = _shareItem; }` - bluebamboo
5
在iOS8中,sourceRect对我无效。我不得不创建一个带有该矩形的sourceView,这样就可以正常工作了。 - Elijah
11
@bluebamboo 为什么不将您的建议作为答案的编辑添加进去呢?在这些评论中很容易错过您重要的信息。 - Ayush Goel
显示剩余4条评论

229

我的项目遇到了同样的问题,后来我找到了解决方案:在iPad上打开UIActivityViewController时,我们需要使用UIPopoverController

以下是在iPhone和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];
}

适用于 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)
}

5
对于较新的 Swift 语法,UIPopoverArrowDirectionAny 应该改为 UIPopoverArrowDirection.Any,UIUserInterfaceIdiomPhone 则应改为 UIUserInterfaceIdiom.Phone。 - EPage_Ed
1
非常感谢,这个结合了EPage_Ed的评论,在XCode 7和SWIFT 2上可以运行。我已经点赞了它。 - Oliver Zhang
1
UIPopoverController在iOS 9中已经被废弃。 - cbartel
3
在iOS 9中,UIPopOverController已被弃用。 - Leena
1
每个人都应该这样回答,因为对于 iOS 的答案来说比 Swift 更容易导航。谢谢你,伙计,你让我的一天变得更美好了。 - MBH
显示剩余8条评论

48

最近我在Swift 2.0中遇到了与原问题完全相同的问题(即原问题),其中UIActivityViewController能够在 iPhone 上正常工作,但在模拟iPad时会导致崩溃。

我只是想在这里添加一个答案,至少在Swift 2.0中,您不需要使用if语句。您可以将popoverPresentationController设置为可选项。

顺便说一下,接受的答案似乎是在说您可以只有sourceView、只有sourceRect或只有barButtonItem,但根据Apple对UIPopoverPresentationController的文档,您需要以下之一:

  • barButtonItem
  • sourceViewsourceRect

我正在处理的特定示例如下,我正在创建一个函数,该函数以UIView(用于sourceView和sourceRect)和String(UIActivityViewController的唯一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)
}

如果设备不支持 popoverPresentationController,则提到它的两行代码将被忽略。这段代码适用于 iPhone 和 iPad(甚至是 tvOS)。

很不错,只需添加两行代码就可以使其适用于 iPad,如果使用 barButtonItem 的话,只需添加一行!


2
这个解决方案看起来比那些需要使用respondsToSelector或者UIUserInterfaceIdiom的解决方案更加优雅,同时也更加适用于Swift语言。虽然在iOS 7上respondsToSelector似乎是必要的,但如果使用较新版本的iOS,那么肯定应该采用这种方式。 - Marcus

22

我看到很多人在使用Swift代码时硬编码iPhone/iPad等设备。

这并不需要,你必须使用语言特性。以下代码假定您将使用UIBarButtonItem,并且可以在两种iPhone和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)
 }

请注意没有任何If语句或其他疯狂的事情。在iPhone上,可选拆包将为nil,因此vc.popoverPresentationController?一行在iPhone上不起作用。


1
教程没有提到popoverPresentationController,所以那段代码会在iPad iOS 9.x上崩溃。解决方法是在呈现视图控制器之前添加 vc.popoverPresentationController?.barButtonItem = navigationItem.rightBarButtonItem - Martin Marconcini
嗨马丁,我已经有了一条类似这样的代码,在shareTapped()函数中:vc.popoverPresentationController?.barButtonItem = sender as? UIBarButtonItem 但两个还是都会崩溃。我做错了什么?顺便问一下,如何使用不同的共享服务(如Facebook、Twitter等)填充弹出窗? - Dave Kliman
再次你好,马丁!感谢你的回答。你说得对。我应该在上面发一个问题...我刚开始学习这个版本的Xcode,它似乎以报告与实际问题不太相关的错误而臭名昭著。 - Dave Kliman
1
@DaveKliman 是的,当涉及到已被删除/重命名的IBOutlets和IBActions时,Xcode非常糟糕,它会在启动时崩溃。在iOS上没有其他不同的方法,您需要使用Xcode和InterfaceBuilder。您可以在代码中做很多事情,但有些需要“拖放”。苹果希望您尽可能多地使用Storyboards和InterfaceBuilder…所以要习惯它 :) - Martin Marconcini
与 @Galen 的答案相同。 - doozMen
显示剩余7条评论

11

使用Xamarin.iOS的解决方案。

在我的例子中,我正在进行屏幕截图,生成图像,并允许用户分享图像。 iPad上的弹出窗口位于屏幕中间左右。

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

非常感谢您 :) 使用依赖服务与Xamarin Forms一起工作 - Ricardo Romo

9

SwiftUI 版本

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

它可以工作,但是在范围内找不到'ScreenSize',因此使用UIScreen.main.bounds.height和width。 - Md Imran Choudhury
哦!我忘记了。ScreenSize是我用于调整大小的自定义类,但它非常简单。ScreenSize.width等于UIScreen.main.bounds.height。我创建了这个类以使其更清晰和更短。我会尽快重新排列答案。 - batuhankrbb

7

Swift,iOS 9/10(在UIPopoverController过时之后)

意为:使用Swift语言,在iOS 9/10系统中(在弹出窗口控制器UIPopoverController被弃用之后)。
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
Swift 3 不支持此功能。 - Yaroslav Dukal

6

如果您在单击UIBarButtonItem时显示UIActivityViewController,请使用以下代码:

activityViewController.popoverPresentationController?.barButtonItem = sender

否则,如果您使用其他控件,例如一个UIButton,请使用以下代码:
activityViewController.popoverPresentationController?.sourceView = sender
activityViewController.popoverPresentationController?.sourceRect = sender.bounds

UIPopoverPresentationController 的文档中:

var barButtonItem: UIBarButtonItem? { get set }

将一个值赋给这个属性,以将弹出窗口锚定到指定的工具栏按钮项。当呈现时,弹出窗口的箭头指向指定的项。或者,您可以使用sourceView和sourceRect属性指定弹出窗口的锚点位置。

5
在Swift中解决这个问题最好的方法是像我发现的那样操作。
    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
    }

使用@Galen的答案。针对iOS8+,不需要进行成语检查。 - doozMen

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接