UIActivityViewController se bloquea en iPads con iOS 8

288

Actualmente estoy probando mi aplicación con Xcode 6 (Beta 6). UIActivityViewController funciona bien con dispositivos y simuladores de iPhone, pero se bloquea con simuladores y dispositivos de iPad (iOS 8) con los siguientes registros

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

Estoy usando el siguiente código para iPhone y iPad tanto para iOS 7 como para 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];

Estoy teniendo un bloqueo similar en una de mis otras aplicaciones también. ¿Puedes por favor guiarme? ¿Ha cambiado algo con UIActivityViewController en iOS 8? Lo comprobé pero no encontré nada sobre esto

Bhumit Mehta
fuente
Las respuestas a continuación prueban el idioma. Deberías usar la respuesta de @ Galen que no.
doozMen

Respuestas:

462

En iPad, el controlador de vista de actividad se mostrará como un popover usando el nuevo UIPopoverPresentationController , requiere que especifique un punto de anclaje para la presentación del popover usando una de las tres propiedades siguientes:

Para especificar el punto de anclaje, deberá obtener una referencia al UIPopoverPresentationController de UIActivityController y establecer una de las propiedades de la siguiente manera:

if ( [activityViewController respondsToSelector:@selector(popoverPresentationController)] ) { 
// iOS8
 activityViewController.popoverPresentationController.sourceView =
parentView;
 }
mmccomb
fuente
44
@Daljeet una manera simple sería colocar una vista transparente 1x1 donde quiera que aparezca el punto del popover y usarla como la vista de anclaje.
Mason Cloud
3
@ Gracias mmccomb, he aplicado su código anterior, funciona bien en iOS 8, pero mi aplicación se bloquea en iOS 7.He publicado un mismo problema en stackoverflow aquí está el enlace: stackoverflow.com/questions/26034149/… se agradece la ayuda
Daljeet
48
@Daljeet Esto se bloqueará en iOS7 ya que UIActivityController no tiene una propiedad popoverPresentationController allí. Use este código para que funcione en iOS8 e iOS7: if ( [activityViewController respondsToSelector:@selector(popoverPresentationController)] ) { // iOS8 activityViewController.popoverPresentationController.sourceView = _shareItem; }
bluebamboo
44
en iOS8, sourceRect no funcionó para mí. Tuve que crear un souceView con ese rect, y funcionó bien.
Harris
10
@bluebamboo ¿Por qué no agrega su sugerencia como una edición a la respuesta? Es bastante fácil perder su información importante en estos comentarios.
Ayush Goel
204

El mismo problema vino a mi proyecto, entonces encontré la solución para abrir el UIActivityViewController en el iPad tenemos que usarUIPopoverController

Aquí hay un código para usarlo tanto en iPhone como en 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];
}

Para 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)
}
Hardik Thakkar
fuente
55
Para la sintaxis Swift "más nueva", UIPopoverArrowDirectionAny debería ser UIPopoverArrowDirection.Any y UIUserInterfaceIdiomPhone es UIUserInterfaceIdiom.Phone
EPage_Ed
1
Muchas gracias, esto, combinado con los comentarios de EPage_Ed, funciona en XCode 7 y SWIFT 2. Lo he votado.
Oliver Zhang el
1
UIPopoverController está en desuso con iOS 9.
cbartel
3
UIPopOverController está en desuso en iOS 9.
Leena
1
Todo el mundo debería responder de esta manera, ya que es más fácil navegar por respuestas de iOS que por rápidas. Gracias, hombre, me alegraste el día.
MBH
42

Me encontré con este problema exacto recientemente (la pregunta original) en Swift 2.0, donde UIActivityViewController funcionó bien para iPhones, pero causó fallas al simular iPads.

Solo quiero agregar a este hilo de respuestas aquí que, al menos en Swift 2.0, no necesita una declaración if. Puedes hacer elpopoverPresentationController opcional.

Como comentario rápido, la respuesta aceptada parece estar diciendo que podría tener solo sourceView, solo sourceRect o solo barButtonItem, pero de acuerdo con la documentación de Apple para UIPopoverPresentationController necesita uno de los siguientes:

  • barButtonItem
  • sourceView y sourceRect

El ejemplo particular en el que estaba trabajando está abajo, donde estoy creando una función que toma un UIView(para sourceView y sourceRect) y String(el único elemento de actividad 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)
}

Este código funciona en iPhone y iPad (e incluso en tvOS, creo), si el dispositivo no es compatible popoverPresentationController , las dos líneas de código que lo mencionan se ignoran esencialmente.

¡Qué bueno que todo lo que necesita hacer para que funcione para iPads es simplemente agregar dos líneas de código, o solo una si está usando un elemento BarButtonItem!

Galen
fuente
2
Esta solución parece mucho más limpia que las otras soluciones que tienen que usar respondsToSelectoro UIUserInterfaceIdiom, además, parece adaptarse mejor a Swift como lenguaje. Aunque respondsToSelectorparece que es necesario para iOS7, si usa versiones más nuevas de iOS, definitivamente este es el camino a seguir.
Marcus
18

Veo a muchas personas codificando iPhone / iPad, etc., mientras usan el código Swift.

Esto no es necesario, debe usar las funciones del idioma. El siguiente código asume que usará un UIBarButtonItem y funcionará tanto en iPhone como en 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)
 }

Observe cómo no hay declaraciones If o cualquier otra cosa loca. El desenvolvimiento opcional será nulo en iPhone, por lo que la línea vc.popoverPresentationController?no hará nada en iPhone.

Martin Marconcini
fuente
¿cuál sería el aspecto en el contexto de este tutorial: hackingwithswift.com/read/3/2/...
David Kliman
1
el tutorial no menciona popoverPresentationController, por lo que el código se bloqueará en iPad iOS 9.x. La solución sería agregar vc.popoverPresentationController?.barButtonItem = navigationItem.rightBarButtonItemantes de presentar el controlador de vista.
Martin Marconcini
hola Martin ... Ya tenía una línea que se veía así, en shareTapped(): vc.popoverPresentationController?.barButtonItem = sender as? UIBarButtonItemambos todavía se estrelló. ¿Qué estoy haciendo mal? Mientras estoy aquí, ¿cómo puedo llenar el popover con los diferentes servicios compartidos como Facebook, Twitter, etc.?
Dave Kliman
¿Y cuál es el accidente? Es posible que desee publicar su propia pregunta. Comprueba que senderefectivamente es a UIBarButtonItem. Verifique que vc no lo sea nil. Compruebe que puede presentar un ViewController ... sin mirar el bloqueo / código es difícil de decir. Los servicios se completarán automáticamente si el usuario tiene instalada la aplicación (a menos que haya decidido explícitamente excluir algunos).
Martin Marconcini
1
@DaveKliman sí, Xcode es muy malo cuando se trata de IBOutlets e IBActions que se han eliminado / renombrado y se bloqueará al inicio. Realmente no hay una forma diferente de hacerlo en iOS, debe usar Xcode e InterfaceBuilder. Puede hacer muchas cosas "en código" pero algunas requieren "arrastrar y soltar". Apple quiere que uses Storyboards e InterfaceBuilder tanto como sea posible ... así que
acostúmbrate
10

Solución usando Xamarin.iOS.

En mi ejemplo, estoy haciendo una captura de pantalla, produciendo una imagen y permitiendo que el usuario comparta la imagen. La ventana emergente en el iPad se coloca en el medio de la pantalla.

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
fuente
Muchas gracias :) trabaja con Xamarin Forms utilizando un servicio de dependencia
Ricardo Romo
7

Swift, iOS 9/10 (después de que UIPopoverController en desuso)

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
fuente
3
Swift 3 no es compatible con esto
Maksim Kniazev
5

En Swift para arreglar esto para iPad, la mejor manera es hacer lo que encontré.

    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
fuente
Usa la respuesta @Galen. No hay tranquilidad para la verificación de idioma cuando se dirige a iOS8 +
doozMen
5

Si muestra UIActivityViewControllercuando hace clic en un, UIBarButtonItemuse el siguiente código:

activityViewController.popoverPresentationController?.barButtonItem = sender

De lo contrario, si usa otro control, por ejemplo a UIButton, use el siguiente código:

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

De la documentación a UIPopoverPresentationController:

var barButtonItem: UIBarButtonItem? { get set }

Asigne un valor a esta propiedad para anclar el popover al elemento del botón de barra especificado. Cuando se presenta, la flecha del popover apunta al elemento especificado. Alternativamente, puede especificar la ubicación del ancla para el popover usando las propiedades sourceView y sourceRect.

DronPop
fuente
4

Arreglo para 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
fuente
2
UIPopoverController ha quedado en desuso.
LevinsonTechnologies
1
¡¡Gracias!! Me ayudó mucho.
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
fuente
3

Rápido:

    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
fuente
2

Solución para Objective-C y con uso 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
fuente
1

Intenté el siguiente código y funciona:

primero coloque un elemento de botón de barra en su controlador de vista y luego cree un IBOutlet:

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

siguiente en el archivo .m: yourUIActivityViewController.popoverPresentationController.barButtonItem = self.barButtonItem;

Miguel
fuente
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
fuente
0

Encontré esta solución En primer lugar, su controlador de vista que presenta el popover debe implementar el <UIPopoverPresentationControllerDelegate> protocolo.

A continuación, deberá configurar el popoverPresentationController delegado de.

Agregue estas funciones:

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

En swift 4 el siguiente código funciona en iphone y ipad. De acuerdo con la documentación

Es su responsabilidad presentar y descartar el controlador de vista utilizando los medios apropiados para el idioma del dispositivo dado. En iPad, debe presentar el controlador de vista en un popover. En otros dispositivos, debe 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)
Imran Khan
fuente
0

Estoy usando Swift 5. Tuve el mismo problema de bloqueo cuando hice clic en "Compartir botón" en mi aplicación en iPad. Encontré una esta solución. paso 1: Agregue el objeto "ver" (busque "UIView" en la biblioteca de objetos) al Main.storyboard. Paso 2: Cree un @IBOutlet en ViewController.swift y asigne cualquier nombre (por ejemplo: view1)

Paso 3: agrega el nombre anterior (por ejemplo: view1) como sourceView. Esta es mi acción de "botón Compartir".

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


}

Soy muy nuevo en Swift y estuve atrapado en esto durante una semana. Espero que esto ayude a alguien. entonces compartiendo esta solución.

Dishara
fuente
Gracias esto funcionó!
Jnguyen22
-1

Para Swift 2.0. Descubrí que esto funciona si estás tratando de anclar el popover a un botón de compartir en iPad. Esto supone que ha creado una salida para el botón compartir en su barra de herramientas.

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
fuente
-5

Tenga cuidado si está desarrollando para iPad usando swift, funcionará bien en la depuración, pero se bloqueará en el lanzamiento. Para que funcione con testFlight y AppStore, desactive la optimización para Swift usando -nonepara el lanzamiento.

ingconti
fuente