前言
在公司發展過程中,除了開發維護自有品牌外,針對有實力有潛質的客戶,公司還會接受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
,具體怎么用就看項目的實際需求了。