diff --git a/IOS_INSTRUCTIONS.md b/IOS_INSTRUCTIONS.md
index f3eb2fc2..2e204421 100644
--- a/IOS_INSTRUCTIONS.md
+++ b/IOS_INSTRUCTIONS.md
@@ -89,6 +89,8 @@ Repeat this process for the Share Extension target, with the exact same group na
Add the following to your app's `Info.plist` (if you already had other URL Schemes, make sure the one you're adding now is the FIRST one):
```OpenStep Property List
+AppGroup
+THE_APP_GROUP_CREATED_ABOVE
CFBundleURLTypes
@@ -106,11 +108,16 @@ Add the following to your app's `Info.plist` (if you already had other URL Schem
Add the following to your Share Extension's `Info.plist`:
```OpenStep Property List
+AppGroup
+THE_APP_GROUP_CREATED_ABOVE
+
+
+
NSExtension
NSExtensionAttributes
diff --git a/ios/Constants.swift b/ios/Constants.swift
index 08385b79..e1c50d17 100644
--- a/ios/Constants.swift
+++ b/ios/Constants.swift
@@ -12,15 +12,10 @@ public let DISMISS_SHARE_EXTENSION_WITH_ERROR_CODE = 1
public let NO_URL_TYPES_ERROR_MESSAGE = "You have not defined CFBundleURLTypes in your Info.plist"
public let NO_URL_SCHEMES_ERROR_MESSAGE = "You have not defined CFBundleURLSchemes in your Info.plist"
-public let NO_SCHEME_ERROR_MESSAGE = "You have not defined a scheme under CFBundleURLSchemes in your Info.plist"
public let NO_APP_GROUP_ERROR = "Failed to get App Group User Defaults. Did you set up an App Group on your App and Share Extension?"
-public let NO_INFO_PLIST_INDENTIFIER_ERROR = "You haven't defined \(HOST_APP_IDENTIFIER_INFO_PLIST_KEY) in your Share Extension's Info.plist"
-public let NO_INFO_PLIST_URL_SCHEME_ERROR = "You haven't defined \(HOST_URL_SCHEME_INFO_PLIST_KEY) in your Share Extension's Info.plist"
public let COULD_NOT_FIND_STRING_ERROR = "Couldn't find string"
public let COULD_NOT_FIND_URL_ERROR = "Couldn't find url"
public let COULD_NOT_FIND_IMG_ERROR = "Couldn't find image"
-public let COULD_NOT_PARSE_IMG_ERROR = "Couldn't parse image"
-public let COULD_NOT_SAVE_FILE_ERROR = "Couldn't save file on disk"
public let NO_EXTENSION_CONTEXT_ERROR = "No extension context attached"
public let NO_DELEGATE_ERROR = "No ReactShareViewDelegate attached"
public let COULD_NOT_FIND_ITEMS_ERROR = "Couldn't find items attached to this share"
@@ -32,6 +27,7 @@ public let USER_DEFAULTS_EXTRA_DATA_KEY = "ShareMenuUserDefaultsExtraData"
public let URL_SCHEME_INFO_PLIST_KEY = "AppURLScheme"
public let HOST_APP_IDENTIFIER_INFO_PLIST_KEY = "HostAppBundleIdentifier"
public let HOST_URL_SCHEME_INFO_PLIST_KEY = "HostAppURLScheme"
+public let APP_GROUP_KEY = "AppGroup"
public let REACT_SHARE_VIEW_BACKGROUND_COLOR_KEY = "ReactShareViewBackgroundColor"
public let COLOR_RED_KEY = "Red"
diff --git a/ios/Modules/ShareMenu.swift b/ios/Modules/ShareMenu.swift
index 74badda6..bc94b9a1 100644
--- a/ios/Modules/ShareMenu.swift
+++ b/ios/Modules/ShareMenu.swift
@@ -83,30 +83,42 @@ class ShareMenu: RCTEventEmitter {
}
guard let scheme = url.scheme, scheme == targetUrlScheme else { return }
- guard let bundleId = Bundle.main.bundleIdentifier else { return }
- guard let userDefaults = UserDefaults(suiteName: "group.\(bundleId)") else {
+ guard let group = getGroup() else {
print("Error: \(NO_APP_GROUP_ERROR)")
return
}
- let extraData = userDefaults.object(forKey: USER_DEFAULTS_EXTRA_DATA_KEY) as? [String:Any]
+ let userDefaults = UserDefaults(suiteName: group)
+ let extraData = userDefaults?.object(forKey: USER_DEFAULTS_EXTRA_DATA_KEY) as? [String:Any]
- if let data = userDefaults.object(forKey: USER_DEFAULTS_KEY) as? [[String:String]] {
+ if let data = userDefaults?.object(forKey: USER_DEFAULTS_KEY) as? [[String:String]] {
sharedData = data
dispatchEvent(with: data, and: extraData)
- userDefaults.removeObject(forKey: USER_DEFAULTS_KEY)
+ userDefaults?.removeObject(forKey: USER_DEFAULTS_KEY)
}
}
+
+ func getGroup() -> String? {
+ guard let group = Bundle.main.object(forInfoDictionaryKey: APP_GROUP_KEY) as? String else {
+ guard let bundleId = Bundle.main.bundleIdentifier else {
+ return nil
+ }
+ return "group.\(bundleId)"
+ }
+
+ return group
+ }
@objc(getSharedText:)
func getSharedText(callback: RCTResponseSenderBlock) {
var data = [DATA_KEY: sharedData] as [String: Any]
- if let bundleId = Bundle.main.bundleIdentifier, let userDefaults = UserDefaults(suiteName: "group.\(bundleId)") {
- data[EXTRA_DATA_KEY] = userDefaults.object(forKey: USER_DEFAULTS_EXTRA_DATA_KEY) as? [String: Any]
- } else {
+ guard let group = getGroup() else {
print("Error: \(NO_APP_GROUP_ERROR)")
+ return
}
+ let userDefaults = UserDefaults(suiteName: group)
+ data[EXTRA_DATA_KEY] = userDefaults?.object(forKey: USER_DEFAULTS_EXTRA_DATA_KEY) as? [String: Any]
callback([data as Any])
sharedData = []
diff --git a/ios/ShareViewController.swift b/ios/ShareViewController.swift
index 12d8c92d..40ce4429 100644
--- a/ios/ShareViewController.swift
+++ b/ios/ShareViewController.swift
@@ -14,27 +14,58 @@ import UIKit
import Social
import RNShareMenu
+enum ShareViewControllerError: Error {
+ case missingKey(key: String)
+ case unknownGroup(name: String)
+ case fileAccessError(debugDescription: String)
+}
+
class ShareViewController: SLComposeServiceViewController {
- var hostAppId: String?
- var hostAppUrlScheme: String?
- var sharedItems: [Any] = []
+ var sharedItems: [[String: String]] = []
- 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)")
+ internal func getStringFromPlist(key: String) throws -> String {
+ guard let entry = Bundle.main.object(forInfoDictionaryKey: key) as? String else {
+ if #available(iOSApplicationExtension 14.0, *) {
+ Logger().error("Error: You haven't defined '\(key)' in your Share Extension's Info.plist")
+ }
+ throw ShareViewControllerError.missingKey(key: key)
}
-
- 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)")
+
+ return entry
+ }
+
+ func getAppGroupName() throws -> String {
+ do {
+ return try getStringFromPlist(key: APP_GROUP_KEY)
+ }
+ catch {
+ return try "group." + getStringFromPlist(key: HOST_APP_IDENTIFIER_INFO_PLIST_KEY)
}
}
+
+ func getUrlScheme() throws -> String {
+ return try getStringFromPlist(key: HOST_URL_SCHEME_INFO_PLIST_KEY)
+ }
+
+ func getUserDefaultsInstance() throws -> UserDefaults {
+ let appGroup = try getAppGroupName()
+ guard let userDefaults = UserDefaults(suiteName: appGroup) else {
+ throw ShareViewControllerError.unknownGroup(name: appGroup)
+ }
+ return userDefaults
+ }
+
+ func getGroupFileManagerContainer() throws -> URL {
+ let appGroup = try getAppGroupName()
+ guard let groupFileManagerContainer = FileManager.default
+ .containerURL(forSecurityApplicationGroupIdentifier: appGroup)
+ else {
+ throw ShareViewControllerError.unknownGroup(name: appGroup)
+ }
+ return groupFileManagerContainer
+ }
+
override func isContentValid() -> Bool {
// Do validation of contentText and/or NSExtensionContext attachments here
return true
@@ -57,73 +88,56 @@ class ShareViewController: SLComposeServiceViewController {
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
+ do {
+ let userDefaults = try self.getUserDefaultsInstance()
+
+ if let data = extraData {
+ try self.storeExtraData(data)
+ } else {
+ try self.removeExtraData()
}
-
- 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)
+
+ let semaphore = DispatchSemaphore(value: 0)
+
+ for item in items {
+ guard let attachments = item.attachments else {
+ self.cancelRequest()
+ return
}
-
- semaphore.wait()
+
+ 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()
+ userDefaults.set(self.sharedItems, forKey: USER_DEFAULTS_KEY)
+ userDefaults.synchronize()
- self.openHostApp()
+ try self.openHostApp()
+ }
+ catch {
+ self.extensionContext!.cancelRequest(withError: error)
+ }
}
}
- 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
- }
+ func storeExtraData(_ data: [String:Any]) throws {
+ let userDefaults = try self.getUserDefaultsInstance()
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
- }
+ func removeExtraData() throws {
+ let userDefaults = try self.getUserDefaultsInstance()
userDefaults.removeObject(forKey: USER_DEFAULTS_EXTRA_DATA_KEY)
userDefaults.synchronize()
}
@@ -162,53 +176,36 @@ class ShareViewController: SLComposeServiceViewController {
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
+ do {
+ guard (error == nil) else {
+ throw ShareViewControllerError.fileAccessError(debugDescription: error.debugDescription)
+ }
+ guard let url = data as? URL else {
+ throw ShareViewControllerError.fileAccessError(debugDescription: COULD_NOT_FIND_IMG_ERROR)
+ }
+
+ let groupFileManagerContainer = try self.getGroupFileManagerContainer()
+ let mimeType = url.extractMimeType()
+ let fileExtension = url.pathExtension
+ let fileName = UUID().uuidString
+ let filePath = groupFileManagerContainer
+ .appendingPathComponent("\(fileName).\(fileExtension)")
+
+ try self.moveFileToDisk(from: url, to: filePath)
+ self.sharedItems.append([DATA_KEY: filePath.absoluteString, MIME_TYPE_KEY: mimeType])
+ semaphore.signal()
}
-
- 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
+ catch {
+ self.extensionContext!.cancelRequest(withError: error)
}
-
- 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
+ func moveFileToDisk(from srcUrl: URL, to destUrl: URL) throws {
+ if FileManager.default.fileExists(atPath: destUrl.path) {
+ try FileManager.default.removeItem(at: destUrl)
}
-
- return true
+ try FileManager.default.copyItem(at: srcUrl, to: destUrl)
}
func exit(withError error: String) {
@@ -216,13 +213,17 @@ class ShareViewController: SLComposeServiceViewController {
cancelRequest()
}
- internal func openHostApp() {
- guard let urlScheme = self.hostAppUrlScheme else {
- exit(withError: NO_INFO_PLIST_URL_SCHEME_ERROR)
- return
+ internal func getHostAppUrl() throws -> URL? {
+ let urlScheme = try self.getUrlScheme()
+ if !urlScheme.hasSuffix("://") {
+ return URL(string: urlScheme + "://")
}
- let url = URL(string: urlScheme)
+ return URL(string: urlScheme)
+ }
+
+ internal func openHostApp() throws {
+ let url = try getHostAppUrl()
let selectorOpenURL = sel_registerName("openURL:")
var responder: UIResponder? = self