Сбой UIActivityViewController на iPad для iOS 8

288

В настоящее время я тестирую свое приложение с Xcode 6 (бета-версия 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];

Я получаю похожий сбой одного из моих других приложений. Можете ли вы вести меня? что-нибудь изменилось с UIActivityViewController в iOS 8? Я проверил, но я ничего не нашел по этому

Бхумит Мехта
источник
Ответы ниже тест для идиомы. Вы должны использовать ответ @ Galen, который не дает.
doozMen

Ответы:

462

На iPad контроллер представления активности будет отображаться в виде всплывающего окна с использованием нового UIPopoverPresentationController , для этого необходимо указать опорную точку для представления всплывающего окна, используя одно из трех следующих свойств:

Чтобы указать точку привязки, вам необходимо получить ссылку на UIPopoverPresentationController UIActivityController и установить одно из следующих свойств:

if ( [activityViewController respondsToSelector:@selector(popoverPresentationController)] ) { 
// iOS8
 activityViewController.popoverPresentationController.sourceView =
parentView;
 }
mmccomb
источник
4
@Daljeet простой способ - разместить прозрачное представление 1x1 там, где вы хотите, чтобы точка всплывающего окна появлялась, и использовать его в качестве привязки.
Мейсон Клауд
3
@ Спасибо mmccomb, я применил ваш код выше, он отлично работает на iOS 8, но мое приложение вылетает на iOS 7. Я опубликовал ту же проблему на stackoverflow, вот ссылка: stackoverflow.com/questions/26034149/… помощь приветствуется
Далджит
48
@Daljeet Это приведет к сбою на iOS7, так как UIActivityController не имеет свойства popoverPresentationController. Используйте этот код, чтобы он работал на iOS8 и iOS7: if ( [activityViewController respondsToSelector:@selector(popoverPresentationController)] ) { // iOS8 activityViewController.popoverPresentationController.sourceView = _shareItem; }
bluebamboo
4
в iOS8 sourceRect не работал для меня. Я должен был создать souceView с этим прямоугольником, и это работало нормально.
Харрис
10
@bluebamboo Почему бы вам не добавить свое предложение в качестве редактирования ответа. Довольно легко пропустить вашу важную информацию в этих комментариях.
Аюш Гоэль
204

Та же проблема пришла в мой проект, тогда я нашел решение, чтобы открыть UIActivityViewControllerв iPad, мы должны использовать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. Я проголосовал за это.
Оливер Чжан
1
UIPopoverController устарел с iOS 9.
cbartel
3
UIPopOverController устарел в iOS 9.
Лина
1
Каждый должен ответить таким образом, так как легче ориентироваться на ответы ios, чем на swift. Спасибо, человек, которого вы сделали моим днем.
MBH
42

Недавно я столкнулся с этой проблемой (первоначальный вопрос) в Swift 2.0, где он UIActivityViewControllerотлично работал на iPhone, но вызывал сбои при моделировании iPad.

Я просто хочу добавить к этой ветке ответов, что, по крайней мере, в Swift 2.0 вам не нужно выражение if. Вы можете просто сделать popoverPresentationControllerопциональным.

Коротко говоря, принятый ответ, по-видимому, гласит, что вы можете иметь только sourceView, просто sourceRect или просто barButtonItem, но в соответствии с документацией Apple для UIPopoverPresentationController вам необходимо одно из следующего:

  • barButtonItem
  • sourceView и sourceRect

Ниже приведен конкретный пример, над которым я работал, где я создаю функцию, которая принимает UIView(для sourceView и sourceRect) и String(единственный ActivityItem объекта 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)
}

Этот код работает на iPhone и iPad (и даже на tvOS, я думаю) - если устройство не поддерживает popoverPresentationController, две строки кода, в которых он упоминается, по существу игнорируются.

Приятно, что все, что вам нужно сделать, чтобы это работало на iPad, - это просто добавить две строки кода или одну, если вы используете barButtonItem!

гален
источник
2
Это решение кажется намного чище, чем другие решения, которые приходится использовать, respondsToSelectorили UIUserInterfaceIdiom, похоже, оно гораздо лучше подходит Swift как языку. Хотя respondsToSelectorкажется, что это необходимо для iOS7, если вы используете более новые версии iOS, это определенно будет правильным решением.
Маркус
18

Я вижу, как многие люди жестко кодируют iPhone / iPad и т. Д. При использовании кода Swift.

Это не нужно, вы должны использовать языковые функции. Следующий код предполагает , что вы будете использовать 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, поэтому линия vc.popoverPresentationController?не будет ничего делать на iPhone.

Мартин Маркончини
источник
как бы это выглядело в контексте этого урока: hackingwithswift.com/read/3/2/…
Дейв Климан,
1
в учебнике не упоминается popoverPresentationController, поэтому код будет падать на iPad iOS 9.x. Решение было бы добавить, vc.popoverPresentationController?.barButtonItem = navigationItem.rightBarButtonItemпрежде чем представить контроллер представления.
Мартин Маркончини
Привет, Мартин ... У меня уже была строка, похожая на эту, в shareTapped(): vc.popoverPresentationController?.barButtonItem = sender as? UIBarButtonItemоба все еще разбились. Что я делаю не так? в то время как я здесь, как мне заполнить поповер различными службами обмена, такими как Facebook, Twitter и т. д.?
Дейв Климан
И что такое авария? Вы можете разместить свой вопрос. Проверьте, что senderэто действительно UIBarButtonItem. Проверьте, что vc нет nil. Проверьте, что вы можете представить ViewController ... не глядя на сбой / код трудно сказать. Службы будут автоматически заполняться, если у пользователя установлено приложение (если вы явно не решили исключить некоторые из них).
Мартин Маркончини
1
@DaveKliman Да, XCode очень плох, когда дело доходит до IBOutlets и IBActions, которые были удалены / переименованы, и он потерпит крах при запуске. Там действительно нет другого способа сделать это на iOS, вам нужно использовать Xcode и InterfaceBuilder. Вы можете делать много вещей «в коде», но некоторые требуют «перетаскивания». Apple хочет, чтобы вы как можно больше использовали раскадровки и InterfaceBuilder… так что
привыкните
10

Решение с использованием 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, используя службу зависимостей
Рикардо Ромо
7

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)
MPaulo
источник
3
Swift 3 не поддерживает это
Максим Князев
5

В Swift, чтобы исправить это для iPad, лучший способ сделать это, как я нашел.

    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. Нет ничего
хорошего
5

Если вы показываете, UIActivityViewControllerкогда нажимаете на UIBarButtonItemиспользование, используйте следующий код:

activityViewController.popoverPresentationController?.barButtonItem = sender

В противном случае, если вы используете другой элемент управления, например a UIButton, используйте следующий код:

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

От документации до UIPopoverPresentationController:

var barButtonItem: UIBarButtonItem? { get set }

Присвойте значение этому свойству, чтобы привязать всплывающее окно к указанному элементу панели кнопок. При отображении стрелка всплывающего окна указывает на указанный элемент. Кроме того, вы можете указать место привязки для всплывающего окна, используя свойства sourceView и sourceRect.

DronPop
источник
4

Исправление для 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
источник
2
UIPopoverController устарел.
LevinsonTechnologies
1
Спасибо!! Это мне очень помогло.
Оскар
4

Свифт 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)
}
Даниэль Маклин
источник
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)

    }
kalpeshdeo
источник
2

Решение для Objective-C и с использованием 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
источник
1

Я попробовал следующий код, и он работает:

сначала вставьте элемент панели кнопок в свой View Controller, затем создайте IBOutlet:

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

следующий в .m файле: yourUIActivityViewController.popoverPresentationController.barButtonItem = self.barButtonItem;

Майк
источник
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
источник
0

Я нашел это решение. Во-первых, ваш контроллер представления, который представляет поповер, должен реализовывать <UIPopoverPresentationControllerDelegate>протокол.

Далее вам нужно установить popoverPresentationControllerделегат.

Добавьте эти функции:

- (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;
}
Монго дб
источник
0

В swift 4 следующий код работает в iphone и ipad. По документации

Вы несете ответственность за представление и отклонение контроллера представления, используя соответствующие средства для данной идиомы устройства. На iPad вы должны представить контроллер представления во всплывающем окне. На других устройствах вы должны представить это модально.

 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)
Имран Хан
источник
0

Я использую Swift 5. У меня была та же проблема сбоя при нажатии кнопки «Поделиться» в моем приложении на iPad. Нашел это решение. Шаг 1: Добавьте объект «просмотр» (поиск «UIView» в библиотеке объектов) в Main.storyboard. Шаг 2. Создайте @IBOutlet в ViewController.swift и назначьте любое имя (например, view1).

Шаг 3: добавьте указанное имя (например, view1) в качестве sourceView. это мое действие "Кнопка" Поделиться ".

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


}

Я очень новичок в Свифте и застрял на этой неделе. надеюсь, это поможет кому-то. так что делюсь этим решением.

Dishara
источник
Спасибо, это сработало!
Jnguyen22
-1

Для Swift 2.0. Я обнаружил, что это работает, если вы пытаетесь привязать поповер к кнопке «Поделиться» на iPad. Это предполагает, что вы создали выход для кнопки «Поделиться» на панели инструментов.

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
источник
-5

Будьте осторожны, если вы разрабатываете для iPad с использованием swift, он будет хорошо работать при отладке, но в выпуске вылетит. Чтобы заставить его работать с testFlight и AppStore, отключите оптимизацию для swift, использующего -noneдля выпуска.

ingconti
источник