Збій UIActivityViewController на iOS 8 iPad


288

Зараз я тестую свій додаток за допомогою 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];

Я отримую подібний збій в одному моєму іншому додатку також. Чи можете ви мене просимо? щось змінилося з UIActivityViewController в iOS 8? Я перевірив, але нічого не знайшов на цьому


Відповіді нижче тест на фразеологізм. Слід скористатися відповіддю @ Галена, яка не відповідає.
doozMen

Відповіді:


462

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

Для того, щоб вказати точку прив’язки, вам потрібно буде отримати посилання на UIActivityController на UIPopoverPresentationController UIActivityController і встановити одне з властивостей наступним чином:

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

4
@Daljeet простим способом було б розмістити прозорий вид 1х1 там, де ви хочете, щоб точка попова з'явилася, і використовувати це як вид прив’язки.
Мейсон Хмара

3
@Thanks mmccomb, я застосував свій код вище він працює відмінно на 8 прошивці, але моє додаток падає на IOS 7.I виклав таку ж проблему на 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

Така ж проблема стосується мого проекту, тоді я знайшов рішення, щоб відкрити UIActivityViewControlleriPad, який ми маємо використовувати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];
}

Для швидкого 4.2 / стрімкого 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
UOSopOverController є застарілим у iOS 9.
Leena

1
Усі повинні відповісти так, як легше орієнтуватися на відповіді на ios, ніж на стрімкий. Дякую чоловікові, що ти зробив мій день.
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(єдину активність 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, щебетати тощо?
Дейв Климан

А що таке аварія? Ви можете написати власне запитання. Перевірте, senderчи справді це UIBarButtonItem. Перевірте, чи немає vc nil. Перевірте, чи можете ви представити ViewController…, не дивлячись на збій / код, важко зрозуміти. Служби будуть автоматично заповнені, якщо користувач встановив додаток (якщо ви прямо не вирішили їх виключити).
Мартін Маркончіні

1
@DaveKliman так, Xcode дуже поганий, коли мова заходить про видалені / перейменовані IBOutlets та IBActions, які під час запуску розбиться. Насправді це не інший спосіб зробити це на iOS, вам потрібно використовувати Xcode та InterfaceBuilder. Ви можете зробити багато речей "у коді", але деякі вимагають "перетягування". Apple хоче, щоб ви максимально використовували Storyboards та 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)

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 }

Призначте цьому властивості значення, щоб прикріпити перехід до зазначеного елемента кнопки бар. При поданні стрілка Popover вказує на вказаний елемент. Крім того, ви можете вказати місце прив’язки для переїзду, використовуючи властивості sourceView та sourceRect.


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

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

Швидкий:

    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)

    }

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

1

Я спробував наступний код, і він працює:

спочатку помістіть елемент панелі кнопки у свій контролер перегляду, а потім створіть 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)

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

У швидкому 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: Додайте об’єкт "view" (пошук "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)


}

Я дуже новачок швидко і затримався на цьому протягом тижня. сподіваюся, що це комусь допоможе. тому поділяємо це рішення.


Спасибі це спрацювало!
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)
        }

    }
}

-5

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.