UIActivityViewController ulega awarii na iPadzie z systemem iOS 8

288

Obecnie testuję swoją aplikację za pomocą Xcode 6 (Beta 6). UIActivityViewController działa dobrze z urządzeniami iPhone i symulatorami, ale zawiesza się z symulatorami i urządzeniami iPad (iOS 8) z następującymi dziennikami

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

Używam następującego kodu dla iPhone'a i iPada zarówno dla iOS 7, jak i 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];

Mam podobną awarię jednej z moich innych aplikacji. Czy możesz mnie poprowadzić? czy coś się zmieniło w UIActivityViewController w iOS 8? Sprawdziłem, ale nie znalazłem nic na ten temat

Bhumit Mehta
źródło
Odpowiedzi poniżej testu dla idiomu. Powinieneś użyć odpowiedzi @ Galen, która nie.
doozMen

Odpowiedzi:

462

Na iPadzie kontroler widoku aktywności będzie wyświetlany jako popover przy użyciu nowego UIPopoverPresentationController , wymaga podania punktu kotwiczenia dla prezentacji popover przy użyciu jednej z trzech następujących właściwości:

Aby określić punkt kontrolny, musisz uzyskać odwołanie do UIPopoverPresentationController UIActivityController i ustawić jedną z właściwości w następujący sposób:

if ( [activityViewController respondsToSelector:@selector(popoverPresentationController)] ) { 
// iOS8
 activityViewController.popoverPresentationController.sourceView =
parentView;
 }
mmccomb
źródło
4
@Daljeet prostym sposobem byłoby umieszczenie przezroczystego widoku 1x1 wszędzie tam, gdzie chcesz, aby pojawił się punkt popovera i użycie go jako widoku kotwicy.
Mason Cloud
3
@ Dzięki mmccomb, zastosowałem twój kod powyżej, działa dobrze na iOS 8, ale moja aplikacja ulega awarii na iOS 7. Wysłałem ten sam problem na stackoverflow tutaj jest link: stackoverflow.com/questions/26034149/... pomoc jest doceniana
Daljeet,
48
@Daljeet To się zawiesi na iOS7, ponieważ UIActivityController nie ma tam właściwości popoverPresentationController. Użyj tego kodu, aby działał na iOS8 i iOS7: if ( [activityViewController respondsToSelector:@selector(popoverPresentationController)] ) { // iOS8 activityViewController.popoverPresentationController.sourceView = _shareItem; }
bluebamboo
4
w iOS8, sourceRect nie działało dla mnie. Musiałem stworzyć souceView z tym odbiciem i działało dobrze.
Harris,
10
@bluebamboo Dlaczego nie dodasz swojej sugestii jako edycji odpowiedzi? W tych komentarzach dość łatwo przeoczyć ważne informacje.
Ayush Goel
204

Ten sam problem pojawia się w moim projekcie, a potem znalazłem rozwiązanie, które trzeba otworzyć UIActivityViewControllerw iPadzieUIPopoverController

Oto kod do użycia zarówno na iPhonie, jak i iPadzie:

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

Dla szybkich 4.2 / szybkich 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)
}
Hardik Thakkar
źródło
5
W przypadku „nowszej” składni Swift UIPopoverArrowDirection Każdy powinien być UIPopoverArrowDirection.Any i UIUserInterfaceIdiomPhone to UIUserInterfaceIdiom.Phone
EPage_Ed
1
Wielkie dzięki, w połączeniu z komentarzami EPage_Ed, działa na XCode 7 i SWIFT 2. Poparłem to.
Oliver Zhang,
1
UIPopoverController jest przestarzały w systemie iOS 9.
cartart
3
UIPopOverController jest przestarzały w iOS 9.
Leena
1
Każdy powinien odpowiedzieć w ten sposób, ponieważ łatwiej jest nawigować w poszukiwaniu odpowiedzi na iOS niż na szybkie.
MBH
42

Dokładnie ten problem miałam ostatnio (pierwotne pytanie) w Swift 2.0, gdzie UIActivityViewControllerdziałał dobrze dla iPhone'ów, ale powodował awarie podczas symulacji iPadów.

Chcę tylko dodać do tego wątku odpowiedzi, że przynajmniej w Swift 2.0 nie potrzebujesz instrukcji if. Możesz po prostu uczynić popoverPresentationControlleropcjonalnym.

Krótko mówiąc, wydaje się, że zaakceptowana odpowiedź mówi, że możesz mieć tylko sourceView, po prostu sourceRect lub po prostu barButtonItem, ale zgodnie z dokumentacją Apple dla UIPopoverPresentationController potrzebujesz jednego z poniższych:

  • barButtonItem
  • sourceView i sourceRect

Konkretny przykład, nad którym pracowałem, znajduje się poniżej, w którym tworzę funkcję, która przyjmuje UIView(dla sourceView i sourceRect) i String(jedyny element aktywności 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)
}

Ten kod działa na iPhonie i iPadzie (a nawet tvOS) - jeśli urządzenie nie obsługuje popoverPresentationController, dwie linie kodu, które go wspominają, są zasadniczo ignorowane.

Miło, że wszystko, co musisz zrobić, aby działało na iPady, to po prostu dodać dwa wiersze kodu lub jeden, jeśli używasz barButtonItem!

Galen
źródło
2
Rozwiązanie to wydaje się o wiele czystsze niż innych rozwiązań, które mają do wykorzystania respondsToSelectorlub UIUserInterfaceIdiom, również wydaje się pasować Swift jako język o wiele lepiej. Chociaż respondsToSelectorwydaje się, że jest to konieczne dla iOS7, jeśli używasz nowszych wersji iOS, zdecydowanie wydaje się to dobrym rozwiązaniem.
Marcus
18

Widzę wielu ludzi, którzy kodują iPhone'a / iPada itp. Podczas korzystania z kodu Swift.

Nie jest to konieczne, musisz użyć funkcji językowych. Poniższy kod zakłada, że ​​użyjesz UIBarButtonItem i będzie działać zarówno na iPhonie, jak i iPadzie.

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

Zauważ, że nie ma żadnych instrukcji If ani żadnych innych szalonych rzeczy. Opcjonalne rozpakowywanie będzie zerowe na iPhonie, więc linia vc.popoverPresentationController?nie zrobi nic na iPhone'ach.

Martin Marconcini
źródło
jak by to wyglądało w kontekście tego samouczka: hackingwithswift.com/read/3/2/…
Dave Kliman
1
samouczek nie wspomina o popoverPresentationController, więc kod zawiesi się na iPadzie iOS 9.x. Rozwiązaniem byłoby dodanie vc.popoverPresentationController?.barButtonItem = navigationItem.rightBarButtonItemprzed zaprezentowaniem kontrolera widoku.
Martin Marconcini,
Cześć Martin ... Już miałem linię wyglądającą tak shareTapped(): w vc.popoverPresentationController?.barButtonItem = sender as? UIBarButtonItemobu nadal się zawiesiło. Co ja robię źle? gdy tu jestem, jak zapełnić popover różnymi usługami udostępniania, takimi jak Facebook, Twitter itp.?
Dave Kliman
A czym jest awaria? Możesz opublikować własne pytanie. Sprawdź, czy senderto rzeczywiście UIBarButtonItem. Sprawdź, czy vc nie jest nil. Sprawdź, czy możesz przedstawić ViewController… bez patrzenia na awarię / kod jest trudny do ustalenia. Usługi zostaną automatycznie wypełnione, jeśli użytkownik zainstaluje aplikację (chyba że wyraźnie postanowiłeś wykluczyć niektóre).
Martin Marconcini,
1
@DaveKliman tak, Xcode jest bardzo zły, jeśli chodzi o IBOutlety i IBAction, które zostały usunięte / zmieniono ich nazwy i zawiesi się podczas uruchamiania. Naprawdę nie ma innego sposobu na zrobienie tego na iOS, musisz użyć Xcode i InterfaceBuilder. Możesz robić wiele rzeczy „w kodzie”, ale niektóre wymagają „przeciągania i upuszczania”. Apple chce, abyś w jak największym stopniu korzystał ze Storyboardów i InterfaceBuilder… więc przyzwyczaj się do tego :)
Martin Marconcini
10

Rozwiązanie za pomocą Xamarin.iOS.

W moim przykładzie robię zrzut ekranu, tworzę obraz i pozwalam użytkownikowi go udostępnić. Wyskakujące okienko na iPadzie znajduje się mniej więcej na środku ekranu.

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);
ben
źródło
Dziękuję bardzo :) współpracuje z Xamarin Forms za pomocą usługi zależności
Ricardo Romo
7

Swift, iOS 9/10 (po przestarzałym 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
źródło
3
Swift 3 tego nie obsługuje
Maksim Kniazev,
5

W Swift, aby to naprawić na iPadzie, najlepszym sposobem jest zrobienie tego w taki sposób, jaki znalazłem.

    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
    }
Niklas
źródło
Użyj odpowiedzi @Galen. Nie ma nietrafnego sprawdzania idiomu podczas celowania w iOS8 +
doozMen
5

Jeśli wyświetlasz się UIActivityViewControllerpo kliknięciu, UIBarButtonItemużyj następującego kodu:

activityViewController.popoverPresentationController?.barButtonItem = sender

W przeciwnym razie, jeśli użyjesz innej kontrolki, na przykład a UIButton, użyj następującego kodu:

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

Od dokumentacji do UIPopoverPresentationController:

var barButtonItem: UIBarButtonItem? { get set }

Przypisz wartość do tej właściwości, aby zakotwiczyć popover do określonego elementu przycisku paska. Po przedstawieniu strzałka popover wskazuje określony element. Alternatywnie możesz określić lokalizację zakotwiczenia dla popovera za pomocą właściwości sourceView i sourceRect.

DronPop
źródło
4

Poprawka dla 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
źródło
2
UIPopoverController został uznany za przestarzały.
LevinsonTechnologies
1
Dziękuję Ci!! Bardzo mi pomogło.
Oscar
4

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)
}
Daniel McLean
źródło
3

Szybki:

    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
źródło
2

Rozwiązanie dla Objective-C i przy użyciu 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
źródło
1

Wypróbowałem następny kod i działa:

najpierw umieść element przycisku paska w kontrolerze widoku, a następnie utwórz IBOutlet:

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

dalej w pliku .m: yourUIActivityViewController.popoverPresentationController.barButtonItem = self.barButtonItem;

Mikrofon
źródło
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
źródło
0

Znalazłem to rozwiązanie Po pierwsze, twój kontroler widoku, który prezentuje popover, powinien zaimplementować <UIPopoverPresentationControllerDelegate>protokół.

Następnie musisz ustawić popoverPresentationControllerdelegata.

Dodaj te funkcje:

- (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;
}
Mongo db
źródło
0

W szybkim 4 następujący kod działa na iPhonie i iPadzie. Zgodnie z dokumentacją

Twoim obowiązkiem jest przedstawienie i zamknięcie kontrolera widoku za pomocą odpowiednich środków dla danego idiomu urządzenia. Na iPadzie musisz przedstawić kontroler widoku w okienku popover. Na innych urządzeniach musisz to przedstawić modalnie.

 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)
Imran Khan
źródło
0

Używam Swift 5. Miałem ten sam problem z awarią, kiedy kliknąłem przycisk „Udostępnij” w mojej aplikacji na iPadzie. Znalazłem to rozwiązanie. krok 1: Dodaj obiekt „widok” (wyszukaj „UIView” w bibliotece obiektów) do Main.storyboard. krok 2: Utwórz @IBOutlet w ViewController.swift i przypisz dowolną nazwę (np .: view1)

krok 3: dodaj powyższą nazwę (np .: view1) jako sourceView. to jest moja akcja „Udostępnij przycisk”.

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


}

Jestem bardzo nowy w swift i utknąłem w tym na tydzień. mam nadzieję, że to komuś pomoże. więc dzieląc się tym rozwiązaniem.

Dishara
źródło
Dzięki, działało!
Jnguyen22
-1

Dla Swift 2.0. Przekonałem się, że działa to, jeśli próbujesz zakotwiczyć popovera do przycisku udostępniania na iPadzie. Zakłada się, że utworzono ujście dla przycisku udostępniania na pasku narzędzi.

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
źródło
-5

Zachowaj ostrożność, jeśli programujesz dla iPada za pomocą szybkiego, będzie działał dobrze podczas debugowania, ale zawiesi się w wersji. Aby działał z testFlight i AppStore, wyłącz optymalizację dla szybkiego użycia -noneprzy wydaniu.

ingconti
źródło