iOS多目标扩展

55

在iOS 8中,当我们创建一个应用程序扩展时,我们必须决定它要附加到哪个目标。扩展将与目标具有相同的Bundle ID前缀。

  1. 是否有办法之后更改目标?
  2. 如果我的项目包含2个(或更多)目标(例如一个用于debug/simulator,一个用于production/device),那么最好的处理扩展的方式是什么?我需要创建另一个扩展并复制代码(非常麻烦,需要保持两个目标的代码完全一样)吗?

1
你找到你的问题的答案了吗? - Iqbal Khan
暂时还没有。我必须先复制代码到第二个目标。 - Enzo Tran
嗯,看起来现在还没有办法在多个扩展上使用相同的代码。 - Iqbal Khan
只需将您的扩展名为 .appex 的文件添加到“嵌入式应用程序扩展”中,并将其添加到目标依赖项中即可。 - Pavan
请查看此相关问题 - mfaani
8个回答

36

要与其他目标共享一个小部件,您只需要在每个父目标的常规配置选项卡中将widget.appex目标添加到嵌入式二进制文件中。

输入图像描述

然后,您将自动在构建阶段获得嵌入应用扩展区域。

输入图像描述


18
出现错误:"嵌入二进制文件的捆绑标识符没有以父应用程序的捆绑标识符为前缀",因为我的两个目标具有不同的捆绑标识符,所以为了添加扩展,我需要提供扩展的捆绑标识符,它将以(或以...为前缀)目标的捆绑标识符开头。无法为多个目标添加相同的通知服务扩展。是否有任何解决方案? - Ankush
@Ankush 在具有不同标识符的目标之间使用一个扩展是不可能的。在开发者门户网站上,您需要为具有不同标识符的应用程序创建物理上不同的扩展。 - malex
@malex,你能解释一下如何在开发者门户上实现吗? - Sushant Jagtap
3
@Ankush,请查看apparition47的答案 - 它很好地解决了“嵌入式二进制文件的捆绑标识符未带有父应用程序的捆绑标识符前缀”的问题。 - Paul King
1
在Xcode11中,“嵌入式二进制文件”现已更名为“框架、库和嵌入式内容”。 - mfaani
它还能工作吗?我在Xcode 12.0上做了所有这些事情,但它不起作用(( - Dmitriy Greh

26
这是我的设置:我有3个目标(生产、预演、本地)和一个扩展目标,我不想重复3次。Neo Chen的答案需要澄清的是,在每个父目标方案中进行编辑:
Build > Pre-actions > New Run Script Action > Provide build settings from (parent scheme).
对于每个扩展,请粘贴以下内容:
#!/bin/bash

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

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

第一次构建似乎可以正常工作。


5
如果您的扩展程序的plist路径与扩展程序的Bundle ID不匹配,您可能需要编辑脚本。 - siburb
5
这个解决方案非常完美,可以使用单一的通知服务扩展来处理不同的目标。 需要将脚本添加到特定应用目标的“pre-actions”中。 请确保在“pre-actions”选项卡中选择您的目标,否则它将无法执行。 - dan
5
我有多个目标,使用不同的帐户和开发团队。扩展需要代码签名。我该如何实现与我的父目标相同的代码签名?我已经尝试使用xcconfig文件,但我无法弄清楚如何正确设置我的扩展的DEVELOPMENT_TEAM。有任何想法吗? - we7ee
3
在本地运行良好。但是,当我尝试将每个目标的已归档版本上传到应用商店时,其中一个目标会出现类似于此错误的错误。有人有解决办法吗?@Erumaru,您是否遇到了同样的问题?您找到了解决方法吗? - Johnson_145
3
我得到了一个错误信息:Set: Entry, ":CFBundleIdentifier", Does Not Exist。有什么想法吗? - heyfrank
显示剩余7条评论

13

看起来您只需要使用其自己的Info.plist复制扩展目标,但不需要其他任何内容。

然而,当您创建一个扩展时,Xcode会将“嵌入应用程序扩展”添加到应用程序目标的构建阶段中,如下所示,尚无UI可处理此操作。

enter image description here

尽管如此,您仍然可以为第二个目标创建扩展,然后删除除.plist文件之外的所有文件,并修复需要修复的内容。以下是逐步过程:

  • 为“Target 1”创建“Extension 1”
  • 为“Target 2”创建“Extension 2”
  • 删除为“Extension 2”创建的所有文件,仅保留其Info.plist
  • 使“Extension 2”目标的“Build Phases”与“Extension 1”的构建阶段相同。通常,这是将必要的.m文件添加到“Compile Sources”阶段以及将资源添加到“Copy Bundle Resources”阶段

5
“Embed App Extensions”构建阶段只是一个具有自定义名称的标准复制文件阶段。您可以轻松地添加该阶段并将extension.appex目标放在插件中。如果需要的话,您也可以更改构建阶段的名称。 - ohmi
Extension 2会使用哪些证书和应用组ID? - Ravi Ojha
2
它起作用了,谢谢。但是如果我们有10个以上的目标,这样做就不好了,因为我们将不得不为每个目标创建10个以上的小部件:( - pash3r

7

我创建了一个运行脚本来支持这个需求。

#!/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是我的扩展程序

将其添加到目标中,确保此脚本在Build Phases中的扩展设置之前运行,然后只需执行两次构建操作(PS:第一次会失败,我会尝试改进),它将支持多个目标。


这是一个很好的解决方案。你找到了如何处理第一个构建错误的方法吗? - gasparuff
您是如何为扩展进行代码签名的?我有多个目标,并且使用相同的扩展。如果在“常规”中手动设置扩展束 ID 和代码签名,它可以很好地工作。您是否有任何运行脚本,在更改扩展束标识符后进行代码签名? - Kirti Nikam

6

在我的项目中,我需要构建一些不同版本的应用程序(细节上有所不同,例如每个应用程序都带有不同的标志)。

假设有大约10个“应用”目标,我无法想象每个主要目标都添加Notification Content和Notification Service扩展(在这种情况下,我将总共维护30个目标 - 疯狂)。

我运行一个脚本(https://gist.github.com/damian-rzeszot/0b23ad87e5ab5d52aa15c095cbf43c59),在“嵌入应用程序扩展”阶段之后, 覆盖应用程序扩展plist和证书、应用程序版本,更改配置文件并重新签署bundle。


错误:嵌入式二进制文件的捆绑标识符未以父应用程序的捆绑标识符为前缀。 "嵌入式二进制文件捆绑标识符:com.foo.NotificationContent 父应用程序捆绑标识符:com.foo.staging"。在构建阶段遇到此错误,您知道如何解决吗? - kl.woon
@kl.woon 更新脚本 - 存在 TO-BE-REPLACED - Damian Rzeszot
是的,我已经用我的目标包ID替换了那个字符串。我猜测我的所有目标都有不同的包ID,即:com.foo.dev、com.foo.staging和com.foo,但扩展名默认为com.foo。我将尝试使用.xcconfig代替。无论如何,感谢您的脚本,我从中学到了很多。 - kl.woon
1
请您详细描述步骤。 - iKushal
@DamianRzeszot,您在Xcode11.3中如何处理代码签名?我尝试了您的脚本,但是在Xcode11.3中出现了$BUNDLE_PLUGINS_FOLDER_PATH == "" 的问题。 - Kirti Nikam
显示剩余5条评论

4

Xcconfigs可以通过修改Xcode方案来修改plist条目和代码变量,是一种很好的方式。

如果您想根据方案更改给定扩展程序的Bundle identifier

  1. 为您的扩展创建一个Xcconfig文件。我的iMessageExtension-Debug.xcconfig有以下条目:PRODUCT_BUNDLE_IDENTIFIER = $(APP_BUNDLE_IDENTIFIER).iMessageExtension

  2. 从Xcode文件检查器中点击项目>在细节面板中点击项目>信息选项卡>添加配置

  3. 我创建了一个调试配置。

  4. 在新创建的配置中,进入您的扩展程序>选择配置文件

  5. 创建一个新方案:在该新方案的运行选项卡中,您可以选择您新创建的配置。同样的过程也适用于发布环境。

  6. 我们创建的Xcconfig选项可以直接在iMessageExtension > Info.plist中使用: Bundle identifier : $(PRODUCT_BUNDLE_IDENTIFIER)

如果您需要基于xcconfig变量在代码中引用变量:

例如,我想根据选择的Xcode方案更改我的应用程序初始屏幕:

Xcconfig: INITIAL_SCREEN = tabBarHome

Plist: initialScreen : $(INITIAL_SCREEN)

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


如果我理解正确,目标是覆盖应用程序的xcconfig文件。对我们来说,“$(APP_BUNDLE_IDENTIFIER)”始终为空,是吗? - E.ws

4

编辑

2个解决方案:

说实话,我认为XCConfig解决方案要优雅得多。您只需交换东西即可,而不是尝试以非常特定的顺序覆盖所有内容...


其他答案都有必要的一部分。通过对此帖子进行一些重要修改,我能够做到正确无误。

你需要做三件事:

  • 更改应用程序扩展名(app extension)的bundleId
  • 在其包被更改后重新签名appex。虽然应用程序在模拟器上可以正常运行,但如果没有这一步,它将无法在真实设备上工作。
  • 设置适当的配置文件。(从此答案中排除,因为我还不知道如何做到这一点)

注意:在嵌入应用程序扩展名之前,您无法对其进行签名。因此,“重新签名appex”的步骤需要在“嵌入应用程序扩展名”步骤之后发生。同样,如果bundleId未以父应用程序的bundleId作为前缀,则无法嵌入应用程序扩展名。

最终顺序应如下图所示:

enter image description here

更改appex的Bundle ID

plutil的语法如下:

-replace keypath -type value 

那就这样做:

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

如果您想了解更多关于plutil的信息(请参见这里这里)。PlistBuddy有点过时了。

注意:ContentExtension是我的目标名称。请确保您正确使用您的名称。

重新签名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"

注意: 我这里的目标名是ContentExtension,请确保你正确使用自己的目标名。

最终结果如下:

enter image description here

不要忘记为每个目标重复这些步骤。确保你做对了的最好方法是将appex的bundleId设置为完全错误的内容,然后在真实设备上测试所有目标。如果你在模拟器上测试,就无法验证代码签名是否正常工作。

顺便说一句,通常最好将所有shell都放在一个目录中,然后从那里引用它们。但为了简化本文,我没有这样做。

还要确保你看到了原始Gist。如果你使用它来更改所有的appexes,它会更加智能。你只需要传递appex的名称,它就会自动解决其余的问题...


这似乎仍然在归档方面失败了? - E.ws

1
你需要为每个ID创建多个扩展,但可以创建动态框架并将其与每个扩展链接。这样,您就不需要重复编写代码。

你有关于如何做这个的示例/链接吗? - djneely

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接