Расширение Share для открытия приложения

Я хочу создать функцию совместного использования стилей Android для своего приложения. Я создал расширение общего доступа, которое вызывается, когда вы выбираете фотографии внутри приложения для фотодокументов и нажимаете "Поделиться". Теперь я хочу, чтобы эти фотографии отправлялись в основное приложение и обрабатывались там. Мой вопрос:

  • Может ли iOS открыть мое приложение после нажатия кнопки в окне расширения общего доступа?
  • Как получить файлы изображений внутри основного приложения?

Ответ 1

В настоящее время нет способа сделать это. Расширение общего доступа не может открыть содержащее приложение.

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

Если вы хотите сделать доступными данные для своего приложения, вы можете настроить группу приложений, чтобы у вас был общий каталог. Расширение может записывать данные там, и приложение может его прочитать. Это произойдет не до следующего раза, когда пользователь запустит приложение.

Ответ 2

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 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 с тем же определением, что и в UIApplication, чтобы компилятор был доволен (так что #selector (openURL (_ :)) может быть найденным).

Затем я прохожу по респондентам, пока не найду тот, который действительно получен из 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 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
}
}

Ответ 3

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

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

Ответ 4

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

Ответ 5

Внедрить пользовательскую схему url в хост-приложении и вызвать openURL (метод url:)

как openURL (url: NSURL (строка: "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)
    }

}

Ответ 6

У меня была эта проблема, и в iOS 11+ ни один из предыдущих ответов не работает. В итоге я добавил обработчик завершения в свой код JavaScript и оттуда установил window.location="myapp://". Это немного странно, но это не выглядит плохо, и пользователь может следовать за ним.

Ответ 7

Не только для этого нет (и не будет): нет НЕОБХОДИМОСТИ для этого в приложении. Предполагается, что расширение должно справиться с этим как и основное приложение. Вы должны создать структуру с API безопасности расширения, совместно используемым между приложением и целями extesnion.

Это главная тема здесь: https://developer.apple.com/library/content/documentation/General/Conceptual/ExtensibilityPG/ExtensionScenarios.html#//apple_ref/doc/uid/TP40014214-CH21-SW1

Ответ 8

EDIT: это решение работает на сегодняшний день (Widget).

Расширение может открыть приложение для хостинга:

- (IBAction)launchHostingApp:(id)sender
{
 NSURL *pjURL = [NSURL URLWithString:@"hostingapp://home"];
[self.extensionContext openURL:pjURL completionHandler:nil];
}

И как Apple говорит в "Сценарии использования сообщества" :

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