UIActivityViewController si arresta in modo anomalo sugli iPad iOS 8


288

Attualmente sto testando la mia app con Xcode 6 (Beta 6). UIActivityViewController funziona perfettamente con dispositivi e simulatori iPhone ma si arresta in modo anomalo con simulatori e dispositivi iPad (iOS 8) con i seguenti registri

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

Sto usando il seguente codice per iPhone e iPad sia per iOS 7 che per 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];

Ricevo un arresto simile anche in un'altra mia app. Potete per favore guidarmi? è cambiato qualcosa con UIActivityViewController in iOS 8? Ho controllato ma non ho trovato nulla al riguardo


Risposte di seguito test per il linguaggio. Dovresti usare la risposta di @ Galen che no.
dooz,

Risposte:


462

Su iPad il controller della vista attività verrà visualizzato come popover utilizzando il nuovo UIPopoverPresentationController , è necessario specificare un punto di ancoraggio per la presentazione del popover utilizzando una delle tre proprietà seguenti:

Per specificare il punto di ancoraggio è necessario ottenere un riferimento a UIPapoverPresentationController di UIActivityController e impostare una delle proprietà come segue:

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

4
@Daljeet un modo semplice sarebbe quello di posizionare una vista trasparente 1x1 dove vuoi che appaia il punto del popover e usarlo come vista di ancoraggio.
Mason Cloud,

3
@Grazie mmccomb, ho applicato il tuo codice sopra, funziona bene su iOS 8, ma la mia app si arresta in modo anomalo su iOS 7. Ho pubblicato lo stesso problema su StackOverflow ecco il link: StackOverflow.com/questions/26034149/… aiuto è apprezzato
Daljeet,

48
@Daljeet Questo si bloccherà su iOS7 poiché UIActivityController non ha una proprietà popoverPresentationController lì. Usa questo codice per farlo funzionare su iOS8 e iOS7: if ( [activityViewController respondsToSelector:@selector(popoverPresentationController)] ) { // iOS8 activityViewController.popoverPresentationController.sourceView = _shareItem; }
bluebamboo,

4
in iOS8, sourceRect non ha funzionato per me. ho dovuto creare un souceView con quel rect, e ha funzionato bene.
Harris,

10
@bluebamboo Perché non aggiungi il tuo suggerimento come modifica alla risposta. È abbastanza facile perdere le tue informazioni importanti in questi commenti.
Ayush Goel,

204

Lo stesso problema è arrivato al mio progetto, quindi ho trovato la soluzione che per aprire l' UIActivityViewControlleriPad dobbiamo usareUIPopoverController

Ecco un codice per usarlo su iPhone e iPad sia:

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

Per rapido 4.2 / rapido 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
Per la sintassi Swift "più recente", UIPopoverArrowDirectionAny dovrebbe essere UIPopoverArrowDirection.Any e UIUserInterfaceIdiomPhone è UIUserInterfaceIdiom.Phone
EPage_Ed

1
Grazie mille, questo, combinato con i commenti di EPage_Ed, funziona su XCode 7 e SWIFT 2. L'ho votato.
Oliver Zhang,

1
UIPopoverController è obsoleto con iOS 9.
cbartel

3
UIPopOverController è obsoleto in iOS 9.
Leena,

1
Tutti dovrebbero rispondere in questo modo, poiché è più facile navigare per le risposte iOS che per quelle veloci. Grazie amico, hai reso la mia giornata.
MBH,

42

Recentemente ho riscontrato questo esatto problema (la domanda originale) in Swift 2.0, dove ha UIActivityViewControllerfunzionato bene per iPhone, ma ha provocato arresti anomali durante la simulazione di iPad.

Voglio solo aggiungere a questo thread di risposte qui che, almeno in Swift 2.0, non hai bisogno di un'istruzione if. Puoi semplicemente rendere popoverPresentationControllerfacoltativo.

In breve, la risposta accettata sembra dire che potresti avere solo un sourceView, solo un sourceRect o solo un barButtonItem, ma secondo la documentazione di Apple per UIPopoverPresentationController hai bisogno di uno dei seguenti:

  • barButtonItem
  • sourceView e sourceRect

L'esempio particolare a cui stavo lavorando è di seguito, in cui sto creando una funzione che UIViewinclude un (per sourceView e sourceRect) e String(unico oggettoIU di 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)
}

Questo codice funziona su iPhone e iPad (e anche su tvOS credo) - se il dispositivo non supporta popoverPresentationController, le due righe di codice che lo menzionano vengono sostanzialmente ignorate.

Bello che tutto ciò che devi fare per farlo funzionare per iPad è solo aggiungere due righe di codice, o solo una se stai usando un barButtonItem!


2
Questa soluzione sembra molto più pulita rispetto alle altre soluzioni che devono usare respondsToSelectoro UIUserInterfaceIdiom, inoltre, sembra adattarsi molto meglio a Swift come lingua. Anche se respondsToSelectorsembra che sia necessario per iOS7, se si utilizzano versioni più recenti di iOS questo sembra sicuramente essere la strada da percorrere.
Marcus,

18

Vedo molte persone che eseguono hardcoding su iPhone / iPad ecc. Mentre usano il codice Swift.

Questo non è necessario, devi usare le funzionalità della lingua. Il codice seguente presuppone che utilizzerai un UIBarButtonItem e funzionerà sia su iPhone che su 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)
 }

Nota come non ci sono dichiarazioni If o altre cose folli. Lo sblocco opzionale sarà nullo su iPhone, quindi la linea vc.popoverPresentationController?non farà nulla su iPhone.


come sarebbe nel contesto di questo tutorial: hackingwithswift.com/read/3/2/…
Dave Kliman,

1
il tutorial non menziona popoverPresentationController, quindi il codice andrà in crash su iPad iOS 9.x. La soluzione sarebbe quella di aggiungere vc.popoverPresentationController?.barButtonItem = navigationItem.rightBarButtonItemprima di presentare il controller di visualizzazione.
Martin Marconcini,

ciao Martin ... Avevo già una linea simile a questa, in shareTapped(): vc.popoverPresentationController?.barButtonItem = sender as? UIBarButtonItementrambi si sono comunque schiantati. Che cosa sto facendo di sbagliato? mentre sono qui, come faccio a popolare il popover con i diversi servizi di condivisione come Facebook, Twitter, ecc.?
Dave Kliman,

E qual è l'incidente? Potresti voler pubblicare la tua domanda. Verifica che sendersia davvero un UIBarButtonItem. Controlla che vc non lo sia nil. Verifica di poter presentare un ViewController ... senza guardare il crash / codice è difficile da dire. I servizi verranno popolati automaticamente se l'utente ha installato l'app (a meno che tu non abbia deciso esplicitamente di escluderne alcuni).
Martin Marconcini

1
@DaveKliman sì, Xcode è pessimo quando si tratta di IBOutlet e IBActions che sono stati rimossi / rinominati e si bloccherà all'avvio. Non c'è davvero un modo diverso di farlo su iOS, devi usare Xcode e InterfaceBuilder. Puoi fare molte cose "nel codice" ma alcuni richiedono il "trascinamento della selezione". Apple vuole che usi Storyboard e InterfaceBuilder il più possibile ... quindi abituati :)
Martin Marconcini

10

Soluzione utilizzando Xamarin.iOS.

Nel mio esempio sto facendo una cattura dello schermo, producendo un'immagine e consentendo all'utente di condividere l'immagine. Il pop-up sull'iPad è posizionato al centro dello schermo.

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

Grazie mille :) lavora con Xamarin Forms utilizzando un servizio di dipendenza
Ricardo Romo,

7

Swift, iOS 9/10 (dopo UIPopoverController obsoleto)

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 non supporta questo
Maksim Kniazev il

5

In Swift per risolvere questo problema per iPad, il modo migliore è quello di fare in questo modo che ho trovato.

    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
    }

Usa la risposta @Galen. Nessun niet per il controllo del linguaggio quando si
prende di

5

Se mostri UIActivityViewControllerquando fai clic su un UIBarButtonItemusa il seguente codice:

activityViewController.popoverPresentationController?.barButtonItem = sender

Altrimenti, se si utilizza un altro controllo, ad esempio a UIButton, utilizzare il seguente codice:

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

Dalla documentazione a UIPopoverPresentationController:

var barButtonItem: UIBarButtonItem? { get set }

Assegna un valore a questa proprietà per ancorare il popover all'elemento del pulsante della barra specificato. Quando viene presentata, la freccia del popover punta sull'elemento specificato. In alternativa, è possibile specificare la posizione di ancoraggio per il popover utilizzando le proprietà sourceView e sourceRect.


4

Correzione per 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 è stato deprecato.
LevinsonTechnologies,

1
Grazie!! Mi ha aiutato molto.
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)
}

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)

    }

2

Soluzione per Objective-C e con UIPopoverPresentationController in uso

    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

Ho provato il codice successivo e funziona:

prima inserisci un elemento pulsante a barra nel View Controller, quindi crea un IBOutlet:

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

prossimo nel file .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

Ho trovato questa soluzione Innanzitutto, il controller di visualizzazione che presenta il popover dovrebbe implementare il <UIPopoverPresentationControllerDelegate>protocollo.

Successivamente, dovrai impostare il popoverPresentationControllerdelegato di.

Aggiungi queste funzioni:

- (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

In swift 4 il seguente codice funziona in iphone e ipad. Secondo la documentazione

È tua responsabilità presentare e licenziare il controller di visualizzazione utilizzando i mezzi appropriati per il linguaggio del dispositivo specificato. Su iPad, è necessario presentare il controller di visualizzazione in un popover. Su altri dispositivi, è necessario presentarlo modalmente.

 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

Sto usando Swift 5. Ho avuto lo stesso problema di crash quando faccio clic su "Pulsante Condividi" nella mia app su iPad. Ho trovato questa soluzione. passaggio 1: aggiungere l'oggetto "view" (cercare "UIView" nella libreria degli oggetti) in Main.storyboard. passaggio 2: creare un @IBOutlet in ViewController.swift e assegnare qualsiasi nome (ad esempio: view1)

passaggio 3: aggiungi il nome sopra (es: view1) come sourceView. questa è la mia azione "Pulsante Condividi".

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


}

Sono molto nuovo a essere veloce e sono rimasto bloccato su questo per una settimana. spero che questo possa aiutare qualcuno. quindi condividendo questa soluzione.


Grazie ha funzionato!
Jnguyen22,

-1

Per Swift 2.0. Ho scoperto che funziona se stai cercando di ancorare il popover a un pulsante di condivisione su iPad. Ciò presuppone che tu abbia creato uno sbocco per il pulsante di condivisione nella barra degli strumenti.

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

Fai attenzione se stai sviluppando per iPad usando swift, funzionerà bene nel debug, ma si arresterà in modo anomalo nel rilascio. Per farlo funzionare con testFlight e AppStore, disabilitare l'ottimizzazione per swift usando -nonefor release.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.