skip to Main Content

I am working on a shareExtension for an app which should consume share-links from Spotify & Apple Music. I have set NSExtensionActivationSupportsWebURLWithMaxCount and NSExtensionActivationSupportsWebPageWithMaxCount to 1 in the Info.plist.

When I am trying to share a song from Spotify/ Apple Music, my extension is in the List of availible targets. When I am trying to share a playlist, it does not show up.

I tried to allow other "supporting" types like file attachments or images, but it did not work.

Any suggesstions what to look out for?

My current Info.plist

<?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">
<plist version="1.0">
<dict>
    <key>NSExtension</key>
    <dict>
        <key>NSExtensionAttributes</key>
        <dict>
            <key>NSExtensionActivationRule</key>
            <dict>
                <key>NSExtensionActivationSupportsMovieWithMaxCount</key>
                <integer>100</integer>
                <key>NSExtensionActivationSupportsImageWithMaxCount</key>
                <integer>100</integer>
                <key>NSExtensionActivationUsesStrictMatching</key>
                <false/>
                <key>NSExtensionActivationSupportsFileWithMaxCount</key>
                <integer>100</integer>
                <key>NSExtensionActivationSupportsAttachmentsWithMaxCount</key>
                <integer>100</integer>
                <key>NSExtensionActivationSupportsWebPageWithMaxCount</key>
                <integer>100</integer>
                <key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
                <integer>100</integer>
            </dict>
        </dict>
        <key>NSExtensionPointIdentifier</key>
        <string>com.apple.share-services</string>
        <key>NSExtensionPrincipalClass</key>
        <string>ShareViewController</string>
    </dict>
</dict>
</plist>

My current ShareViewController.swift

@objc(ShareViewController)
class ShareViewController: SLComposeServiceViewController {

    let URL_TYPE = "public.url"
    let TEXT_TYPE = "public.text"
    let SHARED_SUITE_NAME = "xxx"
    var rootView: ShareView?
    var playlists: DtoPlaylists?
    var shareURL: DtoShareURL?

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

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

    /**
     * Called after the controller's view is loaded into memory
     */
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .clear

        self.modalPresentationStyle = .fullScreen

        initSwiftUIView()
    }

    /**
     * Notifies the view controller that its view is about to be added to a view hierarchy
     - Parameter animated: If true, the view is being added to the window using an animation
     */
    override func viewWillAppear(_ animated: Bool) {
        _ = updatePlaylistsFromDefaults()

        let extensionItem = extensionContext?.inputItems[0] as! NSExtensionItem
        for attachment in extensionItem.attachments! {
            if attachment.hasItemConformingToTypeIdentifier(TEXT_TYPE) {
                attachment.loadItem(forTypeIdentifier: TEXT_TYPE, options: nil, completionHandler: { (results, error) in
                    if let sharedText = results as! String? {
                        self.shareURL?.shareURL = sharedText
                    }
                })
            } else if attachment.hasItemConformingToTypeIdentifier(URL_TYPE) {
                attachment.loadItem(forTypeIdentifier: URL_TYPE, options: nil, completionHandler: { (results, error) in
                    if let sharedURL = results as! URL? {
                        self.shareURL?.shareURL = sharedURL.absoluteString
                    }
                })
            }
        }
    }

    @objc func cancelAction() {
        let error = NSError(domain: "xxx", code: 0, userInfo: [NSLocalizedDescriptionKey: "User aborted request"])
        extensionContext?.cancelRequest(withError: error)
    }

    /**
     * renders the SwiftUI view and attatches it to the viewStack
     */
    private func initSwiftUIView() {
        let decodedPlaylist = updatePlaylistsFromDefaults()

        playlists = DtoPlaylists.init(playlists: decodedPlaylist)
        // TODO remove this placeholder
        shareURL = DtoShareURL.init(shareURL: "PLACEHOLDER")
        rootView = ShareView(closeAction: cancelAction, submit: didSelectPost, playlists: playlists!, shareURL: shareURL!, persistPlaylistInApp: persistPlaylistInApp)
        let controller = UIHostingController(rootView: rootView)
        controller.view.backgroundColor = .blue
        addChild(controller)
        controller.view.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(controller.view)
        controller.didMove(toParent: self)

        NSLayoutConstraint.activate([
            controller.view.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 1.0),
            controller.view.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 1.0),
            controller.view.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            controller.view.centerYAnchor.constraint(equalTo: view.centerYAnchor)
        ])
    }

    private func updatePlaylistsFromDefaults() -> [DtoPlaylist] {
        let defaults = UserDefaults(suiteName: SHARED_SUITE_NAME)
        var decodedPlaylist: [DtoPlaylist] = []
        let savedPlaylists = defaults!.data(forKey: "playlists")
        if savedPlaylists != nil {
            decodedPlaylist = try! JSONDecoder().decode([DtoPlaylist].self, from: savedPlaylists!)
        }
        return decodedPlaylist
    }

    override func didSelectPost() {
//         TODO is this method still needed?
    }

    /**
     * Opens the main app with a specific shareURL so the main app can import that
     */
    private func persistPlaylistInApp(shareURL: String) {
        // TODO remove example
        // TODO make sure to encode shareURL
        let url = URL(string: "xxx://APPLE_MUSIC?pl.2cc9c5f8896546ea9908743222a6d5fb")
        _ = openURL(url!)
    }

    // https://stackoverflow.com/a/44499222/13130130
    // Function must be named exactly like this so a selector can be found by the compiler!
    // Anyway - it's another selector in another instance that would be "performed" instead.
    @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
    }


}

2

Answers


  1. In principle what happens when you try to share a file from an app is tgat the system will look for all apps that can handle that kind of file. The system will launch your ShareExtension which will process the input.

    That means you have to make sure that your ShareExtension‘s NSExtensionActivationRule in the info.plist includes the required value.
    You could try adding NSExtensionActivationSupportsText as this should cover everything in your case.

    Login or Signup to reply.
  2. This worked for me:

        <key>NSExtension</key>
    <dict>
        <key>NSExtensionAttributes</key>
        <dict>
            <key>NSExtensionActivationRule</key>
            <dict>
                <!--Version 2 means we only have to match one (not all) of the things in the shared attachment-->
                <key>NSExtensionActivationDictionaryVersion</key>
                <integer>2</integer>
                <key>NSExtensionActivationUsesStrictMatching</key>
                <false/>
                <!--Had to put Max Count 3 in order for Apple Music playlist sharing to work-->             <key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
                <integer>3</integer>
                <key>NSExtensionActivationSupportsText</key>
                <true/>
                <!--Had to put Max Count 3 in order for Apple Music playlist sharing to work-->             <key>NSExtensionActivationSupportsAttachmentsWithMaxCount</key>
                <integer>3</integer>
            </dict>
        </dict>
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search