receive_sharing_intent icon indicating copy to clipboard operation
receive_sharing_intent copied to clipboard

iOS app does not recieve Intent on the latest iOS version

Open maciej-szulc opened this issue 3 years ago • 44 comments

Hey, I made all the setup for both platforms. It builds and I can see my app in share options in given app (youtube). On Android device I can recieve intent in my app, and iOS does not work.

Any idea why can't recieve text in my iOS app ? Regards

maciej-szulc avatar Dec 23 '20 14:12 maciej-szulc

struggling to make it work on iOS and would love to hear update on this

vbabaria avatar Dec 23 '20 14:12 vbabaria

Doesn't work on the latest version too on iOS. Sharing intent does nothing when clicked

blackmenthor avatar Dec 30 '20 02:12 blackmenthor

I was with the same problem and fixed it just adding the "group.your.app.path" to the Capability of the "Shared Extension" target. Same thing I add in the Runner target.

lecavallari avatar Dec 31 '20 08:12 lecavallari

@lecavallari

Do you mean to add it into App Groups ? Or change Bundle Identifier in Share Extension? Have you tested on the latest OS ?

maciej-szulc avatar Dec 31 '20 10:12 maciej-szulc

Yes, try to add the "group.your.app.path" in the App Groups of the Share Extension target. The same way you did in the App Groups of the Runner target. I'm not able to teste in a real device, but it works well in the simulators.

lecavallari avatar Dec 31 '20 13:12 lecavallari

I'm having a similar problem. It must be a problem with my xcode setup? Here's what mine looks like Screenshot 2020-12-31 at 22 34 14 Screenshot 2020-12-31 at 22 34 09

boxabirds avatar Dec 31 '20 22:12 boxabirds

@lecavallari I have similar setup as @boxabirds

I have tested on iOS 13 and it works. So it does not work only on the latest iOS version

@KasemJaffer do you confirm the bug? are you planning to work on it ?

maciej-szulc avatar Jan 04 '21 14:01 maciej-szulc

FYI, I just tried running the example app on my iPad with OS 14 and it runs fine. So might be implementation issue.

blackmenthor avatar Jan 07 '21 14:01 blackmenthor

Very likely. What’s the issue though? It’s proving elusive.

On Thu, 7 Jan 2021 at 14:23, Angga Dwi Arifandi [email protected] wrote:

FYI, I just tried running the example app on my iPad with OS 14 and it runs fine. So might be implementation issue.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/KasemJaffer/receive_sharing_intent/issues/116#issuecomment-756147229, or unsubscribe https://github.com/notifications/unsubscribe-auth/AABD62JUXQCG53OF77JXLG3SYW7XZANCNFSM4VHBZTIQ .

boxabirds avatar Jan 07 '21 14:01 boxabirds

Very likely. What’s the issue though? It’s proving elusive. On Thu, 7 Jan 2021 at 14:23, Angga Dwi Arifandi @.***> wrote: FYI, I just tried running the example app on my iPad with OS 14 and it runs fine. So might be implementation issue. — You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub <#116 (comment)>, or unsubscribe https://github.com/notifications/unsubscribe-auth/AABD62JUXQCG53OF77JXLG3SYW7XZANCNFSM4VHBZTIQ .

Basically on iOS 14.0 when app is selected via the share sheet, it does nothing (not opening the app at all). I'm currently investigating it.

blackmenthor avatar Jan 07 '21 14:01 blackmenthor

Yes agreed. Sorry I meant what is the cause …

On Thu, 7 Jan 2021 at 14:51, Angga Dwi Arifandi [email protected] wrote:

Very likely. What’s the issue though? It’s proving elusive. … <#m_-112722070415764581_> On Thu, 7 Jan 2021 at 14:23, Angga Dwi Arifandi @.***> wrote: FYI, I just tried running the example app on my iPad with OS 14 and it runs fine. So might be implementation issue. — You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub <#116 (comment) https://github.com/KasemJaffer/receive_sharing_intent/issues/116#issuecomment-756147229>, or unsubscribe https://github.com/notifications/unsubscribe-auth/AABD62JUXQCG53OF77JXLG3SYW7XZANCNFSM4VHBZTIQ .

Basically on iOS 14.0 when app is selected via the share sheet, it does nothing (not opening the app at all). I'm currently investigating it.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/KasemJaffer/receive_sharing_intent/issues/116#issuecomment-756162895, or unsubscribe https://github.com/notifications/unsubscribe-auth/AABD62LZQPPPGN4MJW44RXTSYXC53ANCNFSM4VHBZTIQ .

boxabirds avatar Jan 07 '21 15:01 boxabirds

Yes agreed. Sorry I meant what is the cause … On Thu, 7 Jan 2021 at 14:51, Angga Dwi Arifandi [email protected] wrote: Very likely. What’s the issue though? It’s proving elusive. … <#m_-112722070415764581_> On Thu, 7 Jan 2021 at 14:23, Angga Dwi Arifandi @.***> wrote: FYI, I just tried running the example app on my iPad with OS 14 and it runs fine. So might be implementation issue. — You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub <#116 (comment) <#116 (comment)>>, or unsubscribe https://github.com/notifications/unsubscribe-auth/AABD62JUXQCG53OF77JXLG3SYW7XZANCNFSM4VHBZTIQ . Basically on iOS 14.0 when app is selected via the share sheet, it does nothing (not opening the app at all). I'm currently investigating it. — You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub <#116 (comment)>, or unsubscribe https://github.com/notifications/unsubscribe-auth/AABD62LZQPPPGN4MJW44RXTSYXC53ANCNFSM4VHBZTIQ .

Alright finally found the solution, this is what worked for me

  1. Upgrade to the latest version of the package (previously I was using 1.4.0+2, now I upgrade to 1.4.2 (don't forget to run flutter pub upgrade)
  2. Because I have 2 flavor setup on my project (dev and prod), I use this code below as a bundle identifier
var hostAppBundleIdentifier = Bundle.main.bundleIdentifier?
        .replacingOccurrences(of: ".share-extension", with: "")

turns out it can be null at times, so I need to add a default value in case of null. 3. Previously the order of initialization on the Flutter side was textStream then mediaStream, when I reversed it suddenly it's working.

Before

// For sharing or opening urls/text coming from outside the app while the app is in the memory
    _textStreamSubscription = ReceiveSharingIntent.getTextStream().listen(
      _onSharedText,
    );

    // For sharing or opening urls/text coming from outside the app while the app is closed
    ReceiveSharingIntent.getInitialText().then(_onSharedText);

// For sharing or opening files coming from outside the app while the app is in the memory
    _mediaStreamSubscription = ReceiveSharingIntent.getMediaStream().listen(
      _onSharedMedia,
    );

    // For sharing or opening media coming from outside the app while the app is closed
    ReceiveSharingIntent.getInitialMedia().then(_onSharedMedia);

After

// For sharing or opening files coming from outside the app while the app is in the memory
    _mediaStreamSubscription = ReceiveSharingIntent.getMediaStream().listen(
      _onSharedMedia,
    );

    // For sharing or opening media coming from outside the app while the app is closed
    ReceiveSharingIntent.getInitialMedia().then(_onSharedMedia);
// For sharing or opening urls/text coming from outside the app while the app is in the memory
    _textStreamSubscription = ReceiveSharingIntent.getTextStream().listen(
      _onSharedText,
    );

    // For sharing or opening urls/text coming from outside the app while the app is closed
    ReceiveSharingIntent.getInitialText().then(_onSharedText);

After I did steps above, it started to work. I hope that helps someone.

blackmenthor avatar Jan 07 '21 15:01 blackmenthor

I'm facing same issue in iOS the app open after post text but stream never listen any thing even null not show

niypoo avatar Jan 11 '21 11:01 niypoo

Hi, are there any updates? I did the full iOS configuration (Share extension, app group, provisioning profiles, ...). My project runs but when I share text or media my app is launched but the value is null. Is there an implementation issue in the ShareViewController file?

This is how my ShareViewController looks like: `

//
//  ShareViewController.swift
//  Share Extension
//
//  Created by Jordan de Vogelaere on 11/01/2021.
//  Copyright © 2021 The Chromium Authors. All rights reserved.
//

import UIKit
import Social
import MobileCoreServices
import Photos

class ShareViewController: SLComposeServiceViewController {
    // TODO: IMPORTANT: This should be your host app bundle identifier
    let hostAppBundleIdentifier = Bundle.main.bundleIdentifier?
        .replacingOccurrences(of: ".Share-Extension", with: "")
    let sharedKey = "ShareKey"
    var sharedMedia: [SharedMediaFile] = []
    var sharedText: [String] = []
    let imageContentType = kUTTypeImage as String
    let videoContentType = kUTTypeMovie as String
    let textContentType = kUTTypeText as String
    let urlContentType = kUTTypeURL as String
    let fileURLType = kUTTypeFileURL as String;
    
    override func isContentValid() -> Bool {
        return true
    }
    
    override func viewDidLoad() {
        // 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(imageContentType) {
                        handleImages(content: content, attachment: attachment, index: index)
                    } else if attachment.hasItemConformingToTypeIdentifier(textContentType) {
                        handleText(content: content, attachment: attachment, index: index)
                    } else if attachment.hasItemConformingToTypeIdentifier(fileURLType) {
                        handleFiles(content: content, attachment: attachment, index: index)
                    } else if attachment.hasItemConformingToTypeIdentifier(urlContentType) {
                        handleUrl(content: content, attachment: attachment, index: index)
                    } else if attachment.hasItemConformingToTypeIdentifier(videoContentType) {
                        handleVideos(content: content, attachment: attachment, index: index)
                    }
                }
            }
        }
    }
    
    override func didSelectPost() {
        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 []
    }
    
    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: "group.\(this.hostAppBundleIdentifier)")
                    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: "group.\(this.hostAppBundleIdentifier)")
                    userDefaults?.set(this.sharedText, forKey: this.sharedKey)
                    userDefaults?.synchronize()
                    this.redirectToHostApp(type: .text)
                }
                
            } else {
                self?.dismissWithError()
            }
        }
    }
    
    private func handleImages (content: NSExtensionItem, attachment: NSItemProvider, index: Int) {
        attachment.loadItem(forTypeIdentifier: imageContentType, options: nil) { [weak self] data, error in
            
            if error == nil, let url = data as? URL, let this = self {
                
                // Always copy
                let fileName = this.getFileName(from: url, type: .image)
                let newPath = FileManager.default
                    .containerURL(forSecurityApplicationGroupIdentifier: "group.\(this.hostAppBundleIdentifier)")!
                    .appendingPathComponent(fileName)
                let copied = this.copyFile(at: url, to: newPath)
                if(copied) {
                    this.sharedMedia.append(SharedMediaFile(path: newPath.absoluteString, thumbnail: nil, duration: nil, type: .image))
                }
                
                // 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: "group.\(this.hostAppBundleIdentifier)")
                    userDefaults?.set(this.toData(data: this.sharedMedia), forKey: this.sharedKey)
                    userDefaults?.synchronize()
                    this.redirectToHostApp(type: .media)
                }
                
            } else {
                self?.dismissWithError()
            }
        }
    }
    
    private func handleVideos (content: NSExtensionItem, attachment: NSItemProvider, index: Int) {
        attachment.loadItem(forTypeIdentifier: videoContentType, options: nil) { [weak self] data, error in
            
            if error == nil, let url = data as? URL, let this = self {
                
                // Always copy
                let fileName = this.getFileName(from: url, type: .video)
                let newPath = FileManager.default
                    .containerURL(forSecurityApplicationGroupIdentifier: "group.\(this.hostAppBundleIdentifier)")!
                    .appendingPathComponent(fileName)
                let copied = this.copyFile(at: url, to: newPath)
                if(copied) {
                    guard let sharedFile = this.getSharedMediaFile(forVideo: newPath) else {
                        return
                    }
                    this.sharedMedia.append(sharedFile)
                }
                
                // 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: "group.\(this.hostAppBundleIdentifier)")
                    userDefaults?.set(this.toData(data: this.sharedMedia), forKey: this.sharedKey)
                    userDefaults?.synchronize()
                    this.redirectToHostApp(type: .media)
                }
                
            } else {
                self?.dismissWithError()
            }
        }
    }
    
    private func handleFiles (content: NSExtensionItem, attachment: NSItemProvider, index: Int) {
        attachment.loadItem(forTypeIdentifier: fileURLType, options: nil) { [weak self] data, error in
            
            if error == nil, let url = data as? URL, let this = self {
                
                // Always copy
                let fileName = this.getFileName(from :url, type: .file)
                let newPath = FileManager.default
                    .containerURL(forSecurityApplicationGroupIdentifier: "group.\(this.hostAppBundleIdentifier)")!
                    .appendingPathComponent(fileName)
                let copied = this.copyFile(at: url, to: newPath)
                if (copied) {
                    this.sharedMedia.append(SharedMediaFile(path: newPath.absoluteString, thumbnail: nil, duration: nil, type: .file))
                }
                
                if index == (content.attachments?.count)! - 1 {
                    let userDefaults = UserDefaults(suiteName: "group.\(this.hostAppBundleIdentifier)")
                    userDefaults?.set(this.toData(data: this.sharedMedia), forKey: this.sharedKey)
                    userDefaults?.synchronize()
                    this.redirectToHostApp(type: .file)
                }
                
            } 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) {
        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"
    }
    
    func getFileName(from url: URL, type: SharedMediaType) -> String {
        var name = url.lastPathComponent
        
        if (name.isEmpty) {
            name = UUID().uuidString + "." + getExtension(from: url, type: type)
        }
        
        return name
    }
    
    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 getSharedMediaFile(forVideo: URL) -> SharedMediaFile? {
        let asset = AVAsset(url: forVideo)
        let duration = (CMTimeGetSeconds(asset.duration) * 1000).rounded()
        let thumbnailPath = getThumbnailPath(for: forVideo)
        
        if FileManager.default.fileExists(atPath: thumbnailPath.path) {
            return SharedMediaFile(path: forVideo.absoluteString, thumbnail: thumbnailPath.absoluteString, duration: duration, type: .video)
        }
        
        var saved = false
        let assetImgGenerate = AVAssetImageGenerator(asset: asset)
        assetImgGenerate.appliesPreferredTrackTransform = true
        //        let scale = UIScreen.main.scale
        assetImgGenerate.maximumSize =  CGSize(width: 360, height: 360)
        do {
            let img = try assetImgGenerate.copyCGImage(at: CMTimeMakeWithSeconds(600, preferredTimescale: Int32(1.0)), actualTime: nil)
            try UIImage.pngData(UIImage(cgImage: img))()?.write(to: thumbnailPath)
            saved = true
        } catch {
            saved = false
        }
        
        return saved ? SharedMediaFile(path: forVideo.absoluteString, thumbnail: thumbnailPath.absoluteString, duration: duration, type: .video) : nil
        
    }
    
    private func getThumbnailPath(for url: URL) -> URL {
        let fileName = Data(url.lastPathComponent.utf8).base64EncodedString().replacingOccurrences(of: "==", with: "")
        let path = FileManager.default
            .containerURL(forSecurityApplicationGroupIdentifier: "group.\(hostAppBundleIdentifier)")!
            .appendingPathComponent("\(fileName).jpg")
        return path
    }
    
    class SharedMediaFile: Codable {
        var path: String; // can be image, video or url path. It can also be text content
        var thumbnail: String?; // video thumbnail
        var duration: Double?; // video duration in milliseconds
        var type: SharedMediaType;
        
        
        init(path: String, thumbnail: String?, duration: Double?, type: SharedMediaType) {
            self.path = path
            self.thumbnail = thumbnail
            self.duration = duration
            self.type = type
        }
        
        // Debug method to print out SharedMediaFile details in the console
        func toString() {
            print("[SharedMediaFile] \n\tpath: \(self.path)\n\tthumbnail: \(self.thumbnail)\n\tduration: \(self.duration)\n\ttype: \(self.type)")
        }
    }
    
    enum SharedMediaType: Int, Codable {
        case image
        case video
        case file
    }
    
    func toData(data: [SharedMediaFile]) -> Data {
        let encodedData = try? JSONEncoder().encode(data)
        return encodedData!
    }
}

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


`

jdevogelaere-rmdy avatar Jan 12 '21 15:01 jdevogelaere-rmdy

any updates !!!

niypoo avatar Jan 13 '21 13:01 niypoo

i have the same problem

piotrseed avatar Jan 14 '21 13:01 piotrseed

Having same problem on iOS. I found out, that ShareViewController.swift will not get executed at all. I tried to add several Breakpoints in XCode and nearly every function and nothing was reached here during sharing-process.

Is something else required in order to bundle ShareViewController.swift really with the Extension?

itinance avatar Jan 18 '21 00:01 itinance

I finally made it work after applying all these steps (only 2 of these hints where missing at my side): https://github.com/KasemJaffer/receive_sharing_intent/issues/82#issuecomment-686332264

However, wenn I share text from Safari this way into my flutter app, the Safari browser will stay in somehow modal thing that the whole app can't be used anymore and needs to be killed.

itinance avatar Jan 18 '21 00:01 itinance

I finally made it work after applying all these steps (only 2 of these hints where missing at my side): #82 (comment)

However, wenn I share text from Safari this way into my flutter app, the Safari browser will stay in somehow modal thing that the whole app can't be used anymore and needs to be killed.

not working for me

niypoo avatar Jan 23 '21 13:01 niypoo

After the last update of library it started working.

codigee avatar Jan 27 '21 07:01 codigee

For me problems started when upgrading from 1.4.2 to 1.4.3. The app opened on a sharing intent, but nothing was shared. A simple pubspec change fixed it: receive_sharing_intent: ^1.4.3 to receive_sharing_intent: 1.4.2 (mind the missing ^, so it's fixed to 1.4.2)

woutervanwijk avatar Feb 01 '21 13:02 woutervanwijk

Not working for me. Android works fine, but iOS is not receiving the Intent

FranWeidt avatar Feb 05 '21 15:02 FranWeidt

Me too , still not working

niypoo avatar Feb 05 '21 21:02 niypoo

It could be the last patch that was merged, see https://github.com/KasemJaffer/receive_sharing_intent/pull/117#issuecomment-773196108

Problem is I guess that hasMatchingSchemePrefix blocks all shared text. If I remove that or I remove the "ShareMedia" from line 10: private var customSchemePrefix = "";

it works again. But with these changes, sometimes if I share an url from Safari, Safari crashes #24

This behaviour was not in 1.4.2

woutervanwijk avatar Feb 06 '21 11:02 woutervanwijk

It could be the last patch that was merged, see #117 (comment)

Problem is I guess that hasMatchingSchemePrefix blocks all shared text. If I remove that or I remove the "ShareMedia" from line 10: private var customSchemePrefix = "";

it works again. But with these changes, sometimes if I share an url from Safari, Safari crashes #24

This behaviour was not in 1.4.2

I have downgrade to 1.4.2 and still doesn't work , my app open but nothing received.

niypoo avatar Feb 07 '21 15:02 niypoo

@woutervanwijk I don't know if this the cause of your particular issue on the latest version but I do think that I introduced a bug in my previous PR that I have addressed here: https://github.com/KasemJaffer/receive_sharing_intent/pull/140

let me know if that ends up being your issue as well.

danReynolds avatar Feb 10 '21 05:02 danReynolds

any updates ??

niypoo avatar Feb 11 '21 22:02 niypoo

Have you created a separate provision profile for ShareExtension?

MXilian avatar Feb 12 '21 12:02 MXilian

I think it is depending on the source app (not your app, apps that should list up your app to share the content) I tried a number of apps including Facebook, Youtube and Linkedin etc, none of them is displaying my app to share the content, except google app. Even inside my app, I have a function to share my app content with other apps, I don't see my app on the app list to share the content (I'm using the share package). I guess Apple definitely has changed how the share intent list is fetched and displayed on the apps.

would be nice if someone can confirm this from Apple's changelog somewhere.

MartinJLee avatar Mar 11 '21 12:03 MartinJLee

Possible fix, see #148

I had a similar issue (after opening the application, the stream was never updated). I found that my issue was made of using another library, that requires calling application(_:open:options:) in the AppDelegate, which "muted" the function in this plugin. I have made some updates, which you can find in the pull-request. I hope it will help you fix your problem.

In the pull-request, there is also included the fixing of an issue when on iOS this plugin was opening the incorrect application.

czernas avatar Mar 16 '21 21:03 czernas