前言
在公司发展过程中,除了开发维护自有品牌外,针对有实力有潜质的客户,公司还会接受OEM「贴牌开发」的合作方式。在硬件产品方面,OEM方式主要体现于「外观重新开模改丝印」,「PCB重新layout」和「功能定制开发」;在App方面,主要体现于「App Logo修改」,「欢迎页面修改」,「关于我们页面修改」,「App背景颜色修改」和「功能定制开发」。
目前公司的客户中,大概有5个客户是以OEM的方式进行合作的,所以,对于App,需要采用一个容易维护的方式来管控公司及所有OEM客户的App。
问题描述
针对「前言」中描述的情况,需要解决的问题可以归纳如下:
- 6个App的代码需要共用,尽量减少代码间的差异
- 每个App的「应用名称」、「应用图标」、「欢迎页面」及「关于我们页面」均不同
- 在个别功能或页面在实现上,每个App均有一定差异性
- 每个App对应的开发者帐号均不同
解决方案
在Xcode中,有一个「Mutil-Target」功能,在苹果官方文档中对于Target是这样描述的:
「A target specifies a product to build and contains the instructions for building the product form a set of files in a project or workspace. A target defines a single product;
it organizes the inputs into the build system -- the source files and instructions for processing those source files -- required to build that product. Projects can contain one or more targets, each of which productes one product.」
这段话大概可以理解为「同一个Xcode工程中,每一个target可以对应一个独立的App
,通过target相关的配置,可以定义每个Target(即App)的build属性:例如「需要编译哪些文件」、「App名称」、「App的Bundle ID」等」。这么看来,使用Target功能,即可满足此次需求。
添加Target的过程可分为以下几步:
新建Target
根据需求,新Target的大部分内容跟原Target相似,所以我们使用「Duplicate」的方式来「复制生成」新Target。点击Target名称可以很方便地改名,假设名称改为「NewTarget」。
设置「App名称」
设置info.plist文件
添加「NewTarget」后,可以发现工程中多了一个「Target_copy-info.plist」,在此,我们将其名称改为「NewTarget-info.plist」,一般来说,此文件内容不需要修改。
在「TARGETS」中选择「NewTarget」,进入工程的「Build Setting」页面,找到「Packaging」分类,把里面的「info.plist File」改为「NewTarget-info.plist」
此时回到「General」页面,就可以对Bundle Identifier」和「Team」等选项进行修改了。
设置infoPlist.strings文件
在工程目录中新建文件夹「NewTargetInfo」。
找到工程文件夹中的infoPlist.strings文件(注意,每种语言均有一个infoPlist.strings文件),将其复制到文件夹「NewTargetInfo」中。
-
在Xcode工程中新建Group「NewTarget-Info」,将「NewTarget-info.plist」拖到该Group中,同时,通过「Add Files to “Target”」选项将步骤2)中的infoPlist.strings文件添加到Group「NewTarget-Info」。
-
选中「infoPlist.strings」,在「Utilities View」中的「Target Membership」选项,选择此「infoPlist.strings」属于哪个Target,注意,这里不能多选,只能选中「NewTarget」。
-
打开「infoPlist.strings」文件,在各个语言文件中添加下述语句,其中「AppName」根据需求填写。
"CFBundleDisplayName" = "AppName";
添加「App图标」及「启动页面」
添加「App图标」
添加「App图标」相对简单
进入「Images.xcassets」中,通过「按住Option按钮拖拽」的方式,得到原来的「AppIcon」的拷贝,将其改名为「NewTarget AppIcon」;
右键「Show in Finder」,将新的App图标按照相同的名称拷贝进文件夹,覆盖原有图标。
进入「NewTarget」的「General」页面,找到「App Icons and Launch Images」分类,在「App Icons Source」选项中选择「NewTarget AppIcon」;
添加「启动页面」
「启动页面」分两种情况,一种是IOS7及以下版本的「启动页面」,一种是IOS8及以上版本的「启动页面」
对于IOS7及以下版本
参考「App图标」的三步配置,即可为「NewTarget」创建新的「启动页面」
对于IOS8及以上版本
由于IOS8及以上版本的启动页面使用的是「Launch Screen」,而「Launch Screen」的样式只能通过修改.xib页面实现,不可编写任何代码。假设「Launch Screen」中只有一个ImageView元件,ImageView元件的image为「welcomePage.png」,那么,我们可以在工程中添加多个同名但不同内容的welcomePage.png,并通过「Target Membership」来定义不同的「welcomePage.png」分别关联哪个Target,即可实现不同Target使用不同「启动页面」的目的。
添加预编译宏
对于不同Target间代码级别的差异,我们可以通过预编译宏来实现代码预编译。进入「Target」的「Build Settings」界面,找到「Preprocessor Macros」选项,在「Debug」及「Release」选项中,按需增加宏定义。例如,为「NewTarget」添加宏定义
"TARGET_TYPE"="NEW_TARGET"
那么在代码中,即可通过预编译语句实现代码的差别编译。
#if TARGET_TYPE == NEW_TARGET
//do something for NewTarget
#else
//do something for OtherTarget
#end
Manage Schemes
最后一步!将系统默认生成的 build Scheme 名称改为「New Target Release」即可,当然,还可以按需添加不同的 scheme.
坑!!!
奇怪的编译问题
_Warning: The Copy Bundle Resources build phase contains this target's Info.plist file 'Simart-Info.plist'._
原因:在「Target-Info.plist」的「Target Membership」选项中误选了某个Target,正常情况下,「Target-Info.plist」在「Target Membership」选项中是不选择任何一个Target的。
Unable to run command 'CpResource Simart.app' - this target might include its own product.
Unable to run command 'Touch Simart.app' - this target might include its own product.
原因:在「Products」Group中的某个product的「Target Membership」中给误选了某个Target,正常情况下product不应该关联到任何一个Target。
App名称无法修改
原因:在修改「NewTarget」的「InfoPlist.strings」文件时,发现无论如何修改,App名称都会显示OriginalTarget的App名称,排查后发现,「OriginalTarget」的「InfoPlist.strings」的「Target Membership」中除了勾选了「OriginalTarget」外,还勾选了「NewTarget」,这就导致了在编译「NewTarget」时,错误地读取了「OriginalTarget」的「InfoPlist.strings」文件。
重新检查一遍所有Target「InfoPlist.strings」的「Target Membership」选项,保证关联的唯一性和正确性即可。
「启动页面」无法显示
此问题出现在IOS8.0及以上版本中。
问题具体表现为:无论使用模拟器或者真机调试,在切换Target时,均会「随机」出现「启动页面」变成白色。检查了很多遍各个Target的配置,并未发现问题。
最后,Achive每个Target的AD HOC版本,并通过fir.im进行分发测试,发现每个Target的「启动页面」均正常,难道这是Xcode-7.2.1的Bug?目前不得而知,待后续跟进...
总结
总的来说,使用「Multi-Target」方案,算是「完美地」解决了此次需求。同时,此方案也可用于「发布独立的测试版本」「发布有限制的App版本」等需求。
总而言之,只要理解了A target defines a single product
,具体怎么用就看项目的实际需求了。