iOS extensions with multiple targets
Asked Answered
A

8

56

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)?
Alterable answered 20/8, 2014 at 7:3 Comment(5)
did you find the answer of your question?Soidisant
Not yet. I have to duplicate the code for the 2nd target for the time being.Alterable
Hmm, seems like there is no way to use same code on multiple extention for nowSoidisant
Just add your extension's .appex in "Embed App Extensions" and also add it in Target Dependencies.Adjoining
See this relevant questionCrossfertilize
A
37

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

Anya answered 23/3, 2016 at 18:53 Comment(6)
It shows error: "Embedded binary's bundle identifier is not prefixed with the parent app's bundle identifier" as my both target have different bundle-ids, so in order to ad extension I need to give the extension's bundle-id which will start with (or prefixed with) target's bundle-id. So cannot add same notificaiton-service-extension for multiple target. Is there any solution?Farahfarand
@Farahfarand It is impossible to use one extension between to targets with different identifiers. At developer portal you need to make physically different extensions for Apps with different identifiers.Anya
@Anya how to do it at developer portal can you explainAreopagite
@Ankush, See the answer by apparition47 - that solves the "Embedded binary's bundle identifier is not prefixed with the parent app's bundle identifier" problem nicely.Taut
In Xcode11, Embed Binaries is now renamed to Frameworks, Libraries, and Embedded ContentCrossfertilize
Is it still works? Did it all this things on xcode 12.0 and it's not works((Lizethlizette
D
27

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.

Diagram answered 28/6, 2018 at 2:3 Comment(12)
This is a great solution and also fixes the "initial build failure" issue reported by Neo Chen.Dicrotic
You may need to edit the script if the path to your extension's plist does not match the extension's bundle ID.Dicrotic
This solution works perfectly and enables using different targets with just a single notification service extension. The script needs to be added to the pre-actions in the specific app-target. Make sure to select your target in the drop down "provide build settings for:" in the pre-actions tab otherwise it will not be executed.Lightner
I have multiple targets with different accounts and development teams. The extension requires code signing. How I can accomplish that the code signing is the same as my parent target. I’ve tried it with xcconfig files. But I cannot figure it out how to set the DEVELOPMENT_TEAM correctly for my extension. Any idea?Blanco
For anyone who didn't catch that, you need to replace "notification-service" with the name of the extension target, in my case "NotificationService".Give
I didn't get this to work, until I removed ${SRCROOT}/ on the last line beginning with /usr/libexe .... For some reason, I had to do the same when linking to my Info.plist with my secondary target (sandbox). However, I need to build twice for this to work.Give
Works fine to run locally. However, when I try to upload an archived version of each target to the App Store, I get an error which is similar to this one for one out of two targets. Anyone got an idea how to fix this? @Erumaru is this the same issue you experienced? Have you find a solution to fix it?Ductile
@Ductile sorry, I don't remember this one :)Artemas
How you did code signing for an extension? I have multiple targets and using the same extension. It works well if set extension bundle id and codesigning manually in "General". Do you have any run script that will do code signing after changing the extension bundle identifier?Hebetate
@Ductile I wrote a new answer. See if it helpsCrossfertilize
I'm getting: Set: Entry, ":CFBundleIdentifier", Does Not Exist. Any Ideas?Leif
Getting error Set: Entry, ":CFBundleIdentifier", Does Not Exist.Infertile
T
13

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
Transfusion answered 11/10, 2014 at 1:5 Comment(3)
The "Embed App Extensions" build phase is just a standard Copy Files phase with a custom name. You can easily add that yourself and put the extension.appex target in Plugins. You can change the name of the build phase too if you like.Rittenhouse
which certificates and appgroup id will use for Extension 2?Ailey
it worked, thanks. But it is not good if we have 10+ targets so we will have to create 10+ widgets for each target :(Machinegun
C
7

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

Consolatory answered 13/10, 2017 at 3:43 Comment(2)
Thats a great solution. Did you find a way how to manage to pass the first build error?Barrator
How you did code signing for an extension? I have multiple targets and using same extension. It works well if set extension bundle id and codesigning manually in "General". Do you have any run script that will do code signing after changing extension bundle identifier?Hebetate
E
6

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.

Evitaevitable answered 20/4, 2020 at 0:0 Comment(10)
error: Embedded binary's bundle identifier is not prefixed with the parent app's bundle identifier. "Embedded Binary Bundle Identifier: com.foo.NotificationContent Parent App Bundle Identifier: com.foo.staging". Got this error during the build phase, do you know how to solve?Niehaus
@Niehaus update the script - there is TO-BE-REPLACEDEvitaevitable
Yes I did replaced that string with my target's bundle ID. My guess is all my targets have different bundle ID i.e.: com.foo.dev, com.foo.staging and com.foo but the extension is default to com.foo. I will try using .xcconfig instead. Thanks for your script anyway, learnt quite a lot from it.Niehaus
can you please describe steps in detail.Baritone
@Baritone the main idea behind this solution is simple: after coping appex bundles to main app bundle we change bundle if of appex and resign themEvitaevitable
@DamianRzeszot First of all I am not able to fid proper place to put this run script. Its not build in first time or after clean buildBaritone
Why are you doing it after embed App Extensions? I'm doing it before and the appex's bundle gets changedCrossfertilize
@Honey I prefer to modify a copy of appex bundle which I get for free after appex gets embedded into the app. It doesn't matter, it's just my preference.Evitaevitable
@DamianRzeszot how you have handled code signing in Xcode11.3? I tried your scripts but I am getting $BUNDLE_PLUGINS_FOLDER_PATH == "" in Xcode11.3Hebetate
@KirtiNikam I believe this variable was added in Xcode 12. Just replace it with "PlugIns"Evitaevitable
F
4

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

Forelli answered 25/8, 2020 at 17:6 Comment(1)
If I understand it correctly, the goal is to overwrite the App's xcconfig file. For us the $(APP_BUNDLE_IDENTIFIER) is always empty though?Bradleigh
C
4

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...

Crossfertilize answered 8/12, 2020 at 21:43 Comment(1)
This still seems to be failing for archiving?Bradleigh
L
1

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.

Lakin answered 26/9, 2014 at 14:41 Comment(1)
Do you have any example/link on how to do this?Presidium

© 2022 - 2024 — McMap. All rights reserved.