This section is translated by Cocoachina translation Group member devtalking (blog) from the Apple official document App Extension programming guide--handling Common Scenarios section, Please errata. Welcome to our translation team, see: Cocoachina Editor and translator Recruitment!
When writing custom code to perform an app extension task, you may need to deal with situations where several other types of extensions will also occur. In this chapter, we will help you how to deal with and deal with these common problems.
Share code with inline frames
You can create an inline framework to share code between the app extension and its main application (containing app). For example, you developed the image Filter feature in the photo editing extension, and The extended containing app also has this feature, so you can encapsulate the code that implements the feature as a framework, This framework is embedded in the extended target and the main application target.
You want to make sure that the inline frame you create does not contain APIs that the app extension cannot use. Such APIs are typically tagged with unavailability macros, such as ns_extension_unavailable.
If you create an inline frame that contains APIs that the app extension cannot use, you can safely link to the containing app, which can use the API in the framework, but not the extended shared code with the app (the translator notes that the app extension cannot use all the APIs that the framework provides, Code sharing is not possible). If you upload apps that have this framework in app extensions, or if other parts use an API that is not available, then the audit will be rejected.
If we want to apply extensions using inline frames, first configure them. Set Target's require only app-extension-safe API option to Yes. If you do not set this, Xcode will prompt you with a warning: linking against dylib not safe for use in application extensions.
Important: If the containing app is to be linked to an inline frame, it must support the arm64 schema or it will be rejected when uploading the App store. (as described in the "Create App Extensions" section, all app extensions support the ARM64 schema.) )
When configuring your Xcode project, be sure to set Destination to frameworks in the Copy Files entry on the Build phases tab.
Important: We usually choose Frameworks as the Copy Files build phase destination. If you set it to Sharedframework, you will be rejected when uploading the App store.
You can let the containing app support IOS7 or earlier versions, but when running in IOS8 or later, pay special attention to the security of the inline framework. For more information, see Deploying a containing App to older Versions of IOS.
For more information on creating and using inline frames, watch the video "Building Modern Frameworks" in WWDC 2014.
Share data with the containing app
There is a difference between the application extension and the security domain of its containing app. Even if the expansion pack is nested within the containing app package. By default, app extensions and containing apps are not directly accessible to each other's containers.
But you can use data sharing to realize this desire. For example, you want the app extension to share a single big data set with its containing app . Like prerendered assets.
To achieve data sharing, we want to use Xcode or the developer Portal to allow app extensions and its containing app to become an app group, then register the app group in the developer portal and indicate that the app group is used in the containing app. For information on application groups, please refer to the entitlement Key Reference documentation Adding an app group chapter.
Once you've set up the app group, the app extension and its containing app can share access to the user's information through the Nsuserdefaults API. We can use the Initwithsuitename: method to instantiate a Nsuserdefaults object and then pass in the identifier of the shared group. For example, a shared extension, which may update the user's most recently used shared account, then we can write:
- Create and share access to an Nsuserdefaults object.
- Nsuserdefaults *myshareddefaults = [[Nsuserdefaults alloc] initwithsuitename:@ "Com.example.domain.MyShareExtension" ];
- Use the shared user Defaults object to update the user's account.
- [Myshareddefaults setobject:theaccountname forkey:@ "Lastaccountname"];
Show us how app extensions and its containing app are sharing data through shared containers.
Figure 4-1 The container that applies the extension differs from its containing app container.
Important: If your app extension uses the Nsurlsession class to perform upload and download tasks in the background, you must set up a shared container so that the extension and the containing app can access the data transferred by the transform. For more information on uploading and downloading in the background, see performing uploads and Downloads.
If you set up a shared container, the containing app and the extensions it contains that allow participation in data sharing can read and write to the content in the shared container. You also have to synchronize the operation of the data to avoid data corruption or errors. Using the Uidocument class, Core data, or SQLite can help you get users to access network content by asking Safari to run the JS file and return the results to the extension.
Visit Web pages
In shared extensions (iOS and OS X platforms) and action extensions (iOS platforms), users are generally allowed to use Safari to access Web pages and execute JavaScript scripts and return the results to the extension. You can also modify Web page content through JavaScript files, either before your extension runs (for two platforms) or after a task is completed (only for iOS platforms). For example, share extension, which can help users share content on a webpage, or an action extension on iOS may display the specified translation of the current page.
If you want to add Web Access and Action app extensions, you need to follow these steps:
1. Create a JavaScript file and declare a global object named Extensionpreprocessingjs and assign an instance of the new custom JavaScript class to the object.
2. Add the keyword Nsextensionjavascriptpreprocessingfile to the app extension's attribute list file to indicate to the Safari browser which JavaScript file to use.
3. In the Nsextensionactivationrule dictionary, assign Nsextensionactivationsupportsweburlwithmaxcount to a value that is nonzero. (For more information about the Nsextensionactivationrule dictionary, see declaring supported Data Types for a Share or Action Extension.) )
4. When your app extension starts running, use the Nsitemprovider class to get the results returned by running the JavaScript file.
5. In an app extension for iOS, if you want Safari to update the Web page after the extension finishes performing tasks, you'll pass in the value to the JavaScript file. (The Nsitemprovider class is also used in this step.) )
In order to inform Safari that your app extension contains a JavaScript file, you need to apply the extended Info.plist file to the The Nsextensionattributes dictionary adds the Nsextensionjavascriptpreprocessingfile keyword to indicate your JavaScript file. The value of this keyword is the name of the JavaScript file that you want Safari to load before your app extension runs. Like what:
- <key>NSExtensionAttributes</key>
- <dict>
- <key>NSExtensionJavaScriptPreprocessingFile</key>
- <string>MyJavaScriptFile</string>
- </dict>
In the iOS and OS X platforms, you can define a run () function in your custom JavaScript class, which is the entry for Safari to load JavaScript files. In the run () function, Safari provides a parameter named Completionfunction that you can use as a key-value object to pass the result to the app extension.
In the iOS platform, you can also define a finalize () function when the application extension calls CompleteRequestReturningItems:expirationHandler:completion at the end of the task: Method, Safari calls the Finalize () function. In this function, you can change the content of the Web page by passing a value to the CompleteRequestReturningItems:expirationHandler:completion: method.
For example, your iOS app extension needs to be started on a Web page URI, and when it ends up changing the background color of the page, you need to write JavaScript code like this:
- var myextensionjavascriptclass = function () {};
- Myextensionjavascriptclass.prototype = {
- Run:function (arguments) {
- Pass the BaseURI of the webpage to the extension.
- Arguments.completionfunction ({"BaseURI": Document.baseuri});
- },
- Note that the Finalize function was only available in IOS.
- Finalize:function (arguments) {
- Document.body.style.backgroundColor = arguments["BgColor"];
- }
- };
- var extensionpreprocessingjs = new Myextensionjavascriptclass;
In iOS and OS X platforms, you need to write code to handle the value returned by the fun () function, to get to the value in the dictionary, We need to specify the Kuttypepropertylist type as the identifier for passing in the LoadItemForTypeIdentifier:options:completionHandler of the Nsitemprovider class: method. Use Nsextensionjavascriptpre Processingresultskey as key in the dictionary to take the value. For example, we want to get the return value of the URI passed to run ():
- [Imageprovider loaditemfortypeidentifier:kuttypepropertylist options:nil completionhandler:^ (NSDictionary *item, Nserror *error) {
- Nsdictionary *results = (nsdictionary *) item;
- NSString *baseuri = [[Results Objectforkey:nsextensionjavascriptpreprocessingresultskey] objectforkey:@ "BaseURI"];
- }];
The Finalize () function is a dictionary that contains the values that we need to process, and then uses the Nsitemprovider Initwithitem:typeidentifier: method to encapsulate the dictionary when it is passed and invoked after the application extension finishes executing the task. For example, when the extension finishes the task and we want to make the page red, we can write:
- Nsextensionitem *extensionitem = [[Nsextensionitem alloc] init];
- Extensionitem.attachments = @[[[nsitemprovider alloc] Initwithitem: @{nsextensionjavascriptfinalizeargumentkey: @{@ " BgColor ": @" Red "}} Typeidentifier: (NSString *) kuttypepropertylist]];
- [[Self Extensioncontext] completerequestreturningitems:@[extensionitem] Expirationhandler:nil Completion:nil];
Perform upload and download tasks
User general operating habits tend to be when you use your app extension to complete a task, you can immediately feedback the results in the application using the extension. If an extension is working on a task that contains a lengthy upload download operation, make sure that you continue to complete the task when your app extension is closed. To achieve this, we need to use the Nsurlsession class to create a URL session and create a background upload download task.
Tip: You can think back to other types of background tasks, such as the background support VoIP, background music playback, which can not be implemented with the application extension. For more information, please see respond to the Host APP's request.
When your app extension is ready to upload a download task, the extension completes the request made by the app that called it and terminates the extension without affecting the upload download task. For more information on extending the process of calling application requests, see the respond to the host app's request. On an iOS system, if your app extension is not running when it finishes performing background tasks, the system automatically runs the extended carrier app in the background and calls Application:handleeventsforbackgroundurlsession: Completionhandler: Proxy method.
Important: If your app extension creates a nsurlsession task in the background, you'll have to set up a shared container to ensure that your extensions and carrier apps are sharing data. We can use the Sharedcontaineridentifier property in the Nsurlsessionconfiguration class to specify an identifier for a shared container, and then we can get to the shared container by that identifier. See the sharing Data with Your containing App documentation to set up a shared container.
The following example shows how to configure a URL session and create a download task:
- Nsurlsession *mysession = [self configuremysession];
- Nsurl *url = [Nsurl urlwithstring:@ "Http://www.example.com/LargeFile.zip"];
- Nsurlsessiontask *mytask = [MySession Downloadtaskwithurl:url];
- [MyTask resume];
- -(Nsurlsession *) configuremysession {
- if (!mysession) {
- nsurlsessionconfiguration* config = [nsurlsessionconfiguration backgroundsessionconfigurationwithidentifier:@ " Com.mycompany.myapp.backgroundsession "];
- Config.sharedcontaineridentifier = @ "com.mycompany.myappGroupidentifier";
- MySession = [nsurlsession sessionwithconfiguration:config delegate:self Delegatequeue:nil];
- }
- return mysession;
- }
Because a background session can only be used by one process per unit of time, you need to create a different background session for all extensions in the carrier app (each background session must have a unique identifier). Here we recommend using only one background session created by the extension when the carrier app handles the extended task in the background. If you want to perform other network-related tasks, create a corresponding URL session.
If you need to complete the request for the calling app before creating a URL session in the background, make sure that the code for creating and using the session is valid. When your extension calls Completerequestreturningitems:completionhandler: Method tells the caller that the app has completed the request, the system can terminate your app extension at any time.
Supported data types for sharing and action extension declarations
In your share or action extension, some data may be used in their work, and the types of the data are different. To ensure that your extensions are displayed only when the user chooses the type of data that your extension supports in the calling app. You need to add the Nsextensionactivationrule keyword to the extended attribute list file. You can also use this keyword to specify the maximum number of extensions to handle for each type. When your app extension is running, the system compares the value of the changed keyword with the value of the Attachments property of the extended data item. More information about the Nsextensionactivationrule keyword can be found in the action Extension keys document in Information property list key Reference chapter.
For example, you can declare that your share extension supports maximum processing of 10 images, a movie and a website URL. You can refer to the following wording:
- <key>NSExtensionAttributes</key>
- <dict>
- <key>NSExtensionActivationRule</key>
- <dict>
- <key>NSExtensionActivationSupportsImageWithMaxCount</key>
- <integer>10</integer>
- <key>NSExtensionActivationSupportsMovieWithMaxCount</key>
- <integer>1</integer>
- <key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
- <integer>1</integer>
- </dict>
- </dict>
If you want to specify an unsupported data type, you can set the value of the type to 0, or do not add the type in Nsextensionactivationrule.
Tip: If your share extension or the action extension in iOS requires access to a webpage, you must make sure that the Nsextensionactivationsupportsweburlwithmaxcount The value of the keyword is not 0 (see Accessing a webpage for more information about accessing Web pages through JavaScript in app extensions).
You can also use the UTI subtype defined by Nsextensionitem for data detectors to detect text messages, such as phone numbers or mailing addresses.
The keywords in the Nsextensionactivationrule dictionary are sufficient to meet the filtering needs of most applications. If you need to do more complex filtering, such as the difference between Public.url and public.image, then you have to create an assertion statement in the text. If you are creating an assertion, set the value of the Nsextensionactivationrule keyword to the assertion string you specified. (At run time, the system will automatically compile the string as a Nspredicate object)
For example, an app extension's Attachment property can be specified as a PDF file, which can be written like this:
- {Extensionitems = ({
- Attachments = (
- {
- Registeredtypeidentifiers = (
- "Com.adobe.pdf",
- "Public.file-url"
- );
- }
- );
- })}
To specify that your app extension can handle PDF files, you can create an assertion string like this:
- Subquery (Extensionitems, $extensionItem, subquery ($extensionItem. Attachments, $attachment, any $ Attachment.registeredtypeidentifiers uti-conforms-to "com.adobe.pdf") [email protected] = = 1) [email protected] = = 1
During development, you can test your code path with the Truepredicate constant (the result is true) before you create an assertion statement. For more information on the syntax of assertion statements, see predicate Format String Syntax.
Important: Before uploading your carrier app to the App Store, make sure that all Truepredicate constants have been replaced with the specified assertion statement or Nsextensionactivationrule keyword, otherwise the carrier app will be rejected by the App Store.
Configure the carrier app to work with older versions of the iOS system
If you use an inline frame in a carrier application, it can be used in a post-iOS8.0 version, even if the inline frame doesn't support the old version of the system.
Enabling the carrier application to do this is the Dlopen command, which allows you to use conditional links and mechanisms to load the framework package. You can use this command instead of a compile-time link, which you can edit in the general options of Xcode or the build phases option. The principle is that inline frames are only linked when the carrier application is running in iOS8.0 or later versions.
IMPORTANT: If your carrier application uses an inline frame, you must support the arm64 architecture , otherwise it will be rejected by the App Store.
Set conditional links for applying extensions in your Xcode project
1. Set the run system version of each app extension to iOS8.0 or higher, usually select target in Xcode and set deployment info in the General option.
2. Set the operating system version of your carrier app to the minimum iOS version you want to support.
3. In your carrier application, through the Systemversion method, the operational
Check to determine the version of iOS and determine whether to execute the dlopen command. The Dlopen command is only specified when your carrier app is running in iOS8.0 or later versions.
The specific iOS API uses the inline framework with the Dlopen command. You have to use these APIs selectively, just as you would with the Dlopen command. These APIs are CFBUNDLEREF package types:
Cfbundlegetfunctionpointerforname Cfbundlegetfunctionpointersfornames
There are also methods from the NSBundle class:
Load loadandreturnerror:classnamed:
Because you typically configure the runtime version of the carrier application to a lower version, these APIs are usually checked at runtime and are only used when the carrier app is running in iOS8.0 or later
APP Extension Programming Guide (Ios8/os X v10.10): Solutions to frequently asked questions