Everything you need to know about iOS App Extensions



    App Extensions appeared in iOS 8 and made the system more flexible, powerful and affordable for users. Applications can be displayed as a widget in the Notification Center, offer their filters for photos in Photos, display a new system keyboard and much more. At the same time, the security of user data and the system was preserved. The features of the work of App Extensions will be discussed below.

    Apple has always sought to carefully isolate applications from each other. This is the best way to ensure the safety of users and protect their data. Each application is given a separate place in the file system with limited access. App Extensions made it possible to interact with the application without launching it or displaying 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 they contain - Containing App. By themselves, they cannot be published on the App Store, only with the Containing App. All App Extensions perform one specific task and are tied to only one area of ​​iOS, depending on their type. For example: Custom Keyboard Extensions are for replacing the standard keyboard, and Photo Editing Extensions are for editing photos in Photos. There are currently 25 types of App Extensions.

    Life Extension App Extension


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



    • The user selects the App Extension through the Host App.
    • 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, performing a task, or starts a background process to complete it; upon completion of the task, the result can be returned to the Host App.
    • Once 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 using IPC .
    • App Extension - Containing App
      Indirect interaction. App Groups are used for data exchange , and Embedded Frameworks is used for general code . You can launch the Containing App from the App Extension using the URL Schemes .

    Generic code: dynamic frameworks


    If the Containing App and 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 that uses 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 a 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.
    • APIs marked with inaccessibility macros.
    • Camera and microphone (except iMessage Extension).
    • Performing lengthy background tasks (features of this restriction vary depending on the type of App Extension).
    • Receive data using AirDrop.

    Using any of this list in App Extensions will lead to its rejection when published to the App Store.

    In the framework settings in General, you need to check the box next to “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, do import:

    import ImageFilters

    Data Exchange: App Groups


    The Containing App and App Extension have their own limited sections of the file system, and only they have access to them. In order 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.

    The App Group is created in the Apple Developer Portal :



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



    Next Continue -> Register -> Done .

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



    Similarly for the App Extension:



    Now the Containing App and App Extension share a container. Next, we’ll talk about how to read and write to it.

    UserDefaults


    To exchange a small amount of data 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, it’s better suited NSFileCoordinatorto ensure read and write consistency. This will avoid data corruption, since 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 it NSFileCoordinatorworks synchronously. While some file will be occupied by some process, others will have to wait for it to be released.

    If you want the App Extension to know when the Containing App changes data state, it is used NSFilePresenter. 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 property presentedItemOperationQueuereturns the queue that is used for callbacks when changing files. The method is presentedItemDidChange()called when a process, in this case the Containing App, changes the contents of the data. If changes were made directly using low-level write calls, it is presentedItemDidChange()not called. Only changes using are taken into account NSFileCoordinator.

    When initializing an object, NSFileCoordinatorit is recommended to transfer the object NSFilePresenter, especially if it starts some kind of file operation:

    let fileCoordinator = NSFileCoordinator(filePresenter: self)

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

    To start monitoring the state of data, you need to call the method addFilePresenter(_:)with the corresponding object:

    NSFileCoordinator.addFilePresenter(self)

    Any objects created later NSFileCoordinatorwill automatically be aware of this object NSFilePresenterand notify about changes occurring in its directory.

    To stop monitoring data status, use removeFilePresenter(_:):

    NSFileCoordinator.removeFilePresenter(self)

    Core data


    For data sharing, you can use SQLite and, accordingly, Core Data. They can manage processes that work with shared data. To configure Core Data to be shared between the Containing App and App Extension, create a subclass NSPersistentContainerand override the method defaultDirectoryURLthat should return the data store address:

    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 AppDelegatechange the property persistentContainer. It is automatically created if, when creating a project, check the Use Core Data checkbox . Now we will return the class object SharedPersistentContainer:

    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
    }()

    All that remains is to add .xcdatamodeld to the App Extension. Select the .xcdatamodeld file in the navigator panel. In the File Inspector , under the Target Membership section, check the box next to App Extension:



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

    Launching the Containing App from the App Extension


    When the Host App sends an App Extension request, it provides extensionContext. This object has a method open(_:completionHandler:)by which you can open the Containing App. However, this method is not available for all types of App Extension. On iOS, it is supported by 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 submit to the App Store.

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



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

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

    For those types of App Extensions for which the method call is 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 go through the chain of objects UIResponderuntil there is UIApplicationone that will accept the call openURL:

    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 has brought a lot to 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 has been missing for so long.

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

    Useful links:

    White papers
    Sharing data between iOS apps and app extensions
    iOS 8 App Extension Development Tips

    Also popular now: