-
Notifications
You must be signed in to change notification settings - Fork 242
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support "No Share Extension UI" flow on iOS #285
Comments
Hi, |
Hi @skizzo, at the moment we're working on wrapping up another project but should get started on this soon. |
Hey, is there an update on this? :) |
Hello, is there an update on this? :) |
I don't have a good solution but I have a solution. I followed the ios instructions and at the bottom of the Though, one downside I noticed is that if you wait for some time, then navigate back to the previous app, the social media post view does show. Still working on a better solution. |
Okay, I have a nice clean solution now. I have not tested it with much though. I really just needed a way for my app to show when I share a website and then have my app accept the URL and do stuff with it. I haven't tested any other scenarios like sharing text, images, files, or anything else.
// This file comes from the react-native-share-menu package.
// ./node_modules/react-native-share-menu/ios/ShareViewController.swift
import Foundation
import MobileCoreServices
import UIKit
import Social
import RNShareMenu
class ShareViewController: UIViewController {
var hostAppId: String?
var hostAppUrlScheme: String?
var sharedItems: [Any] = []
override func viewDidLoad() {
super.viewDidLoad()
if let hostAppId = Bundle.main.object(forInfoDictionaryKey: HOST_APP_IDENTIFIER_INFO_PLIST_KEY) as? String {
self.hostAppId = hostAppId
} else {
print("Error: \(NO_INFO_PLIST_INDENTIFIER_ERROR)")
}
if let hostAppUrlScheme = Bundle.main.object(forInfoDictionaryKey: HOST_URL_SCHEME_INFO_PLIST_KEY) as? String {
self.hostAppUrlScheme = hostAppUrlScheme
} else {
print("Error: \(NO_INFO_PLIST_URL_SCHEME_ERROR)")
}
// This is called after the user selects Post. Do the upload of contentText and/or NSExtensionContext attachments.
guard let items = extensionContext?.inputItems as? [NSExtensionItem] else {
cancelRequest()
return
}
handlePost(items)
}
func handlePost(_ items: [NSExtensionItem], extraData: [String:Any]? = nil) {
DispatchQueue.global().async {
guard let hostAppId = self.hostAppId else {
self.exit(withError: NO_INFO_PLIST_INDENTIFIER_ERROR)
return
}
guard let userDefaults = UserDefaults(suiteName: "group.\(hostAppId)") else {
self.exit(withError: NO_APP_GROUP_ERROR)
return
}
if let data = extraData {
self.storeExtraData(data)
} else {
self.removeExtraData()
}
let semaphore = DispatchSemaphore(value: 0)
var results: [Any] = []
for item in items {
guard let attachments = item.attachments else {
self.cancelRequest()
return
}
for provider in attachments {
if provider.isText {
self.storeText(withProvider: provider, semaphore)
} else if provider.isURL {
self.storeUrl(withProvider: provider, semaphore)
} else {
self.storeFile(withProvider: provider, semaphore)
}
semaphore.wait()
}
}
userDefaults.set(self.sharedItems,
forKey: USER_DEFAULTS_KEY)
userDefaults.synchronize()
self.openHostApp()
}
}
func storeExtraData(_ data: [String:Any]) {
guard let hostAppId = self.hostAppId else {
print("Error: \(NO_INFO_PLIST_INDENTIFIER_ERROR)")
return
}
guard let userDefaults = UserDefaults(suiteName: "group.\(hostAppId)") else {
print("Error: \(NO_APP_GROUP_ERROR)")
return
}
userDefaults.set(data, forKey: USER_DEFAULTS_EXTRA_DATA_KEY)
userDefaults.synchronize()
}
func removeExtraData() {
guard let hostAppId = self.hostAppId else {
print("Error: \(NO_INFO_PLIST_INDENTIFIER_ERROR)")
return
}
guard let userDefaults = UserDefaults(suiteName: "group.\(hostAppId)") else {
print("Error: \(NO_APP_GROUP_ERROR)")
return
}
userDefaults.removeObject(forKey: USER_DEFAULTS_EXTRA_DATA_KEY)
userDefaults.synchronize()
}
func storeText(withProvider provider: NSItemProvider, _ semaphore: DispatchSemaphore) {
provider.loadItem(forTypeIdentifier: kUTTypeText as String, options: nil) { (data, error) in
guard (error == nil) else {
self.exit(withError: error.debugDescription)
return
}
guard let text = data as? String else {
self.exit(withError: COULD_NOT_FIND_STRING_ERROR)
return
}
self.sharedItems.append([DATA_KEY: text, MIME_TYPE_KEY: "text/plain"])
semaphore.signal()
}
}
func storeUrl(withProvider provider: NSItemProvider, _ semaphore: DispatchSemaphore) {
provider.loadItem(forTypeIdentifier: kUTTypeURL as String, options: nil) { (data, error) in
guard (error == nil) else {
self.exit(withError: error.debugDescription)
return
}
guard let url = data as? URL else {
self.exit(withError: COULD_NOT_FIND_URL_ERROR)
return
}
self.sharedItems.append([DATA_KEY: url.absoluteString, MIME_TYPE_KEY: "text/plain"])
semaphore.signal()
}
}
func storeFile(withProvider provider: NSItemProvider, _ semaphore: DispatchSemaphore) {
provider.loadItem(forTypeIdentifier: kUTTypeData as String, options: nil) { (data, error) in
guard (error == nil) else {
self.exit(withError: error.debugDescription)
return
}
guard let url = data as? URL else {
self.exit(withError: COULD_NOT_FIND_IMG_ERROR)
return
}
guard let hostAppId = self.hostAppId else {
self.exit(withError: NO_INFO_PLIST_INDENTIFIER_ERROR)
return
}
guard let groupFileManagerContainer = FileManager.default
.containerURL(forSecurityApplicationGroupIdentifier: "group.\(hostAppId)")
else {
self.exit(withError: NO_APP_GROUP_ERROR)
return
}
let mimeType = url.extractMimeType()
let fileExtension = url.pathExtension
let fileName = UUID().uuidString
let filePath = groupFileManagerContainer
.appendingPathComponent("\(fileName).\(fileExtension)")
guard self.moveFileToDisk(from: url, to: filePath) else {
self.exit(withError: COULD_NOT_SAVE_FILE_ERROR)
return
}
self.sharedItems.append([DATA_KEY: filePath.absoluteString, MIME_TYPE_KEY: mimeType])
semaphore.signal()
}
}
func moveFileToDisk(from srcUrl: URL, to destUrl: URL) -> Bool {
do {
if FileManager.default.fileExists(atPath: destUrl.path) {
try FileManager.default.removeItem(at: destUrl)
}
try FileManager.default.copyItem(at: srcUrl, to: destUrl)
} catch (let error) {
print("Could not save file from \(srcUrl) to \(destUrl): \(error)")
return false
}
return true
}
func exit(withError error: String) {
print("Error: \(error)")
cancelRequest()
}
internal func openHostApp() {
guard let urlScheme = self.hostAppUrlScheme else {
exit(withError: NO_INFO_PLIST_URL_SCHEME_ERROR)
return
}
let url = URL(string: urlScheme)
let selectorOpenURL = sel_registerName("openURL:")
var responder: UIResponder? = self
while responder != nil {
if responder?.responds(to: selectorOpenURL) == true {
responder?.perform(selectorOpenURL, with: url)
}
responder = responder!.next
}
completeRequest()
}
func completeRequest() {
// Inform the host that we're done, so it un-blocks its UI. Note: Alternatively you could call super's -didSelectPost, which will similarly complete the extension context.
extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
}
func cancelRequest() {
extensionContext!.cancelRequest(withError: NSError())
}
}
If you are curious what I changed, it was not much. The original was extending a subclass I also removed some override functions ( Finally, I added a call to |
In this ticket, we'll add support for the option to make iOS behave like Android: navigate immediately into the main app to share, without any visible share extension UI. (The share extension will still exist and run; it will just immediately link to the main app without showing any UI, using the linking logic already present in the library).
Scope:
UIViewController
(we don't want to display the compose view fromSLComposeServiceViewController
), and we'll immediately call the logic that currently lives inhandlePost
onShareViewController
.The text was updated successfully, but these errors were encountered: