web-dev-qa-db-ja.com

拡張機能を共有して、含まれているアプリを開く

私はAndroidアプリのスタイル共有機能を作成します。ストックフォトアプリ内の画像を選択して共有を押すと呼び出される共有拡張機能を作成しました。これらの画像をメインアプリに送信され、そこで処理されます。私の質問は次のとおりです。

  1. 共有拡張ウィンドウでボタンを押した後、iOSでアプリを開くことはできますか?
  2. メインアプリ内で画像ファイルを取得するにはどうすればよいですか?
24
zanzoken

現在、これを行う方法はありません。共有拡張機能は、それを含むアプリを開くことができません。

共有拡張のintendedアプローチは、必要なすべての作業を自分で処理するというものです。拡張機能は、カスタムフレームワークを使用してコードを含むアプリとコードを共有できるため、ほとんどの場合、問題はありません。

アプリでデータを利用できるようにする場合は、アプリグループを設定して共有ディレクトリを作成できます。拡張機能はそこにデータを書き込むことができ、アプリはそれを読み取ることができます。ただし、それはユーザーが次にアプリを起動するまで発生しません。

12
Tom Harrington

Swift 4+(iOS 13でテスト済み)

_@objc_をopenURLの宣言に追加する必要があります。つまり、

_@objc func openURL(_ url: URL) -> Bool {
    // Code below.
}
_

これがないと、このコンパイラエラーが発生します。

_Argument of '#selector' refers to instance method 'openURL' that is not exposed to Objective-C
_

Swift 3.1(iOS10でテスト済み):の作業ソリューション

独自のURLスキームを作成し、この関数をViewControllerに追加して、openURL("myScheme://myIdentifier")で呼び出す必要があります

_//  Function must be named exactly like this so a selector can be found by the compiler!
//  Anyway - it's another selector in another instance that would be "performed" instead.
func openURL(_ url: URL) -> Bool {
    var responder: UIResponder? = self
    while responder != nil {
        if let application = responder as? UIApplication {
            return application.perform(#selector(openURL(_:)), with: url) != nil
        }
        responder = responder?.next
    }
    return false
}
_

編集:明確化のためのメモ:openURLはUIApplicationのメソッドです-ShareExtensionはUIApplicationから派生していないため、同じ定義で自分のopenURLを追加しました#selector(openURL(_:)が見つかるように)UIApplicationからコンパイラを幸せに保ちます。

次に、実際にUIApplicationから派生したものを見つけるまでレスポンダを調べ、openURLを呼び出します。

ShareExtension内のファイルをローカルディレクトリにコピーし、ファイル名をシリアル化し、別のアプリでopenURLを呼び出す、よりストリップされたサンプルコード:

_//
//  ShareViewController.Swift
//

import UIKit
import Social
import MobileCoreServices

class ShareViewController: UIViewController {

var docPath = ""

override func viewDidLoad() {
    super.viewDidLoad()

    let containerURL = FileManager().containerURL(forSecurityApplicationGroupIdentifier: "group.com.my-domain")!
    docPath = "\(containerURL.path)/share"

    //  Create directory if not exists
    do {
        try FileManager.default.createDirectory(atPath: docPath, withIntermediateDirectories: true, attributes: nil)
    } catch let error as NSError {
        print("Could not create the directory \(error)")
    } catch {
        fatalError()
    }

    //  removing previous stored files
    let files = try! FileManager.default.contentsOfDirectory(atPath: docPath)
    for file in files {
        try? FileManager.default.removeItem(at: URL(fileURLWithPath: "\(docPath)/\(file)"))
    }
}

override func viewDidAppear(_ animated: Bool) {

    let alertView = UIAlertController(title: "Export", message: " ", preferredStyle: .alert)

    self.present(alertView, animated: true, completion: {

        let group = DispatchGroup()

        NSLog("inputItems: \(self.extensionContext!.inputItems.count)")

            for item: Any in self.extensionContext!.inputItems {

            let inputItem = item as! NSExtensionItem

            for provider: Any in inputItem.attachments! {

                let itemProvider = provider as! NSItemProvider
                group.enter()
                itemProvider.loadItem(forTypeIdentifier: kUTTypeData as String, options: nil) { data, error in
                    if error == nil {
                        //  Note: "data" may be another type (e.g. Data or UIImage). Casting to URL may fail. Better use switch-statement for other types.
                        //  "screenshot-tool" from iOS11 will give you an UIImage here
                        let url = data as! URL
                        let path = "\(self.docPath)/\(url.pathComponents.last ?? "")"
                        print(">>> sharepath: \(String(describing: url.path))")

                        try? FileManager.default.copyItem(at: url, to: URL(fileURLWithPath: path))

                    } else {
                        NSLog("\(error)")
                    }
                    group.leave()
                }
            }
        }

        group.notify(queue: DispatchQueue.main) {
            NSLog("done")

            let files = try! FileManager.default.contentsOfDirectory(atPath: self.docPath)

            NSLog("directory: \(files)")

            //  Serialize filenames, call openURL:
            do {
                let jsonData : Data = try JSONSerialization.data(
                    withJSONObject: [
                        "action" : "incoming-files"
                        ],
                    options: JSONSerialization.WritingOptions.init(rawValue: 0))
                let jsonString = (NSString(data: jsonData, encoding: String.Encoding.utf8.rawValue)! as String).addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)
                let result = self.openURL(URL(string: "myapp://com.myapp.share?\(jsonString!)")!)
            } catch {
                alertView.message = "Error: \(error.localizedDescription)"
            }
            self.dismiss(animated: false) {
                self.extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
            }
        }
    })
}

//  Function must be named exactly like this so a selector can be found by the compiler!
//  Anyway - it's another selector in another instance that would be "performed" instead.
func openURL(_ url: URL) -> Bool {
    var responder: UIResponder? = self
    while responder != nil {
        if let application = responder as? UIApplication {
            return application.perform(#selector(openURL(_:)), with: url) != nil
        }
        responder = responder?.next
    }
    return false
}
}
_
35
coyer

技術的には、共有拡張機能から含まれているアプリを開くことはできませんが、ローカル通知をスケジュールすることができます。それが私がやっていることです。 super.didSelectPostを呼び出す直前に、テキストでローカル通知をスケジュールします。ユーザーが含まれているアプリを開きたい場合は、できます。できなかった場合は、ワークフローを続行できます。私は、それを含むアプリを自動的に開いて、彼らがしていることを妨害するよりも良いアプローチだとさえ思っています。

6
Cherpak Evgeny

ホストアプリにカスタムURLスキーマを実装し、openURL(url :)メソッドを呼び出す

like openURL(url:NSURL(string: "schema_name://"))

extension SLComposeServiceViewController {

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

}
3
SPatel

共有拡張機能からホストアプリをトリックで開きました。クリアな背景色のウェブビューを使用します。以下はコードです

 NSString *customURL = @"MY_Host_URL_SCHEME_APP://";
UIWebView *webView = [[UIWebView alloc] initWithFrame:CGRectMake(0, 0, 300, 400)];
webView.backgroundColor = [UIColor clearColor];
    webView.tintColor = [UIColor clearColor];
    [webView setOpaque:NO];
    [self.view addSubview:webView];
    NSURLRequest *urlRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:customURL]];
    [webView loadRequest:urlRequest];
    [self didSelectCancel];
3
Waqas

私はこの問題を抱えていました。iOS11以降では、これまでの回答はどれも機能しません。 JavaScriptコードに完了ハンドラーを追加して、そこからwindow.location="myapp://"。少しハックですが、見た目は悪くないので、ユーザーはそれに従ってください。

0
Nylon

これを行う方法がない(そしてできない)だけでなく、アプリでこれを処理する必要もありません。拡張機能は、メインアプリとまったく同じコードベースでこれを処理することになっています。アプリと拡張ターゲット間で共有される拡張セーフAPIを使用してフレームワークを作成する必要があります。

これがここのトップトピックです: https://developer.Apple.com/library/content/documentation/General/Conceptual/ExtensibilityPG/ExtensionScenarios.html#//Apple_ref/doc/uid/TP40014214-CH21-SW1

0
Anton Tropashko