Подтвердить что ты не робот

OpenURL не работает в Action Extension

Я добавляю следующий код:

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

}

после создания цели расширения действия. Но он не может работать.

Моя цель в том, что: когда пользователь просматривает фотографию в Photos.app(фотошаблон iOS default.app или называется галереей), и он щелкает кнопкой общего доступа, чтобы запустить наш расширенный вид. Мы можем перенести изображение из Photos.app в мое собственное приложение и обработать или загрузить изображение в моем приложении.

Я также пытаюсь "CFBundleDocumentTypes", но он также не может работать.

Любая помощь будет оценена.

4b9b3361

Ответ 1

Это по дизайну. Мы не хотим, чтобы пользовательские действия становились приложением для запуска приложений.

Ответ 2

Это то, что я использовал:

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

Обратите внимание, что в этом случае я запускаю этот вызов из UIInputViewController.

Этот метод также должен работать с использованием схемы URL из содержащего приложения

ОБНОВЛЕНИЕ 04/17/2015: Это не работает с iOS 8.3. Мы ищем решение, и мы скоро обновим ответ

ОБНОВЛЕНИЕ 06/01/2015: Мы нашли решение, которое работает в 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()
}

Это найдет подходящего ответчика для отправки openURL.

Вам нужно добавить это расширение, которое заменяет performSelector для быстрого и помогает в построении механизма:

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

ОБНОВЛЕНИЕ 15.06.2012: Objective-C

Кто-то попросил код в Objective-C, так вот он. Я не собираюсь запускать его, поскольку у меня нет времени прямо сейчас, но это должно быть довольно просто:

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

Как уже упоминалось, я не запускал этот код Objective-C, это просто преобразование из кода Swift. Пожалуйста, дайте мне знать, если у вас возникнут проблемы и решение, и я обновлю его. В настоящее время я просто использую быстрый и, к сожалению, мой мозг обесценивается Objective-C

ОБНОВЛЕНИЕ 05/02/2016: Устаревшие функции

Как указано @KyleKIM, функции Selector были заменены в Swift 2.2 на #selector. Кроме того, существует функция, которая устарела и, вероятно, будет удалена в Swift 3.0, поэтому я делаю некоторые исследования, чтобы найти альтернативу.

ОБНОВЛЕНИЕ 09/16/2016: XCode 8, Swift 3.0 и iOS10 Следующий код по-прежнему работает над упомянутыми версиями. Вы получите предупреждения:

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
}

ОБНОВЛЕНИЕ 6/15/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
}

Ответ 3

Попробуйте этот код.

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

Ответ 4

Apple приняла следующее решение, которое является "тем же" кодом, который будет использоваться хост-приложением. Он работает во всех версиях iOS 8 на сегодняшний день (проверен на 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];
}

Ответ 5

Рабочее решение в 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
    }
}

Объяснение:

В расширении api ограничен компилятором, чтобы вы не использовали openURl (: URL), как в контейнерном приложении. Однако api все еще здесь.

И мы не можем выполнять метод в нашем классе, пока не объявим его, что нам действительно нужно, чтобы позволить UIApplication выполнить этот метод.

Вспомните цепочку ответчиков, мы можем использовать

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

для перехода к объекту UIApplication.

И мои приложения с этим методом проходят процесс обзора, поэтому не беспокойтесь, чтобы использовать его.

Ответ 6

Кажется, это ошибка, потому что docs говорят:

Открытие сопутствующего приложения

В некоторых случаях может потребоваться расширение для запроса приложение для открытия. Например, открывается виджет Календарь в OS X Календарь, когда пользователи нажимают на событие. Чтобы убедиться, что ваше приложение открывается таким образом, что имеет смысл в контексте текущих пользователей задача, вам нужно определить настраиваемую схему URL-адресов, в которой и приложение, и его расширения могут использовать.

Расширение напрямую не сообщает об открытии приложения, содержащего его; вместо этого он использует метод openURL: completeHandler: NSExtensionContext, чтобы сообщить системе открыть свое содержащее приложение. когда расширение использует этот метод для открытия URL-адреса, система проверяет запрос перед его выполнением.

Я сообщил об этом сегодня: http://openradar.appspot.com/17376354 Вы должны обмануть его, если у вас есть свободное время.

Ответ 7

Рабочее решение (протестировано на iOS 9.2) для расширения клавиатуры. Эта категория добавляет специальный метод для доступа к скрытому объекту sharedApplication, а затем вызывает на нем openURL:. (Конечно, тогда вы должны использовать метод openURL: с вашей схемой приложений.)

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

NSExtensionContext поддерживает только функцию openURL в сегодняшнем расширении, это описано в документах Apple о NSExtensionContext. Исходные слова: "Каждая точка расширения определяет, следует ли поддерживать этот метод или в каких условиях поддерживать этот метод. В iOS 8.0 только Точка расширения Today поддерживает этот метод.

Ответ 9

Возможное обходное решение: Создайте и добавьте небольшой UIWebView в свое представление и запустите его метод loadRequest с установленной вами схемой URL. Это обходной путь, и я не уверен, что Apple скажет об этом. Удачи!

Ответ 10

Обновленная версия ответа Хулио Байлона с современным синтаксисом 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()
}

Теперь нет необходимости в расширении для NSObject.

Примечание: вы должны дождаться присоединения представления к иерархии представлений перед вызовом этого кода, иначе цепочка ответчиков не может быть использована.

Ответ 11

Решение для последней версии IOS SDK 10.2. Все предыдущие решения используют устаревшие api. Это решение основано на поиске URIsponder UIApplication хостинг-приложения (это приложение, которое создает контекст выполнения для нашего расширения). Решение может быть предоставлено только в Objective-C, потому что для вызова используется метод из 3 аргументов, и это невозможно сделать с помощью методов performSelector:. Чтобы вызвать этот не устаревший метод openURL:options:completionHandler:, нам нужно использовать экземпляр NSInvocation, который недоступен в Swift. Предоставляемое решение можно вызвать из Objective-C и Swift (любая версия). Мне нужно сказать, что я еще не знаю, если предоставленное решение будет действительным для процесса просмотра 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

Из Swift 3 Вы можете выполнить это, только если ваш контроллер просмотра находится в иерархии вида. Это код, как я его использую:

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

Ответ 12

Следующий код работает на Xcode 8.3.3, iOS10, Swift3 и Xcode 9, iOS11, Swift4 без каких-либо предупреждений компилятора:

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

Убедитесь, что ваше приложение поддерживает Universal Links, иначе он откроет ссылку в браузере. Подробнее здесь: https://developer.apple.com/library/content/documentation/General/Conceptual/AppSearch/UniversalLinks.html

Ответ 13

Я предполагаю, что это намеренно невозможно. Блок openURL:completionHandler: говорит, что он может не поддерживаться во всех типах расширений, а в документах расширения действий явно говорится:

Если вы хотите помочь пользователям обмениваться контентом на социальном веб-сайте или предоставлять пользователям обновления информации, о которых они заботятся, точка расширения Action не является правильным выбором.

Я думаю, что расширение общего доступа может быть более уместным, но документы для обоих типов предполагают, что опыт должен быть встроен в хост-приложение, а не принимать пользователей в ваше приложение, поэтому он может и не позволить это для этого. Итак, возможно, следуйте за документами расширения домена и просто загружайте свое изображение из внутреннего интерфейса пользователя, как он предлагает?

Ответ 14

Не каждый тип расширения приложения поддерживает "extensionContext openURL".

Я тестировал на iOS 8 beta 4 и обнаружил, что расширение Today поддерживает его, но расширение клавиатуры не работает.

Ответ 15

Кажется, работает только Today Extension.
Он не задокументирован на 100%, но сотрудник Apple конкретно говорит, что расширения клавиатуры не поддерживают openURL: completeHandler.
В документации указано:

Каждая точка расширения определяет, поддерживать ли этот метод или в каких условиях поддерживать этот метод.

Таким образом, на практике поставщик Share, Action, Keyboard и Document делает не работу для всех (бета-версия 5), и поддерживается только Today Extension.

Ответ 16

Как документ яблока "Виджет" Сегодня "(и другой тип расширения приложения) может попросить систему открыть свое содержащее приложение, вызвав метод openURL: completeHandler: класс NSExtensionContext."

Для другого расширения я использовал это решение

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