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
334 stars 395 forks source link

iOS 18 #324

Closed Nightbl927 closed 1 month ago

Nightbl927 commented 2 months ago

Is anyone else having issues sharing on an iOS 18 device? I still get the icon to share to the app, and it appears to be fine on the app that is doing the sharing but it seems like the app that is receiving the sharing never opens and receives the shared data.

Nightbl927 commented 2 months ago

attached what I believe to be the error is:

Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Share_Extension.ShareViewController openURL:]: unrecognized selector sent to instance 0x10201f400'

FROM

private func redirectToHostApp() {
        // ids may not loaded yet so we need loadIds here too
        loadIds()
        let url = URL(string: "\(kSchemePrefix)-\(hostAppBundleIdentifier):share")
        var responder = self as UIResponder?
        let selectorOpenURL = sel_registerName("openURL:")
        while (responder != nil) {
            if (responder?.responds(to: selectorOpenURL))! {
                _ = responder?.perform(selectorOpenURL, with: url)
            }
            responder = responder!.next
        }
        extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
    }
zhponng commented 2 months ago

I have the same issue; file sharing in iOS 18 is unresponsive and doesn't show any error messages, but it works fine in iOS 16 and 17.

aleynaak commented 2 months ago

Any news? I'm having the same issue. @Nightbl927

Nightbl927 commented 2 months ago

Any news? I'm having the same issue. @Nightbl927

No news. I haven't found any way to resolve this. Unfortunately, I am not too familiar with Swift to troubleshoot it myself. I was able to pinpoint the issue to let selectorOpenURL = sel_registerName("openURL:"). I believe this no longer works in iOS 18.

sstadtl commented 2 months ago

I think i found the solution here: This is an external link ](https://stackoverflow.com/questions/27506413/share-extension-to-open-containing-app/78975759#78975759) doing more testing right now....

@objc @discardableResult private func openURL(_ url: URL) -> Bool {
    var responder: UIResponder? = self
    while responder != nil {
        if let application = responder as? UIApplication {
            if #available(iOS 18.0, *) {
                application.open(url, options: [:], completionHandler: nil)
                return true
            } else {
                return application.perform(#selector(openURL(_:)), with: url) != nil
            }
        }
        responder = responder?.next
    }
    return false
}
Nightbl927 commented 1 month ago

I think i found the solution here: This is an external link ](https://stackoverflow.com/questions/27506413/share-extension-to-open-containing-app/78975759#78975759) doing more testing right now....

@objc @discardableResult private func openURL(_ url: URL) -> Bool {
    var responder: UIResponder? = self
    while responder != nil {
        if let application = responder as? UIApplication {
            if #available(iOS 18.0, *) {
                application.open(url, options: [:], completionHandler: nil)
                return true
            } else {
                return application.perform(#selector(openURL(_:)), with: url) != nil
            }
        }
        responder = responder?.next
    }
    return false
}

How did your testing go? @sstadtl?

sstadtl commented 1 month ago

it works in ip14 iOS18, Sim ip15 iOS 17.5.1.

this is my new code in ShareViewController.swift:

private func redirectToHostApp(type: RedirectType) {
        let url = URL(string: "ShareMedia-\(hostAppBundleIdentifier)://dataUrl=\(sharedKey)#\(type)")
        var responder = self as UIResponder?
        // https://stackoverflow.com/questions/27506413/share-extension-to-open-containing-app/78975759#78975759
        // https://github.com/KasemJaffer/receive_sharing_intent/issues/324
        if #available(iOS 18.0, *) {            
            while responder != nil {
                   if let application = responder as? UIApplication {                       
                       application.open(url!, options: [:], completionHandler: nil)                       
                   }
                   responder = responder?.next
               }
        } else {
            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)
        }
    }

But for historic reasons i use a very custom "ShareViewController.swift:". so i am unsure this works by just overwriting this single function for everyone.

as "redirectToHostApp" is private i think there is more to do...

Nightbl927 commented 1 month ago

Thank you @sstadtl , I will see if this works for me.

Akash-ptl commented 1 month ago

is there any update on this ? @sstadtl

Nightbl927 commented 1 month ago

Yes application will not be in scope with the code as is. However, changing application.open(url!, options: [:], completionHandler: nil) to UIApplication.shared.open(url!, options: [:], completionHandler: nil) should work

Akash-ptl commented 1 month ago

Yes application will not be in scope with the code as is. However, changing application.open(url!, options: [:], completionHandler: nil) to UIApplication.shared.open(url!, options: [:], completionHandler: nil) should work

Not working for me

i am still not able to receive files from ios 18 iphone to my flutter app

Nightbl927 commented 1 month ago

Yes application will not be in scope with the code as is. However, changing application.open(url!, options: [:], completionHandler: nil) to UIApplication.shared.open(url!, options: [:], completionHandler: nil) should work

Not working for me

i am still not able to receive files from ios 18 iphone to my flutter app

I am using it to receive shared links and text from Safari and have not tested with files yet. Could you see if that works for you?

malt03 commented 1 month ago

It works for me! Thank you! completeRequest may need to be called outside of the if statement.

import receive_sharing_intent

import UIKit
import MobileCoreServices
import Photos

@available(swift, introduced: 5.0)
open class ShareViewController: UIViewController {
    var hostAppBundleIdentifier = ""
    var appGroupId = ""

    open override func viewDidLoad() {
        super.viewDidLoad()

        loadIds()
    }

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

        guard
            let content = extensionContext!.inputItems[0] as? NSExtensionItem,
            let attachments = content.attachments,
            let attachment = attachments.first
            else { return }

        if !attachment.hasItemConformingToTypeIdentifier(UTType.fileURL.identifier) { return }
        attachment.loadItem(forTypeIdentifier: UTType.fileURL.identifier) { [weak self] (data, error) in
            guard let s = self, let url = data as? URL else { return }
            s.handleMedia(forFile: url)
        }
    }

    private func loadIds() {
        let shareExtensionAppBundleIdentifier = Bundle.main.bundleIdentifier!

        let lastIndexOfPoint = shareExtensionAppBundleIdentifier.lastIndex(of: ".")
        hostAppBundleIdentifier = String(shareExtensionAppBundleIdentifier[..<lastIndexOfPoint!])
        let defaultAppGroupId = "group.\(hostAppBundleIdentifier)"

        let customAppGroupId = Bundle.main.object(forInfoDictionaryKey: kAppGroupIdKey) as? String

        appGroupId = customAppGroupId ?? defaultAppGroupId
    }

    private func handleMedia(forFile url: URL) {
        let fileName = url.lastPathComponent
        let newPath = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupId)!.appendingPathComponent(fileName)

        if !copyFile(at: url, to: newPath) { return }

        let newPathDecoded = newPath.absoluteString.removingPercentEncoding!;
        saveAndRedirect(data: SharedMediaFile(
            path: newPathDecoded,
            mimeType: url.mimeType(),
            type: .file
        ))
    }

    private func saveAndRedirect(data: SharedMediaFile) {
        let userDefaults = UserDefaults(suiteName: appGroupId)
        userDefaults?.set(toData(data: [data]), forKey: kUserDefaultsKey)
        userDefaults?.set(nil, forKey: kUserDefaultsMessageKey)
        userDefaults?.synchronize()
        redirectToHostApp()
    }

    private func redirectToHostApp() {
        loadIds()
        let url = URL(string: "\(kSchemePrefix)-\(hostAppBundleIdentifier):share")
        var responder = self as UIResponder?

        if #available(iOS 18.0, *) {
            while responder != nil {
                if let application = responder as? UIApplication {
                    application.open(url!, options: [:], completionHandler: nil)
                }
                responder = responder?.next
            }
        } else {
            let selectorOpenURL = sel_registerName("openURL:")

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

    private func copyFile(at srcURL: URL, to dstURL: URL) -> Bool {
        do {
            if FileManager.default.fileExists(atPath: dstURL.path) {
                try FileManager.default.removeItem(at: dstURL)
            }
            try FileManager.default.copyItem(at: srcURL, to: dstURL)
        } catch (let error) {
            print("Cannot copy item at \(srcURL) to \(dstURL): \(error)")
            return false
        }
        return true
    }

    private func toData(data: [SharedMediaFile]) -> Data {
        let encodedData = try? JSONEncoder().encode(data)
        return encodedData!
    }
}

extension URL {
    public func mimeType() -> String {
        if #available(iOS 14.0, *) {
            if let mimeType = UTType(filenameExtension: self.pathExtension)?.preferredMIMEType {
                return mimeType
            }
        } else {
            if let uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, self.pathExtension as NSString, nil)?.takeRetainedValue() {
                if let mimetype = UTTypeCopyPreferredTagWithClass(uti, kUTTagClassMIMEType)?.takeRetainedValue() {
                    return mimetype as String
                }
            }
        }

        return "application/octet-stream"
    }
}
aleynaak commented 1 month ago

I use the share extension for sharing files and text in my application. I've been struggling with the problem for a few days but still haven't found a solution. I've tried similar suggestions as above but without success. My ShareViewController is as it is currently used in the package. Does anyone have a different update for this situation?

Nightbl927 commented 1 month ago

@aleynaak have you updated the ShareViewController as suggested by @malt03 above? I have not yet updated and forked the code but if someone is willing to do that who has gotten it to work, that could also help.

dmitry-kotorov commented 1 month ago

@Nightbl927 I've updated the controller. Please merge https://github.com/KasemJaffer/receive_sharing_intent/pull/328

Nightbl927 commented 1 month ago

@Nightbl927 I've updated the controller. Please merge #328

Unfortunately, I am unable to merge it. I was hoping for someone to fork and have @aleynaak pull from that repo.

aleynaak commented 1 month ago

Thank you for your support. I solved the problem. It was a problem with my URLShemes. When I fixed it, the problem disappeared. @Nightbl927 @dmitry-kotorov

Nightbl927 commented 1 month ago

Thank you for your support. I solved the problem. It was a problem with my URLShemes. When I fixed it, the problem disappeared. @Nightbl927 @dmitry-kotorov

Good to hear!

test0terter0n commented 1 month ago

We have tested current master with PR https://github.com/KasemJaffer/receive_sharing_intent/pull/328 and it seems to work correct ✅. Pls consider releasing new version

castelles commented 1 month ago

Release a new version, please :)

KasemJaffer commented 1 month ago

I just published PR https://github.com/KasemJaffer/receive_sharing_intent/pull/328 under version 1.8.1

Thank you all and sorry for the delay