carsten-klaffke / send-intent

Repository for send-intent Capacitor plugin
MIT License
104 stars 12 forks source link

Sharing photo to app in iOS with capacitor v4 #66

Open j-d-carmichael opened 1 year ago

j-d-carmichael commented 1 year ago

I just built an app in capacitor v4 - sharing links, web-urls and photos works flawlessly in Android.

In iOS text and web-urls work fine, but sharing a photo I cannot get to work in v4.

Setup:

The shareintent plugin info.plist has been updated to:

                <key>NSExtensionAttributes</key>
        <dict>
            <key>NSExtensionActivationRule</key>
            <dict>
                <key>NSExtensionActivationSupportsFileWithMaxCount</key>
                    <integer>1</integer>
                    <key>NSExtensionActivationSupportsImageWithMaxCount</key>
                    <integer>1</integer>
                    <key>NSExtensionActivationSupportsMovieWithMaxCount</key>
                    <integer>5</integer>
                    <key>NSExtensionActivationSupportsText</key>
                    <true/>
                    <key>NSExtensionActivationSupportsWebPageWithMaxCount</key>
                    <integer>1</integer>
                    <key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
                    <integer>1</integer>
                    <key>NSExtensionActivationUsesStrictMatching</key>
                    <false/>
            </dict>
        </dict>

The result

Sharing links and text to the ios app works perfectly. Sharing a photo (not a even a live photo) does not work.

The capacitor app does not load, instead the photos app being share from sort of animates up about 4mm on the screen, then sort of "gives up". In xcode there are no error messages that are consistent... but sometimes (I think it is related) I get this error in xcode:

"2022-12-16 10:06:29.618067+0000 App[15668:493451] [Snapshotting] Snapshotting a view (0x10a00d600, UIKeyboardImpl) that has not been rendered at least once requires afterScreenUpdates:YES."

carsten-klaffke commented 1 year ago

I did not experience this behavior so far and I don't see that anything is wrong with your configuration. In Xcode, have you tried running the plugin directly and not your app? This might give better logging output as the share plugin is a little extra app.

wescarr commented 1 year ago

@j-d-carmichael I had a similar issue to this previously and after some debugging noticed that I did not the have the App Group entitlement set correctly. You need to add this in the Signing & Capabilities tab. FWIW, I added it both to the main App, and the Share Extension.

tarangshah19 commented 1 year ago

can i get example code base my app listed in share screen but when i try to click on share button nothing happend dont know why

[//
//  ShareViewController.swift
//  mindlib
//
//  Created by Carsten Klaffke on 05.07.20.
//

import MobileCoreServices
import Social
import UIKit

class ShareItem {

    public var title: String?
    public var type: String?
    public var url: String?
}

class ShareViewController: UIViewController {

    private var shareItems: [ShareItem] = []

    override public func viewDidAppear(_ animated: Bool) {
       super.viewDidAppear(animated)
       self.extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
    }

    private func sendData() {
        let queryItems = shareItems.map {
            [
                URLQueryItem(
                    name: "title",
                    value: $0.title?.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) ?? ""),
                URLQueryItem(name: "description", value: ""),
                URLQueryItem(
                    name: "type",
                    value: $0.type?.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) ?? ""),
                URLQueryItem(
                    name: "url",
                    value: $0.url?.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) ?? ""),
            ]
        }.flatMap({ $0 })
        print("SomeString", queryItems)

        var urlComps = URLComponents(string: "localhost://")!
        urlComps.queryItems = queryItems
        openURL(urlComps.url!)

        print("SomeString", urlComps)
    }

    fileprivate func createSharedFileUrl(_ url: URL?) -> String {
        let fileManager = FileManager.default

        let copyFileUrl =
        fileManager.containerURL(forSecurityApplicationGroupIdentifier: "group.com.imeuswe.app")!
            .absoluteString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)! + "/" + url!
            .lastPathComponent.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
        try? Data(contentsOf: url!).write(to: URL(string: copyFileUrl)!)

        return copyFileUrl
    }

    func saveScreenshot(_ image: UIImage) -> String {
        let fileManager = FileManager.default

        let copyFileUrl =
        fileManager.containerURL(forSecurityApplicationGroupIdentifier: "group.com.imeuswe.app")!
            .absoluteString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
        + "/screenshot.png"
        do {
            try image.pngData()?.write(to: URL(string: copyFileUrl)!)
            return copyFileUrl
        } catch {
            print(error.localizedDescription)
            return ""
        }
    }

    fileprivate func handleTypeUrl(_ attachment: NSItemProvider)
    async throws -> ShareItem
    {
        let results = try await attachment.loadItem(forTypeIdentifier: kUTTypeURL as String, options: nil)
        let url = results as! URL?
        let shareItem: ShareItem = ShareItem()

        if url!.isFileURL {
            shareItem.title = url!.lastPathComponent
            shareItem.type = "application/" + url!.pathExtension.lowercased()
            shareItem.url = createSharedFileUrl(url)
        } else {
            shareItem.title = url!.absoluteString
            shareItem.url = url!.absoluteString
            shareItem.type = "text/plain"
        }

        return shareItem
    }

    fileprivate func handleTypeText(_ attachment: NSItemProvider)
    async throws -> ShareItem
    {
        let results = try await attachment.loadItem(forTypeIdentifier: kUTTypeText as String, options: nil)
        let shareItem: ShareItem = ShareItem()
        let text = results as! String
        shareItem.title = text
        shareItem.type = "text/plain"
        return shareItem
    }

    fileprivate func handleTypeMovie(_ attachment: NSItemProvider)
    async throws -> ShareItem
    {
        let results = try await attachment.loadItem(forTypeIdentifier: kUTTypeMovie as String, options: nil)
        let shareItem: ShareItem = ShareItem()

        let url = results as! URL?
        shareItem.title = url!.lastPathComponent
        shareItem.type = "video/" + url!.pathExtension.lowercased()
        shareItem.url = createSharedFileUrl(url)
        return shareItem
    }

    fileprivate func handleTypeImage(_ attachment: NSItemProvider)
    async throws -> ShareItem
    {
        let data = try await attachment.loadItem(forTypeIdentifier: kUTTypeImage as String, options: nil)

        let shareItem: ShareItem = ShareItem()
        switch data {
        case let image as UIImage:
            shareItem.title = "screenshot"
            shareItem.type = "image/png"
            shareItem.url = self.saveScreenshot(image)
        case let url as URL:
            shareItem.title = url.lastPathComponent
            shareItem.type = "image/" + url.pathExtension.lowercased()
            shareItem.url = self.createSharedFileUrl(url)
        default:
            print("Unexpected image data:", type(of: data))
        }
        return shareItem
    }

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

        shareItems.removeAll()

        let extensionItem = extensionContext?.inputItems[0] as! NSExtensionItem
        Task {
            try await withThrowingTaskGroup(
                of: ShareItem.self,
                body: { taskGroup in

                    for attachment in extensionItem.attachments! {
                        if attachment.hasItemConformingToTypeIdentifier(kUTTypeURL as String) {
                            taskGroup.addTask {
                                return try await self.handleTypeUrl(attachment)
                            }
                        } else if attachment.hasItemConformingToTypeIdentifier(kUTTypeText as String) {
                            taskGroup.addTask {
                                return try await self.handleTypeText(attachment)
                            }
                        } else if attachment.hasItemConformingToTypeIdentifier(kUTTypeMovie as String) {
                            taskGroup.addTask {
                                return try await self.handleTypeMovie(attachment)
                            }
                        } else if attachment.hasItemConformingToTypeIdentifier(kUTTypeImage as String) {
                            taskGroup.addTask {
                                return try await self.handleTypeImage(attachment)
                            }
                        }
                    }

                    for try await item in taskGroup {
                        self.shareItems.append(item)
                    }
                })

            self.sendData()

        }
    }

    @objc 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
    }

}](url)
alessio-libardi-zupit commented 1 year ago

Thanks, @wescarr, you saved me, adding the App Group to the Signing & Capabilities tab of both my app and of the share extension fixed the issue for me! 🚀

carsten-klaffke commented 1 year ago

Hey guys, I recently added an example project for Android and iOS (/Example/SendIntentExample). Maybe this is of help for you.