📜 ⬆️ ⬇️

Everything you need to know about the iOS App Extensions



App Extensions appeared in iOS 8 and made the system more flexible, powerful and accessible to users. Applications can be displayed as a widget in the Notification Center, offer your own filters for photos in Photos, display a new system keyboard and much more. At the same time, user data and system security has been preserved. On the features of the App Extensions and will be discussed below.

Apple has always sought to carefully isolate applications from each other. This is the best way to keep users safe and secure. Each application is given a separate place in the file system with limited access. Appearance of App Extensions allowed to interact with the application without launching it or showing it on the screen. Thus, part of its functionality will be available to users when they interact with other applications or the system.

App Extensions are executable files that run independently of the application containing them - Containing App . By themselves, they cannot be published to the App Store, only with the Containing App. All App Extensions perform one specific task and are tied to only one iOS area, depending on their type. For example: Custom Keyboard Extensions are designed to replace the standard keyboard, and Photo Editing Extensions - to edit photos in Photos. In total there are 25 types of App Extensions.
')

App Extension Life Cycle


The application that the user uses to run the App Extension is called the Host App . The Host App launches the App Extension life cycle by sending it a request in response to a user action:




For example, when sharing a photo from Photos using the Facebook Share Extension, Facebook is the Containing App, and Photos is the Host App. In this case, Photos starts the Facebook Share Extension life cycle when the user selects it in the Share menu:



Interaction with App Extension





Common code: dynamic frameworks


If the Containing App and the App Extension use the same code, it should be placed in a dynamic framework.

For example, a Photo Editing Extension may be associated with a custom photo editing application, using some filters from the Containing App. A good solution would be to create a dynamic framework for these filters.

To do this, add a new Target and select the Cocoa Touch Framework :



Specify the name (for example, ImageFilters ), and in the navigator panel you can see a new folder with the name of the created framework:

You need to make sure that the framework does not use APIs that are not available for App Extensions:


Using any of this list in App Extensions will cause it to be rejected when published to the App Store.

In the settings of the framework in General, you must tick the “Allow app extension API only” :



In the framework code, all classes, methods, and properties used in the Containing App and App Extensions should be public . Wherever you need to use the framework, we import :

 import ImageFilters 

Data Exchange: App Groups


Containing App and App Extension have their own limited portions of the file system, and only they have access to them. For the Containing App and App Extension to have a common container with read and write access, you need to create an App Group for them.

App Group is created in the Apple Developer Portal :



In the upper right corner, click "+", in the window that appears, enter the necessary data:



Further Continue -> Register -> Done .

In the settings of the Containing App, go to the Capabilities tab, activate App Groups and select the created group:



Similarly for App Extension:



Now the Containing App and App Extension share a common container. Next, let's talk about how to read and write to it.

UserDefaults


To exchange a small amount of data, it is convenient to use UserDefaults , you just need to specify the name of the App Group:

 let sharedDefaults = UserDefaults(suiteName: "group.com.maxial.onemoreapp") 

NSFileCoordinator and NSFilePresenter


For big data, the NSFileCoordinator is better suited, thanks to which you can ensure consistency of reading and writing. This will avoid data corruption, as there is a possibility that several processes can access them simultaneously.

The URL of the shared container is obtained as follows:

 let sharedUrl = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.com.maxial.onemoreapp") 

Record:

 fileCoordinator.coordinate(writingItemAt: sharedUrl, options: [], error: nil) { [unowned self] newUrl in do { let data = try NSKeyedArchiver.archivedData(withRootObject: self.object, requiringSecureCoding: false) try data.write(to: newUrl, options: .atomic) } catch { print(error) } } 

Reading:

 fileCoordinator.coordinate(readingItemAt: sharedUrl, options: [], error: nil) { newUrl in do { let data = try Data(contentsOf: newUrl) if let object = try NSKeyedUnarchiver.unarchivedObject(ofClass: NSString.self, from: data) as String? { self.object = object } } catch { print(error) } } 

It is worth considering that the NSFileCoordinator works synchronously. While some file will be occupied by some process, others will have to wait for its release.

If you want the App Extension to know when the Containing App changes the state of the data, NSFilePresenter used. This is a protocol whose implementation may look like this:

 extension TodayViewController: NSFilePresenter { var presentedItemURL: URL? { let sharedUrl = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.com.maxial.onemoreapp") return sharedUrl?.appendingPathComponent("Items") } var presentedItemOperationQueue: OperationQueue { return .main } func presentedItemDidChange() { } } 

The presentedItemOperationQueue property returns a queue that is used for callbacks when files change. The presentedItemDidChange() method will be called when a process, in this case the Containing App, changes the content of the data. If changes were made directly using low-level write calls, then presentedItemDidChange() not called. Only changes using NSFileCoordinator taken into account.

When initializing an NSFileCoordinator object, NSFileCoordinator recommended to pass an NSFilePresenter object, especially if it starts any file operation:

 let fileCoordinator = NSFileCoordinator(filePresenter: self) 

Otherwise, the NSFilePresenter object will receive notifications about these operations, which can lead to a deadlock when working in the same thread.

To start tracking the status of the data, call the addFilePresenter(_:) method with the corresponding object:

 NSFileCoordinator.addFilePresenter(self) 

Any NSFileCoordinator objects created later will automatically be aware of this NSFilePresenter object and will be notified of changes occurring in its directory.

To stop tracking the status of data, use removeFilePresenter(_:) :

 NSFileCoordinator.removeFilePresenter(self) 

Core data


To share data, you can use SQLite and, accordingly, Core Data. They know how to manage processes that work with shared data. To configure Core Data for sharing for the Containing App and the App Extension, create a subclass of NSPersistentContainer and override the defaultDirectoryURL method, which should return the address of the data store:

 class SharedPersistentContainer: NSPersistentContainer { override open class func defaultDirectoryURL() -> URL { var storeURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.com.maxial.onemoreapp") storeURL = storeURL?.appendingPathComponent("OneMoreApp.sqlite") return storeURL! } } 

In AppDelegate change the persistentContainer property. It is automatically created if you tick the Use Core Data box while creating the project. Now we will return an object of the SharedPersistentContainer class:

 lazy var persistentContainer: NSPersistentContainer = { let container = SharedPersistentContainer(name: "OneMoreApp") container.loadPersistentStores(completionHandler: { (storeDescription, error) in if let error = error as NSError? { fatalError("Unresolved error \(error), \(error.userInfo)") } }) return container }() 

It only remains to add .xcdatamodeld to the App Extension. Select the .xcdatamodeld file in the navigator panel. In the File Inspector, in the Target Membership section, tick the App Extension box:



Thus, the Containing App and the App Extension will be able to read and write data in one storage and use the same model.

Run the Containing App from the App Extension


When the Host App sends an App Extension request, it provides an extensionContext . This object has an open(_:completionHandler:) method, with which you can open the Containing App. However, this method is not available for all App Extension types. In iOS, it is supported by the Today Extension and iMessage Extension. iMessage Extension can only use it to open the Containing App. If Today Extension opens another application with it, additional verification may be required to be sent to the App Store.

To open an application from App Extension, you need to determine the URL Scheme in the Containing App:



Next, call the open(_:completionHandler:) method open(_:completionHandler:) with this schema from the App Extension:

 guard let url = URL(string: "OneMoreAppUrl://") else { return } extensionContext?.open(url, completionHandler: nil) 

For those types of App Extensions that the open(_:completionHandler:) method call open(_:completionHandler:) not available, there is also a way. But there is a possibility that the application may be rejected when checking in the App Store. The essence of the method is to UIResponder through the chain of UIResponder objects until there is a UIApplication , which will accept the openURL call:

 guard let url = URL(string: "OneMoreAppUrl://") else { return } 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 } 

Future App Extensions


App Extensions have brought a lot of new in iOS development. Gradually, more types of App Extensions appear, their capabilities are developing. For example, with the release of iOS 12 SDK, you can now interact with the content area in notifications, which was not enough for so long.

Thus, Apple continues to develop this tool, which inspires optimism about its future future.

Useful links:

Official documentation
Sharing data between iOS apps and app extensions
iOS 8 App Extension Development Tips

Source: https://habr.com/ru/post/441890/


All Articles