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
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
‘sNSExtensionActivationRule
in the info.plist includes the required value.You could try adding
NSExtensionActivationSupportsText
as this should cover everything in your case.This worked for me: