iOS extensions with multiple targets

IosPlistCode SigningIos App-ExtensionBundle Identifier

Ios Problem Overview


In iOS 8, when we create an app extension, we have to decide which target it is attached to. The extension will have the same bundle ID's prefix as the target.

  1. Is there any way to change the target afterward?
  2. If my project contains 2 (or more) targets (for example one for debug/simulator, one for production/device), what's the best way to work with extensions? Do I need to create another extension and duplicate the code (very bothersome to keep the same code for both targets)?

Ios Solutions


Solution 1 - Ios

To share one widget with others targets, you only need to add widget.appex target to Embedded Binaries for every parent target in General configuration tab

enter image description here

Then you'll get Embed App Extensions area at Build Phases automatically

enter image description here

Solution 2 - Ios

This is my setup: I have 3 targets (production, staging, local) and an extension target that I don't want to duplicate 3 times.

Just to clarify Neo Chen's answer, edit each of your parent targets' schemes:

Build > Pre-actions > New Run Script Action > Provide build settings from (parent scheme).

Paste this for each extension:

#!/bin/bash

buildID=${PRODUCT_BUNDLE_IDENTIFIER}
extId="notification-service"

/usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier $buildID.$extId" "${SRCROOT}/${extId}/Info.plist"

Seems to work on first build.

Solution 3 - Ios

It seems like you should be able to just duplicate the Extension target with its own Info.plist, but not anything else.

However, when you create an Extension, Xcode adds "Embed App Extensions" to the Build Phases of the app's target, as seen below, and there's no UI to do that yet.

enter image description here

Still, you can create the extension for the second target, then delete all the files except the .plist, and fix what needs to be fixed. Here's a step-by-step:

  • Create "Extension 1" for "Target 1"

  • Create "Extension 2" for "Target 2"

  • Delete all files created for "Extension 2", except its Info.plist

  • Make the "Build Phases" for "Extension 2" target the same as the build phases for "Extension 1". Usually that's adding the necessary .m files to the "Compile Sources" phase, and resources to the "Copy Bundle Resources" phase

Solution 4 - Ios

I have create a Run Script to support this requirement

#!/bin/sh
buildNumber=$(/usr/libexec/PlistBuddy -c "Print CFBundleVersion" "$INFOPLIST_FILE")
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $buildNumber" "${SRCROOT}/ImagePush/Info.plist"

buildVersion=$(/usr/libexec/PlistBuddy -c "Print CFBundleShortVersionString" "$INFOPLIST_FILE")
/usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString $buildVersion" "${SRCROOT}/ImagePush/Info.plist"

buildID=${PRODUCT_BUNDLE_IDENTIFIER}
/usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier $buildID.ImagePush" "${SRCROOT}/ImagePush/Info.plist"

ImagePush is my Extension

add to the target which one you need and add make sure this script run before your extension setting in Build Phases, then you just need to do the build action twice (PS: first time it will fail, will try to improve) and it will support multiple target

Solution 5 - Ios

In my project I need to build a bit different versions of apps (differ in details, e.g. each application is branded with a different logo).

Let's say there is about 10 "app" targets, I can't imagine adding Notification Content and Notification Service extensions per each main target (in this case I would maintaining 30 targets in total - madness).

I run a script (https://gist.github.com/damian-rzeszot/0b23ad87e5ab5d52aa15c095cbf43c59) after "Embed App Extensions" phase, that overrides bundle id in app extension plists and entitlements, app version, changes the provisioning profile and re-signs the bundle.

Solution 6 - Ios

Xcconfigs are a great way to modify plist entries and in code variable based on an Xcode scheme change.

If you are wanting to change the Bundle identifier for example of a given extension based on scheme:

  1. Create an Xcconfig file for your extension. My iMessageExtension-Debug.xcconfig has this entry: PRODUCT_BUNDLE_IDENTIFIER = $(APP_BUNDLE_IDENTIFIER).iMessageExtension

  2. From the Xcode file inspector click on your project > in the details pane click on your project > Info tab > Add configuration

  3. I created a debug config.

  4. In your newly create config drill to your extension > Select the config file

  5. Create a new Scheme: in the run tab of that new scheme you can select your newly created config. This same process can be done for release extra.

  6. The Xcconfig option we created can be used directly inside of the iMessageExtension > Info.plist: Bundle identifier : $(PRODUCT_BUNDLE_IDENTIFIER)

If you need to reference a variable in code based on an xcconfig variable:

For example I wanted to change my application initial screen based on the Xcode scheme that was selected:

Xcconfig: INITIAL_SCREEN = tabBarHome

Plist: initialScreen : $(INITIAL_SCREEN)

Swift Code: var initialScreen = object(forInfoDictionaryKey: "initialScreen") as? String

Solution 7 - Ios

EDIT

2 solutions:

To be honest I think the XCConfig solution is much much more elegant. You just swap things in and out rather than trying to override everything in a very specific order...


Every other answer had a piece of what was necessary. With some important modifications to this post I was able to get things right.

You need to do three things:

  • Change bundleId of appex (app extension)
  • re-sign appex after its bundle is changed. While the app runs on simulator correctly it won't work on real device without this step.
  • set appropriate provisioning profiles. (excluded from this answer as I don't know how to do that yet)

Note: You can't sign the appex until it's embedded. So the 're-sign appex' step needs to happen after the 'embed App extension' step. Similarly the appex can't be embedded if the bundleId isn't prefixed with the parent app's bundleId.

The final order should look like this:

enter image description here

change bundleId of appex

plutil's sytanx is like this:

-replace keypath -type value 

So just do:

plutil -replace \
CFBundleIdentifier -string \
$PRODUCT_BUNDLE_IDENTIFIER.contentExt \
"$BUILT_PRODUCTS_DIR/contentExt.appex/Info.plist"

In case you wanted to learn more about plutil (see here and here for more). PlistBuddy is kinda old.

Note: ContentExtension is the target name I have. Make sure you use yours correctly

re-sign appex

/usr/bin/codesign \
--force \
--sign $EXPANDED_CODE_SIGN_IDENTITY \
--entitlements $CONFIGURATION_TEMP_DIR/ContentExtension.build/ContentExtension.appex.xcent \
--timestamp=none \
"$BUILT_PRODUCTS_DIR/$FULL_PRODUCT_NAME/$BUNDLE_PLUGINS_FOLDER_PATH/ContentExtension.appex"

Note: ContentExtension is the target name I have. Make sure you use yours correctly

The end result is like this:

enter image description here

Don't forget to repeat the steps for every target. The best way to make sure that you have it right is to set the appex's bundleId to something totally wrong and then test all your targets on a real device. If you test it on the sim then you won't be able to validate if the code-signing works correctly or not.

FWIW it's usually a good idea to dump all your shells in one directory like and then reference them from there. But for the sake of simplicity of this post I'm not doing that.

Also make sure you see the original gist it's much smarter if you use that to change all your appexes. You just have to pass the name of the appex and then it would figure out the rest...

Solution 8 - Ios

You need to create multiple extensions for each ID, but you can create dynamic framework and just link it with each extension. Then you will not need to duplicate your code.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionEnzo TranView Question on Stackoverflow
Solution 1 - IosmalexView Answer on Stackoverflow
Solution 2 - Iosapparition47View Answer on Stackoverflow
Solution 3 - IosleonakaView Answer on Stackoverflow
Solution 4 - IosNeo ChenView Answer on Stackoverflow
Solution 5 - IosDamian RzeszotView Answer on Stackoverflow
Solution 6 - IosChristopherView Answer on Stackoverflow
Solution 7 - IosmfaaniView Answer on Stackoverflow
Solution 8 - IosAugardView Answer on Stackoverflow