
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:

- The user selects the App Extension through the Host App.
- The Host App sends an App Extension request.
- iOS launches the App Extension in the context of the Host App and establishes a communication channel between them.
- The user performs an action in the App Extension.
- App Extension completes the request from the Host App by executing the task, or starts a background process to execute it; upon completion of the task, the result can be returned to the Host App.
- As soon as the App Extension executes its code, the system terminates this App Extension.
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

- Containing App - Host App
Do not interact with each other.
- App Extension - Host App
Interact with the use of IPC .
- App Extension - Containing App
Indirect interaction App Groups are used for data exchange, and Embedded Frameworks is used for shared code. You can launch the Containing App from the App Extension using URL Schemes .
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:
- Shared from UIApplication.
- API marked with inaccessible macros.
- Camera and microphone (except iMessage Extension).
- Perform lengthy background tasks (features of this restriction vary depending on the type of App Extension).
- Retrieving data using AirDrop.
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 documentationSharing data between iOS apps and app extensionsiOS 8 App Extension Development Tips