KasemJaffer / receive_sharing_intent

A Flutter plugin that enables flutter apps to receive sharing photos, text and url from other apps.
Apache License 2.0
325 stars 372 forks source link

ios only open app when share extension, but haven't receive intent #251

Closed trungrikkei closed 1 year ago

trungrikkei commented 1 year ago
  1. Share extension/Info.plist
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>AppGroupId</key>
   <string>$(CUSTOM_GROUP_ID)</string>
    <key>CFBundleVersion</key>
    <string>$(FLUTTER_BUILD_NUMBER)</string>
    <key>NSExtension</key>
    <dict>
        <key>NSExtensionAttributes</key>
        <dict>
            <key>NSExtensionActivationRule</key>
            <dict>
                <key>NSExtensionActivationSupportsText</key>
                <true/>
                <key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
                <integer>1</integer>
            </dict>
        </dict>
        <key>NSExtensionMainStoryboard</key>
        <string>MainInterface</string>
        <key>NSExtensionPointIdentifier</key>
        <string>com.apple.share-services</string>
    </dict>
</dict>
</plist>
  1. ShareViewController
    
    import Social
    import MobileCoreServices
    import Photos
    import UniformTypeIdentifiers

class ShareViewController: SLComposeServiceViewController {

// TODO: IMPORTANT: This should be your host app bundle identifier
let sharedKey = "ShareKey";
var appGroupId = "group.test.example.com";
var hostAppBundleIdentifier = "test.example.com";
var sharedText: [String] = [];
let textContentType = kUTTypeText as String;
let urlContentType = kUTTypeURL as String;

override func isContentValid() -> Bool {
    // Do validation of contentText and/or NSExtensionContext attachments here
    return true;
}

private func loadIds() {
    hostAppBundleIdentifier = "test.example.com";
    appGroupId = "group.test.example.com";
}

override func viewDidLoad() {
        super.viewDidLoad();
        // load group and app id from build info
        loadIds();
    }

override func didSelectPost() {
    // This is called after the user selects Post. Do the upload of contentText and/or NSExtensionContext attachments.

    // Inform the host that we're done, so it un-blocks its UI. Note: Alternatively you could call super's -didSelectPost, which will similarly complete the extension context.
    // self.extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
    print("didSelectPost");
}

override func configurationItems() -> [Any]! {
    // To add configuration options via table cells at the bottom of the sheet, return an array of SLComposeSheetConfigurationItem here.
    return []
}

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

    // This is called after the user selects Post. Do the upload of contentText and/or NSExtensionContext attachments.
    if let content = extensionContext!.inputItems[0] as? NSExtensionItem {
        if let contents = content.attachments {
            for (index, attachment) in (contents).enumerated() {
                if attachment.hasItemConformingToTypeIdentifier(textContentType) {
                    handleText(content: content, attachment: attachment, index: index)
                } else if attachment.hasItemConformingToTypeIdentifier(urlContentType) {
                    handleUrl(content: content, attachment: attachment, index: index)
                }
            }
        }
    }
}

private func handleText (content: NSExtensionItem, attachment: NSItemProvider, index: Int) {
    attachment.loadItem(forTypeIdentifier: textContentType, options: nil) { [weak self] data, error in

        if error == nil, let item = data as? String, let this = self {

            this.sharedText.append(item)

            // If this is the last item, save imagesData in userDefaults and redirect to host app
            if index == (content.attachments?.count)! - 1 {
                let userDefaults = UserDefaults(suiteName: this.appGroupId)
                userDefaults?.set(this.sharedText, forKey: this.sharedKey)
                userDefaults?.synchronize()
                this.redirectToHostApp(type: .text)
            }

        } else {
            self?.dismissWithError()
        }
    }
}

private func handleUrl (content: NSExtensionItem, attachment: NSItemProvider, index: Int) {
        attachment.loadItem(forTypeIdentifier: urlContentType, options: nil) { [weak self] data, error in

            if error == nil, let item = data as? URL, let this = self {

                this.sharedText.append(item.absoluteString)

                // If this is the last item, save imagesData in userDefaults and redirect to host app
                if index == (content.attachments?.count)! - 1 {
                    let userDefaults = UserDefaults(suiteName: this.appGroupId)
                    userDefaults?.set(this.sharedText, forKey: this.sharedKey)
                    userDefaults?.synchronize()
                    this.redirectToHostApp(type: .text)
                }

            } else {
                self?.dismissWithError()
            }
        }
    }

private func dismissWithError() {
    print("[ERROR] Error loading data!")
    let alert = UIAlertController(title: "Error", message: "Error loading data", preferredStyle: .alert)

    let action = UIAlertAction(title: "Error", style: .cancel) { _ in
        self.dismiss(animated: true, completion: nil)
    }

    alert.addAction(action)
    present(alert, animated: true, completion: nil)
    extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
}

private func redirectToHostApp(type: RedirectType) {
        // ids may not loaded yet so we need loadIds here too
        loadIds();
        let url = URL(string: "ShareMedia://dataUrl=\(sharedKey)#\(type)")
        var responder = self as UIResponder?
        let selectorOpenURL = sel_registerName("openURL:")

        while (responder != nil) {
            if (responder?.responds(to: selectorOpenURL))! {
                let _ = responder?.perform(selectorOpenURL, with: url)
            }
            responder = responder!.next
        }
        extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
    }

enum RedirectType {
    case media
    case text
    case file
}

func getExtension(from url: URL, type: SharedMediaType) -> String {
    let parts = url.lastPathComponent.components(separatedBy: ".")
    var ex: String? = nil
    if (parts.count > 1) {
        ex = parts.last
    }

    if (ex == nil) {
        switch type {
            case .image:
                ex = "PNG"
            case .video:
                ex = "MP4"
            case .file:
                ex = "TXT"
        }
    }
    return ex ?? "Unknown"
}

enum SharedMediaType: Int, Codable {
    case image
    case video
    case file
}

}

extension Array { subscript (safe index: UInt) -> Element? { return Int(index) < count ? self[Int(index)] : nil } }


3. Runner.entitlements

<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">

aps-environment development com.apple.developer.associated-domains applinks:www.youtube.com applinks:youtube.com applinks:m.youtube.com applinks:youtu.be com.apple.security.application-groups group.test.example.com

4. In file RunnerDebug.entitlements is as same as Runner.entitlements

5. Podfile 
added this code 

target 'Runner' do use_frameworks! use_modular_headers!

flutter_install_all_ios_pods File.dirname(File.realpath(FILE))

Sharing Extension is name of Extension which you created. It is 'Share Extension' and 'Sharing Extension' in example

target 'Share Extension' do inherit! :search_paths end end

hnq90 commented 1 year ago

@trungrikkei Could you please share your solution if you have found it?

trungrikkei commented 1 year ago

add App Group to Share Extension is as same as Runner is worked