从iOS8自定义键盘启动包含应用程序

时间:2021-07-04 07:11:35

I want to launch my containing app.

我想发布我的包含应用程序。

I tried using URL schemes.

我尝试使用URL方案。

The URL scheme launched the app from other places - so the problem is not there.

URL方案从其他地方启动了应用程序 - 所以问题不在那里。

Looks like this object is nil:

看起来这个对象是零:

   self.extensionContext

thus i can't run this method:

因此我无法运行此方法:

[self.extensionContext openURL:url completionHandler:nil];

Can I launch my app? Do URL Schemes work in a custom keyboard?

我可以启动我的应用吗? URL方案是否可以在自定义键盘中使用?

thanks!

11 个解决方案

#1


13  

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

#2


9  

To answer MY OWN question:

回答我自己的问题:

In an iOS8 custom keyboard, the extensionContext object is nil, thus I can't use it to launch the containing app.

在iOS8自定义键盘中,extensionContext对象为nil,因此我无法使用它来启动包含应用程序。

The workaround I came up with is:

我想出的解决方法是:

  1. create a url scheme for your app
  2. 为您的应用创建网址方案

  3. add a UIWebView to your inputView
  4. 将UIWebView添加到inputView

  5. load the url scheme for your containing app in the webview
  6. 在webview中加载包含应用程序的url方案

I'm not sure if Apple will allow this to happen, but it works now.

我不确定苹果是否允许这种情况发生,但它现在有效。

#3


8  

Here is working solution (tested on iOS 9.2) for Keyboard Extension. This category adds special method for access to hidden sharedApplication object and then call openURL: on it. (Of course then you have to use openURL: method with your app scheme.)

这是键盘扩展的工作解决方案(在iOS 9.2上测试)。此类别添加了访问隐藏的sharedApplication对象的特殊方法,然后在其上调用openURL:。 (当然,您必须使用openURL:方法与您的应用程序方案。)

// Usage:
// UIInputViewController.openURL(NSURL(string: "your-app-scheme://")!)
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)
    }

}

Lately I developed slightly different approach:

最近我开发了略有不同的方法:

// Usage:
// UIApplication.????sharedApplication().????openURL(NSURL(string: "your-app-scheme://")!)

extension UIApplication {

    public static func ????sharedApplication() -> UIApplication {
        guard UIApplication.respondsToSelector("sharedApplication") else {
            fatalError("UIApplication.sharedKeyboardApplication(): `UIApplication` does not respond to selector `sharedApplication`.")
        }

        guard let unmanagedSharedApplication = UIApplication.performSelector("sharedApplication") else {
            fatalError("UIApplication.sharedKeyboardApplication(): `UIApplication.sharedApplication()` returned `nil`.")
        }

        guard let sharedApplication = unmanagedSharedApplication.takeUnretainedValue() as? UIApplication else {
            fatalError("UIApplication.sharedKeyboardApplication(): `UIApplication.sharedApplication()` returned not `UIApplication` instance.")
        }

        return sharedApplication
    }

    public func ????openURL(url: NSURL) -> Bool {
        return self.performSelector("openURL:", withObject: url) != nil
    }

}

#4


4  

Apple does not allow any app extensions other than Today extensions to open the containing app.

Apple不允许除今日扩展程序之外的任何应用程序扩展程序打开包含应用程序。

From the guidelines:

从指南:

A Today widget (and no other app extension type) can ask the system to open its containing app by calling the openURL:completionHandler: method of the NSExtensionContext class.

“今日”窗口小部件(并且没有其他应用程序扩展类型)可以通过调用NSExtensionContext类的openURL:completionHandler:方法来要求系统打开其包含的应用程序。

You can check Here

你可以在这里查看

#5


2  

I tried to do above solutions for latest xcode 8.2 and swift 3.0.

我试着为最新的xcode 8.2和swift 3.0做上面的解决方案。

Unfortunately. I can't get it work. So I found my own solution and it works well in swift 3.0, xcode 8.2

不幸。我无法让它发挥作用。所以我找到了自己的解决方案,它在swift 3.0,xcode 8.2中运行良好

   func openURL(_ url: URL) {
        return
   }

   func openApp(_ urlstring:String) {

       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: urlstring)!)
              return
             }
             responder = responder?.next
          }
   }

  // Usage
  //call the method like below
  //self.openApp(urlString)

  //URL string need to included custom scheme.
  //for example, if you created scheme name = customApp
  //urlString will be "customApp://?[name]=[value]"
  //self.openApp("customApp://?category=1")

#6


1  

According to Apple's documentation https://developer.apple.com/library/ios/documentation/General/Conceptual/ExtensibilityPG/index.html, you should use NSExtensionContext like this:

根据Apple的文档https://developer.apple.com/library/ios/documentation/General/Conceptual/ExtensibilityPG/index.html,您应该使用NSExtensionContext,如下所示:

NSExtensionContext *ctx = [[NSExtensionContext alloc] init];
[ctx openURL:[NSURL URLWithString:@"myapp://"] completionHandler:^(BOOL success){return ;}];

#7


1  

In an App Extension (ex: custom keyboard), that would have been handled through UIViewController.extensionContext but as of iOS 8.1.2, the field is nil in the following Swift call:

在App Extension(例如:自定义键盘)中,可以通过UIViewController.extensionContext处理,但是从iOS 8.1.2开始,该字段在以下Swift调用中为nil:

self.extensionContext?.openURL(appURL, completionHandler: nil)
// Does nothing because extensionContext is nil (iOS 8.1)

Actually, it is not possible either to use Application.sharedApplication.openURL(...) in an App Extension as stated in Apple documentation.

实际上,如Apple文档中所述,无法在App Extension中使用Application.sharedApplication.openURL(...)。

So, as of 8.1.2, the workaround is to use a dumb UIWebView to redirect to the containing app like this:

因此,从8.1.2开始,解决方法是使用哑UIWebView重定向到包含的应用程序,如下所示:

let webView = UIWebView(frame: CGRectMake(0, 0, 0, 0));
let url = "MyKeyboard://";
let content = "<head><meta http-equiv='refresh' content='0; URL=\(url)'></head>";
webView.loadHTMLString(content, baseURL: nil);
self.view.addSubview(webView);
let delayInSeconds = 2.0
let startTime = dispatch_time(DISPATCH_TIME_NOW, Int64(delayInSeconds * Double(NSEC_PER_SEC)))
dispatch_after(startTime, dispatch_get_main_queue()) { () -> Void in
    webView.removeFromSuperview()
}

Where MyKeyboard:// must be defined accordingly as URL scheme in the containing app.

其中MyKeyboard://必须在包含应用程序中相应地定义为URL方案。

Please take note that I don't know yet whether this is approved by Apple. Forwarding to apps from custom keyboards may be bad enough for user experience.

请注意,我还不知道这是否得到Apple的批准。从自定义键盘转发到应用程序可能对用户体验来说足够糟糕。

#8


1  

This is what I found to open any URL using what has been described above:

这是我发现使用上面描述的内容打开任何URL:

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

Please note that in this case I am instantiating this call from the UIInputViewController.

请注意,在这种情况下,我将从UIInputViewController实例化此调用。

This method should also work using the URL scheme from the containing app

此方法也应使用包含应用程序中的URL方案

NOTE: As of iOS 8.3 Apple has killed this method

注意:从iOS 8.3开始,Apple已经杀死了这种方法

#9


1  

A Swift 2.2 version, akin to DongHyun Jang's answer for Objective-C:

一个Swift 2.2版本,类似于DongHyun Jang对Objective-C的回答:

func webLink(urlString: String) {
    let url = NSURL(string: urlString)
    var responder: UIResponder? = self
    while let r = responder {
        if r.respondsToSelector("openURL:") {
            r.performSelector("openURL:", withObject: url)
            break;
        }
        responder = r.nextResponder()
    }  
}

#10


1  

As per Apple review guidelines for Extension, it is now not advised to open external apps from the extension.

根据Apple的Extension审核指南,现在不建议从扩展程序中打开外部应用程序。

For reference please see apple review guidelines

供参考,请参阅苹果评审指南

Section 4.4.1

They must not:

他们不能:

  • Include marketing, advertising, or in-app purchases;
  • 包括营销,广告或应用内购买;

  • Launch other apps besides Settings; or
  • 启动除设置之外的其他应用要么

#11


0  

I struggled with this for a couple of days. I could not get the UIWebView working inside the custom keyboard.

我挣扎了几天。我无法让UIWebView在自定义键盘内工作。

Fix: put it inside the viewDidAppear:animated instead of viewDidLoad (on another note this is where the code for the keyboard's custom height should stay as well).

修复:将它放在viewDidAppear:animated而不是viewDidLoad中(另一方面,这是键盘自定义高度的代码也应该保留的位置)。

Code:

- (void)viewDidAppear:(BOOL)animated {
    UIWebView * webView = [[UIWebView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
    NSString * content = @"<a href='your_app_schema://your_params'>anchor</a>";
    [webView loadHTMLString:content baseURL:nil];
    [self.view addSubview:webView];
}

#1


13  

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

#2


9  

To answer MY OWN question:

回答我自己的问题:

In an iOS8 custom keyboard, the extensionContext object is nil, thus I can't use it to launch the containing app.

在iOS8自定义键盘中,extensionContext对象为nil,因此我无法使用它来启动包含应用程序。

The workaround I came up with is:

我想出的解决方法是:

  1. create a url scheme for your app
  2. 为您的应用创建网址方案

  3. add a UIWebView to your inputView
  4. 将UIWebView添加到inputView

  5. load the url scheme for your containing app in the webview
  6. 在webview中加载包含应用程序的url方案

I'm not sure if Apple will allow this to happen, but it works now.

我不确定苹果是否允许这种情况发生,但它现在有效。

#3


8  

Here is working solution (tested on iOS 9.2) for Keyboard Extension. This category adds special method for access to hidden sharedApplication object and then call openURL: on it. (Of course then you have to use openURL: method with your app scheme.)

这是键盘扩展的工作解决方案(在iOS 9.2上测试)。此类别添加了访问隐藏的sharedApplication对象的特殊方法,然后在其上调用openURL:。 (当然,您必须使用openURL:方法与您的应用程序方案。)

// Usage:
// UIInputViewController.openURL(NSURL(string: "your-app-scheme://")!)
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)
    }

}

Lately I developed slightly different approach:

最近我开发了略有不同的方法:

// Usage:
// UIApplication.????sharedApplication().????openURL(NSURL(string: "your-app-scheme://")!)

extension UIApplication {

    public static func ????sharedApplication() -> UIApplication {
        guard UIApplication.respondsToSelector("sharedApplication") else {
            fatalError("UIApplication.sharedKeyboardApplication(): `UIApplication` does not respond to selector `sharedApplication`.")
        }

        guard let unmanagedSharedApplication = UIApplication.performSelector("sharedApplication") else {
            fatalError("UIApplication.sharedKeyboardApplication(): `UIApplication.sharedApplication()` returned `nil`.")
        }

        guard let sharedApplication = unmanagedSharedApplication.takeUnretainedValue() as? UIApplication else {
            fatalError("UIApplication.sharedKeyboardApplication(): `UIApplication.sharedApplication()` returned not `UIApplication` instance.")
        }

        return sharedApplication
    }

    public func ????openURL(url: NSURL) -> Bool {
        return self.performSelector("openURL:", withObject: url) != nil
    }

}

#4


4  

Apple does not allow any app extensions other than Today extensions to open the containing app.

Apple不允许除今日扩展程序之外的任何应用程序扩展程序打开包含应用程序。

From the guidelines:

从指南:

A Today widget (and no other app extension type) can ask the system to open its containing app by calling the openURL:completionHandler: method of the NSExtensionContext class.

“今日”窗口小部件(并且没有其他应用程序扩展类型)可以通过调用NSExtensionContext类的openURL:completionHandler:方法来要求系统打开其包含的应用程序。

You can check Here

你可以在这里查看

#5


2  

I tried to do above solutions for latest xcode 8.2 and swift 3.0.

我试着为最新的xcode 8.2和swift 3.0做上面的解决方案。

Unfortunately. I can't get it work. So I found my own solution and it works well in swift 3.0, xcode 8.2

不幸。我无法让它发挥作用。所以我找到了自己的解决方案,它在swift 3.0,xcode 8.2中运行良好

   func openURL(_ url: URL) {
        return
   }

   func openApp(_ urlstring:String) {

       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: urlstring)!)
              return
             }
             responder = responder?.next
          }
   }

  // Usage
  //call the method like below
  //self.openApp(urlString)

  //URL string need to included custom scheme.
  //for example, if you created scheme name = customApp
  //urlString will be "customApp://?[name]=[value]"
  //self.openApp("customApp://?category=1")

#6


1  

According to Apple's documentation https://developer.apple.com/library/ios/documentation/General/Conceptual/ExtensibilityPG/index.html, you should use NSExtensionContext like this:

根据Apple的文档https://developer.apple.com/library/ios/documentation/General/Conceptual/ExtensibilityPG/index.html,您应该使用NSExtensionContext,如下所示:

NSExtensionContext *ctx = [[NSExtensionContext alloc] init];
[ctx openURL:[NSURL URLWithString:@"myapp://"] completionHandler:^(BOOL success){return ;}];

#7


1  

In an App Extension (ex: custom keyboard), that would have been handled through UIViewController.extensionContext but as of iOS 8.1.2, the field is nil in the following Swift call:

在App Extension(例如:自定义键盘)中,可以通过UIViewController.extensionContext处理,但是从iOS 8.1.2开始,该字段在以下Swift调用中为nil:

self.extensionContext?.openURL(appURL, completionHandler: nil)
// Does nothing because extensionContext is nil (iOS 8.1)

Actually, it is not possible either to use Application.sharedApplication.openURL(...) in an App Extension as stated in Apple documentation.

实际上,如Apple文档中所述,无法在App Extension中使用Application.sharedApplication.openURL(...)。

So, as of 8.1.2, the workaround is to use a dumb UIWebView to redirect to the containing app like this:

因此,从8.1.2开始,解决方法是使用哑UIWebView重定向到包含的应用程序,如下所示:

let webView = UIWebView(frame: CGRectMake(0, 0, 0, 0));
let url = "MyKeyboard://";
let content = "<head><meta http-equiv='refresh' content='0; URL=\(url)'></head>";
webView.loadHTMLString(content, baseURL: nil);
self.view.addSubview(webView);
let delayInSeconds = 2.0
let startTime = dispatch_time(DISPATCH_TIME_NOW, Int64(delayInSeconds * Double(NSEC_PER_SEC)))
dispatch_after(startTime, dispatch_get_main_queue()) { () -> Void in
    webView.removeFromSuperview()
}

Where MyKeyboard:// must be defined accordingly as URL scheme in the containing app.

其中MyKeyboard://必须在包含应用程序中相应地定义为URL方案。

Please take note that I don't know yet whether this is approved by Apple. Forwarding to apps from custom keyboards may be bad enough for user experience.

请注意,我还不知道这是否得到Apple的批准。从自定义键盘转发到应用程序可能对用户体验来说足够糟糕。

#8


1  

This is what I found to open any URL using what has been described above:

这是我发现使用上面描述的内容打开任何URL:

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

Please note that in this case I am instantiating this call from the UIInputViewController.

请注意,在这种情况下,我将从UIInputViewController实例化此调用。

This method should also work using the URL scheme from the containing app

此方法也应使用包含应用程序中的URL方案

NOTE: As of iOS 8.3 Apple has killed this method

注意:从iOS 8.3开始,Apple已经杀死了这种方法

#9


1  

A Swift 2.2 version, akin to DongHyun Jang's answer for Objective-C:

一个Swift 2.2版本,类似于DongHyun Jang对Objective-C的回答:

func webLink(urlString: String) {
    let url = NSURL(string: urlString)
    var responder: UIResponder? = self
    while let r = responder {
        if r.respondsToSelector("openURL:") {
            r.performSelector("openURL:", withObject: url)
            break;
        }
        responder = r.nextResponder()
    }  
}

#10


1  

As per Apple review guidelines for Extension, it is now not advised to open external apps from the extension.

根据Apple的Extension审核指南,现在不建议从扩展程序中打开外部应用程序。

For reference please see apple review guidelines

供参考,请参阅苹果评审指南

Section 4.4.1

They must not:

他们不能:

  • Include marketing, advertising, or in-app purchases;
  • 包括营销,广告或应用内购买;

  • Launch other apps besides Settings; or
  • 启动除设置之外的其他应用要么

#11


0  

I struggled with this for a couple of days. I could not get the UIWebView working inside the custom keyboard.

我挣扎了几天。我无法让UIWebView在自定义键盘内工作。

Fix: put it inside the viewDidAppear:animated instead of viewDidLoad (on another note this is where the code for the keyboard's custom height should stay as well).

修复:将它放在viewDidAppear:animated而不是viewDidLoad中(另一方面,这是键盘自定义高度的代码也应该保留的位置)。

Code:

- (void)viewDidAppear:(BOOL)animated {
    UIWebView * webView = [[UIWebView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
    NSString * content = @"<a href='your_app_schema://your_params'>anchor</a>";
    [webView loadHTMLString:content baseURL:nil];
    [self.view addSubview:webView];
}