web-dev-qa-db-fra.com

openURL ne fonctionne pas dans Action Extension

J'ajoute le code suivant:

- (IBAction)done {
    // Return any edited content to the Host app.
    // This template doesn't do anything, so we just echo the passed in items.

    NSURL *url = [NSURL URLWithString:@"lister://today"];
    [self.extensionContext openURL:url completionHandler:^(BOOL success) {
        NSLog(@"fun=%s after completion. success=%d", __func__, success);
    }];
    [self.extensionContext completeRequestReturningItems:self.extensionContext.inputItems completionHandler:nil];

}

après avoir créé la cible Action Extension. Mais ça ne peut pas marcher.

Mon objectif est le suivant: lorsque l'utilisateur visualise une photo dans Photos.app (le fichier Photos.app par défaut d'iOS ou galerie appelée), et qu'il clique sur le bouton de partage pour lancer notre vue d'extension ..__, nous pouvons transférer l'image depuis Photos.app à ma propre application et traiter ou télécharger l'image dans mon application.

J'essaie aussi "CFBundleDocumentTypes" mais cela ne peut pas fonctionner non plus.

Toute aide serait appréciée.

41
Laurence Fan

C'est par conception. Nous ne voulons pas que les actions personnalisées deviennent des lanceurs d'applications.

20
Ian Baird

C'est ce que je faisais avant:

UIWebView * webView = [[UIWebView alloc] initWithFrame:CGRectMake(0, 0, 0, 0)];
NSString *urlString = @"https://iTunes.Apple.com/us/app/watuu/id304697459";
NSString * content = [NSString stringWithFormat : @"<head><meta http-equiv='refresh' content='0; URL=%@'></head>", urlString];
[webView loadHTMLString:content baseURL:nil];
[self.view addSubview:webView];
[webView performSelector:@selector(removeFromSuperview) withObject:nil afterDelay:2.0];

Veuillez noter que dans ce cas, j'instancie cet appel à partir de UIInputViewController.

Cette méthode devrait également fonctionner avec le schéma d'URL de l'application contenant

UPDATE 17/04/2015: Cela ne fonctionne pas avec iOS 8.3. Nous recherchons une solution et nous mettrons à jour la réponse prochainement

MISE À JOUR 06/01/2015: Nous avons trouvé une solution fonctionnant sous iOS 8.3

var responder = self as UIResponder?

while (responder != nil){
    if responder!.respondsToSelector(Selector("openURL:")) == true{
        responder!.callSelector(Selector("openURL:"), object: url, delay: 0)
    }
    responder = responder!.nextResponder()
}

Ceci trouvera un répondeur approprié auquel envoyer l'openURL.

Vous devez ajouter cette extension qui remplace performSelector for Swift et facilite la construction du mécanisme:

extension NSObject {
    func callSelector(selector: Selector, object: AnyObject?, delay: NSTimeInterval) {
        let delay = delay * Double(NSEC_PER_SEC)
        let time = dispatch_time(DISPATCH_TIME_NOW, Int64(delay))

        dispatch_after(time, dispatch_get_main_queue(), {
            NSThread.detachNewThreadSelector(selector, toTarget:self, withObject: object)
        })
    }
}

MISE À JOUR 06/15/2015: Objective-C

Quelqu'un a demandé le code dans Objective-C, alors le voici. Je ne vais pas le lancer car je n'ai pas le temps pour l'instant, mais cela devrait être assez simple:

UIResponder *responder = self;
while(responder){
    if ([responder respondsToSelector: @selector(OpenURL:)]){
        [responder performSelector: @selector(OpenURL:) withObject: [NSURL URLWithString:@"www.google.com" ]];
    }
    responder = [responder nextResponder];
}

Comme mentionné, je n'ai pas exécuté ce code Objective-C, il s'agit simplement d'une conversion du code Swift. S'il vous plaît laissez-moi savoir si vous rencontrez des problèmes et la solution et je vais le mettre à jour. De nos jours, je n'utilise que Swift et malheureusement, mon cerveau déprécie Objective-C

UPDATE 05/02/2016: Fonctions obsolètes

Comme indiqué par @KyleKIM, les fonctions de sélecteur ont été remplacées dans Swift 2.2 par #selector. En outre, il existe une fonction obsolète qui sera probablement supprimée dans Swift 3.0. Je suis donc en train de faire des recherches pour trouver une alternative.

MISE À JOUR 16/09/2016: XCode 8, Swift 3.0 et iOS10 Le code suivant fonctionne toujours sur les versions mentionnées. Vous obtiendrez des avertissements:

let url = NSURL(string:urlString)
let context = NSExtensionContext()
context.open(url! as URL, completionHandler: nil)

var responder = self as UIResponder?

while (responder != nil){
    if responder?.responds(to: Selector("openURL:")) == true{
        responder?.perform(Selector("openURL:"), with: url)
    }
    responder = responder!.next
}

MISE À JOUR DU 15/06/2017: XCode 8.3.3

let url = NSURL(string: urlString)
let selectorOpenURL = sel_registerName("openURL:")
let context = NSExtensionContext()
context.open(url! as URL, completionHandler: nil)

var responder = self as UIResponder?

while (responder != nil){
    if responder?.responds(to: selectorOpenURL) == true{
        responder?.perform(selectorOpenURL, with: url)
    }
    responder = responder!.next
}
40
Julio Bailon

Essayez ce code.

    UIResponder* responder = self;
    while ((responder = [responder nextResponder]) != nil)
    {
        NSLog(@"responder = %@", responder);
        if([responder respondsToSelector:@selector(openURL:)] == YES)
        {
            [responder performSelector:@selector(openURL:) withObject:[NSURL URLWithString:urlString]];
        }
    }
25
DongHyun Jang

Apple a accepté la solution suivante, qui est le "même" code qu'une application hôte utiliserait. Il fonctionne sur toutes les versions iOS 8 à ce jour (testé sur iOS 8.0 - iOS 8.3).

NSURL *destinationURL = [NSURL URLWithString:@"myapp://"];

// Get "UIApplication" class name through ASCII Character codes.
NSString *className = [[NSString alloc] initWithData:[NSData dataWithBytes:(unsigned char []){0x55, 0x49, 0x41, 0x70, 0x70, 0x6C, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6F, 0x6E} length:13] encoding:NSASCIIStringEncoding];
if (NSClassFromString(className)) {
    id object = [NSClassFromString(className) performSelector:@selector(sharedApplication)];
    [object performSelector:@selector(openURL:) withObject:destinationURL];
}
15
Hank Brekke

Solution travaillée dans Swift 3.0 & 4.0:

// For skip compile error. 
func openURL(_ url: URL) {
    return
}

func openContainerApp() {
    var responder: UIResponder? = self as UIResponder
    let selector = #selector(openURL(_:))
    while responder != nil {
        if responder!.responds(to: selector) && responder != self {
            responder!.perform(selector, with: URL(string: "containerapp://")!)
            return
        }
        responder = responder?.next
    }
}

Explication:

En extension, api est limité par le compilateur pour ne pas vous laisser utiliser openURl (: URL) comme dans une application conteneur. Cependant, l'API est toujours là.

Et nous ne pouvons pas exécuter de méthode dans notre classe tant que nous ne l'avons pas déclarée. Ce que nous voulons vraiment, c'est laisser UIApplication exécuter cette méthode.

Rappel à la chaîne de réponse, nous pouvons utiliser

    var responder: UIResponder? = self as UIResponder
    responder = responder?.next

faire une boucle sur un objet UIApplication.

Et mes applications utilisant cette méthode passent le processus de révision, alors ne vous inquiétez pas de l’utiliser.

12
Alen Liang

Solution de travail (testée sur iOS 9.2) for Keyboard Extension. Cette catégorie ajoute une méthode spéciale d'accès à l'objet caché sharedApplication et appelle ensuite openURL: dessus . (Vous devez bien entendu utiliser la méthode openURL: avec votre modèle d'application.)

extension UIInputViewController {

    func openURL(url: NSURL) -> Bool {
        do {
            let application = try self.sharedApplication()
            return application.performSelector("openURL:", withObject: url) != nil
        }
        catch {
            return false
        }
    }

    func sharedApplication() throws -> UIApplication {
        var responder: UIResponder? = self
        while responder != nil {
            if let application = responder as? UIApplication {
                return application
            }

            responder = responder?.nextResponder()
        }

        throw NSError(domain: "UIInputViewController+sharedApplication.Swift", code: 1, userInfo: nil)
    }

}
8
Valentin Shergin

NSExtensionContext ne prend en charge que la fonction openURL dans l’extension Today, elle est décrite dans les documents d’Apple sur NSExtensionContext.Le mot original est "Chaque point d’extension détermine si cette méthode est prise en charge ou dans quelles conditions. Point supporte cette méthode. "

7
fiona zhou

Cela semble être un bogue, car les docs disent:

Ouverture de l'application contenant

Dans certains cas, il peut être judicieux pour une extension de demander son contenant l'application à ouvrir. Par exemple, le widget Calendrier sous OS X s'ouvre Calendrier lorsque les utilisateurs cliquent sur un événement. Pour vous assurer que votre application contenant s’ouvre d’une manière qui a du sens dans le contexte du courant de l’utilisateur Pour ce faire, vous devez définir un schéma d’URL personnalisé qui associe l’application et son les extensions peuvent utiliser.

Une extension ne dit pas directement à l’application qui la contient de s’ouvrir; au lieu de cela, il utilise la méthode openURL: completionHandler: de NSExtensionContext pour indiquer au système d'ouvrir l'application qui le contient. Quand une extension utilise cette méthode pour ouvrir une URL, le système valide le demande avant de l'accomplir.

Je l'ai signalé aujourd'hui: http://openradar.appspot.com/17376354 Vous devriez le duper, si vous avez un peu de temps libre.

7
Arek Holko

Solution possible: Créez et ajoutez un petit UIWebView à votre vue et exécutez sa méthode loadRequest avec le schéma d'URL que vous avez défini ci-dessusBonne chance!

6
nurxyz

Solution pour le dernier SDK iOS 10.2. Toutes les solutions précédentes utilisent une API obsolète. Cette solution est basée sur la recherche de UIApplication UIResponder de l’application d’hébergement (cette application qui crée un contexte d’exécution pour notre extension). La solution ne peut être fournie qu'en Objective-C car il existe une méthode à 3 arguments à invoquer et il est impossible de le faire avec les méthodes performSelector:. Pour appeler cette méthode non dépréciée openURL:options:completionHandler:, nous devons utiliser l'instance NSInvocation qui n'est pas disponible dans Swift. La solution fournie peut être appelée à partir d’Objective-C et de Swift (n’importe quelle version). Je dois dire que je ne sais pas encore si la solution fournie sera valable pour le processus de révision Apple.

UIViewController+OpenURL.h

#import <UIKit/UIKit.h>
@interface UIViewController (OpenURL)
- (void)openURL:(nonnull NSURL *)url;
@end

UIViewController+OpenURL.m

#import "UIViewController+OpenURL.h"

@implementation UIViewController (OpenURL)

- (void)openURL:(nonnull NSURL *)url {

    SEL selector = NSSelectorFromString(@"openURL:options:completionHandler:");

    UIResponder* responder = self;
    while ((responder = [responder nextResponder]) != nil) {
        NSLog(@"responder = %@", responder);
        if([responder respondsToSelector:selector] == true) {
            NSMethodSignature *methodSignature = [responder methodSignatureForSelector:selector];
            NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature];

            // Arguments
            NSDictionary<NSString *, id> *options = [NSDictionary dictionary];
            void (^completion)(BOOL success) = ^void(BOOL success) {
                NSLog(@"Completions block: %i", success);
            };

            [invocation setTarget: responder];
            [invocation setSelector: selector];
            [invocation setArgument: &url atIndex: 2];
            [invocation setArgument: &options atIndex:3];
            [invocation setArgument: &completion atIndex: 4];
            [invocation invoke];
            break;
        }
    }
}

@end

De Swift 3 Vous ne pouvez exécuter ceci que si votre contrôleur de vue est dans la hiérarchie de vues. Voici le code tel que je l’utilise:

override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        let context = self.extensionContext!
        let userAuthenticated = self.isUserAuthenticated()

        if !userAuthenticated {
            let alert = UIAlertController(title: "Error", message: "User not logged in", preferredStyle: .alert)
            let cancel = UIAlertAction(title: "Cancel", style: .cancel) { _ in
                context.completeRequest(returningItems: nil, completionHandler: nil)
            }
            let login = UIAlertAction(title: "Log In", style: .default, handler: { _ in
                //self.openContainingAppForAuthorization()
                let url = URL(string: "fashionapp://login")!
                self.open(url)
                context.completeRequest(returningItems: nil, completionHandler: nil)
            })

            alert.addAction(cancel)
            alert.addAction(login)
            present(alert, animated: true, completion: nil)
        }
    }
3
Marcin Kapusta

Une version mise à jour de la réponse de Julio Bailon avec la syntaxe moderne Swift:

let url = NSURL(string: "scheme://")!
var responder: UIResponder? = self
while let r = responder {
    if r.respondsToSelector("openURL:") {
        r.performSelector("openURL:", withObject: url)
        break
    }
    responder = r.nextResponder()
}

Il n’est pas nécessaire de faire une extension pour NSObject maintenant. 

Remarque: vous devez attendre que la vue soit attachée à la hiérarchie des vues avant d'appeler ce code, sinon la chaîne de répondeur ne peut pas être utilisée.

3
Pulsar

Le code suivant fonctionne sur Xcode 8.3.3, iOS10, Swift3 et Xcode 9, iOS11, Swift4 sans aucun avertissement du compilateur:

func openUrl(url: URL?) {
    let selector = sel_registerName("openURL:")
    var responder = self as UIResponder?
    while let r = responder, !r.responds(to: selector) {
        responder = r.next
    }
    _ = responder?.perform(selector, with: url)
}

func canOpenUrl(url: URL?) -> Bool {
    let selector = sel_registerName("canOpenURL:")
    var responder = self as UIResponder?
    while let r = responder, !r.responds(to: selector) {
        responder = r.next
    }
    return (responder!.perform(selector, with: url) != nil)
}

Assurez-vous que votre application prend en charge les liens universels, sinon le lien sera ouvert dans le navigateur. Plus d'infos ici: https://developer.Apple.com/library/content/documentation/General/Conceptual/AppSearch/UniversalLinks.html

2
Deniss Fedotovs

Seule l'extension Today semble fonctionner.
Ce n’est pas documenté à 100%, mais un employé d’Apple a spécifiquement indiqué que les extensions de clavier ne prenaient pas en charge openURL: completionHandler.
La documentation dit:

Chaque point d'extension détermine s'il faut prendre en charge cette méthode ou dans quelles conditions prendre en charge cette méthode.

Ainsi, dans la pratique, Partage, Action, Clavier et le fournisseur de documents fonctionnent non pour tout le monde (version bêta 5) et seul Today Extension les prend en charge.

0
n8tr

Je suppose que c'est intentionnellement impossible. Le bloc openURL:completionHandler: indique qu'il peut ne pas être pris en charge par tous les types d'extension et les documents d'extension d'action indiquent explicitement:

Si vous souhaitez aider les utilisateurs à partager du contenu sur un site Web social ou les mettre à jour sur les informations qui les intéressent, le point d'extension Action n'est pas le bon choix.

Je pense qu'une extension de partage serait peut-être plus appropriée, mais les documents pour les deux types suggèrent que l'expérience devrait être intégrée à l'application hôte, sans amener les utilisateurs vers votre application, de sorte que cela ne le permet peut-être pas non plus. Alors, suivez peut-être les documents d'extension de partage et téléchargez simplement votre image à partir de l'interface utilisateur d'extension, comme cela est suggéré.

0
Jesse Rusak

Tous les types d'extension d'application ne prennent pas en charge "extensionContext openURL". 

J'ai testé sur iOS 8 bêta 4 et j'ai trouvé que l'extension Today le supportait, mais pas l'extension clavier.

0
Vince Yuan

En tant que document Apple .__ "Un widget Today (et aucun autre type d'extension d'application) peut demander au système d'ouvrir l'application qu'il contient en appelant la méthode openURL: completionHandler: de la classe NSExtensionContext."

Pour autre extension, j'ai utilisé cette solution 

UIWebView * webView = [[UIWebView alloc] initWithFrame:CGRectMake(0, 0, 0, 0)];
NSString *urlString = @"ownApp://"; // Use other application url schema.

NSString * content = [NSString stringWithFormat : @"<head><meta http-equiv='refresh' content='0; URL=%@'></head>", urlString];
[webView loadHTMLString:content baseURL:nil];
[self.view addSubview:webView];
[webView performSelector:@selector(removeFromSuperview) withObject:nil afterDelay:1.0];
0
Anand Mishra