原文鏈接:http://ios.jobbole.com/81583/
在上一篇教程里栏妖,你學(xué)到了如何創(chuàng)建一個(gè)可復(fù)用的把手控件拾氓。然而,還不是很容易讓其他開(kāi)發(fā)者方便的復(fù)用這個(gè)控件底哥。
一種共享它的方式就是直接提供源碼文件咙鞍。然而,這不是特別優(yōu)雅趾徽。有可能你不想共享代碼的實(shí)現(xiàn)細(xì)節(jié)续滋。此外,開(kāi)發(fā)者可能不想看見(jiàn)所有的東西孵奶,他們只是想繼承一部分代碼到自己的代碼庫(kù)里疲酌。
另一種方式是把你的代碼編譯成靜態(tài)庫(kù)來(lái)讓開(kāi)發(fā)者添加到他們的項(xiàng)目中去。然而這要求你來(lái)提供公共頭文件了袁,這樣顯得非常的笨拙朗恳。
你需要有一種簡(jiǎn)潔的方式來(lái)編譯你的代碼,并且它還要方便的共享和在多個(gè)項(xiàng)目間復(fù)用载绿。你需要的是只是將靜態(tài)庫(kù)和它的頭文件打包在一個(gè)文件里粥诫,然后只需要把這個(gè)文件添加到工程里就能立即開(kāi)始使用。
好消息是這篇教程就是圍繞這展開(kāi)的崭庸。通過(guò)制作 framework怀浆,能夠幫你解決這些迫在眉睫的問(wèn)題。OS X 對(duì)制作 framework 有著良好的支持怕享, Xcode 提供了一個(gè)工程模板执赡,它包含有一個(gè)默認(rèn)的構(gòu)建目標(biāo)還可以容納資源文件,例如圖片函筋,音頻和字體沙合。你能夠?yàn)?iOS 創(chuàng)建一個(gè) framework,但這有點(diǎn)棘手跌帐,如果你跟著我一步一步來(lái)首懈,你將會(huì)學(xué)到如何解決這些阻礙芳来。
完成這篇教程,你將:
用 Xcode 構(gòu)建一個(gè)基本靜態(tài)庫(kù)工程
構(gòu)建一個(gè)依賴(lài)與這個(gè)靜態(tài)庫(kù)的 app
探索如何把靜態(tài)庫(kù)轉(zhuǎn)換成一個(gè)完整的 framework
最后猜拾,你將會(huì)看到如何把一個(gè)圖片文件打包到 framework 中的資源包中
現(xiàn)在開(kāi)始
這篇教程的主要目的是解釋如何在 iOS 項(xiàng)目中創(chuàng)建一個(gè)可復(fù)用的 framework即舌,因此不會(huì)像本站中其他教程一樣,只會(huì)有少量的 Objective-C 代碼挎袜,用來(lái)闡述所涉及的概念顽聂。
在這里可以先下載好?RWKnobControl?資源文件。當(dāng)你在?創(chuàng)建靜態(tài)庫(kù)項(xiàng)目?這個(gè)部分里創(chuàng)建第一個(gè)工程的過(guò)程中盯仪,你會(huì)看到如何使用它們紊搪。
你要?jiǎng)?chuàng)建的所有的這些代碼和工程文件在?Github?上都能訪問(wèn)到,并且還有每個(gè)構(gòu)建部分的獨(dú)立提交全景。
什么是 Framework耀石?
Framework 是一種資源集合,它把一個(gè)靜態(tài)庫(kù)和它的頭文件匯集成一個(gè)單一結(jié)構(gòu)爸黄,這樣 Xcode 能夠很容易的將其集成到你的工程中去滞伟。
在 OS X 中允許我們創(chuàng)建動(dòng)態(tài)鏈接庫(kù)。通過(guò)動(dòng)態(tài)鏈接炕贵,framework 能夠顯式的實(shí)時(shí)更新而無(wú)需應(yīng)用程序重新鏈接它們梆奈。在運(yùn)行時(shí),一份靜態(tài)庫(kù)的副本被所有進(jìn)程共享称开,這能夠顯著減少內(nèi)存使用提升系統(tǒng)性能亩钟。如你所見(jiàn),這是個(gè)強(qiáng)大的東西鳖轰。
在 iOS 中你不能用這種方式給系統(tǒng)添加自定義的 framework清酥,因此只能使用 Apple 提供的動(dòng)態(tài)庫(kù)。
然而蕴侣,這不意味著 framework 跟 iOS 就毫無(wú)相關(guān)了焰轻。對(duì)于在不同 app 中進(jìn)行代碼復(fù)用,靜態(tài)鏈接庫(kù)仍然是一個(gè)便捷的打包方式睛蛛。
既然 framework 本質(zhì)上是對(duì)靜態(tài)庫(kù)的一站式購(gòu)物鹦马,那這篇教程中首要事情你了解如何創(chuàng)建和使用靜態(tài)庫(kù)胧谈。當(dāng)這篇教程進(jìn)展到構(gòu)建 framework 時(shí)忆肾,你會(huì)知道接下來(lái)會(huì)發(fā)生什么。
創(chuàng)建靜態(tài)庫(kù)工程
打開(kāi) Xcode 并且通過(guò)點(diǎn)擊?FileNewProject?和?iOSFramework and LibraryCocoa Touch Static Library?來(lái)創(chuàng)建一個(gè)新的靜態(tài)庫(kù)工程菱肖。
為了讓開(kāi)發(fā)者更方便的使用你的庫(kù)和框架场仲,你需要導(dǎo)入一個(gè)頭文件來(lái)訪問(wèn)所有的你希望公開(kāi)的類(lèi)和悦,好讓他們只需訪問(wèn)這個(gè)頭文件就行。
當(dāng)創(chuàng)建靜態(tài)庫(kù)工程的時(shí)候渠缕,Xcode 添加了?RWUIControls.h?和?RWUIControls.m鸽素。你不需要實(shí)現(xiàn)文件,因此右鍵?RWUIControls.m?選擇刪除亦鳞,按提示把它移到垃圾箱中馍忽。
打開(kāi)?RWUIControls.h?并且用下面的代碼替換文件內(nèi)容:
#import <UIKit/UIKit.h>
這句代碼導(dǎo)入了?UIKit?的傘型頭文件,它包含有自身所需要的庫(kù)燕差。當(dāng)你創(chuàng)建不同的組件類(lèi)時(shí)遭笋,你要把它們添加到這個(gè)文件里,這樣能夠確保它們讓這個(gè)庫(kù)的使用者能訪問(wèn)徒探。
你構(gòu)建這個(gè)工程時(shí)會(huì)依賴(lài)?UIKit瓦呼,但 Xcode 靜態(tài)庫(kù)工程沒(méi)有默認(rèn)的鏈接到?UIkit。為了修正這個(gè)問(wèn)題测暗,要添加?UIKit?作為一個(gè)依賴(lài)央串。選擇工程的導(dǎo)航器,并且在主面板選擇?RWUIControls?目標(biāo)碗啄。
單擊?Build Phases?然后展開(kāi)?Link Binary With Libraries?部分蹋辅。單擊?+?來(lái)添加一個(gè)新的框架,查找?UIKit.framework挫掏,單擊?add?添加侦另。
如果不綁定到頭文件的話,靜態(tài)包是沒(méi)有用的尉共。這些編譯好的類(lèi)和方法是包含在二進(jìn)制文件中褒傅。你創(chuàng)建的類(lèi),有一些你可以在外部使用袄友,另一個(gè)則只能在包內(nèi)使用殿托。
接下來(lái),你需要在構(gòu)件時(shí)添加引用剧蚣,把公開(kāi)的頭文件放到編譯者能使用的地方支竹。最后,你要復(fù)制這些東西到框架里鸠按。
當(dāng)你在Xcode里看到Build Phases 時(shí)礼搁,選擇 EditorAdd Build PhaseAdd Copy Headers Build Phase.
注意:如果你發(fā)現(xiàn)選項(xiàng)變灰了,試試點(diǎn)擊下方的空白區(qū)域看看目尖,然后再?lài)L試一遍馒吴。
把?RWUIControls.h?從導(dǎo)航器拖到面板的?Public?部分。這確保這個(gè)頭文件對(duì)任何使用你庫(kù)的用戶(hù)都可用。
注意:這可能有點(diǎn)多此一舉饮戳,但把包含有你工程所有公開(kāi)類(lèi)頭文件的頭文件放到公有部分非常重要豪治。否則,開(kāi)發(fā)者在企圖使用這個(gè)庫(kù)的時(shí)候會(huì)發(fā)生編譯錯(cuò)誤扯罐。這對(duì)任何人都不是開(kāi)玩笑的负拟,當(dāng) Xcode 讀取公有頭的時(shí)又不能讀取你忘記添加的公有文件。
創(chuàng)建一個(gè) UI 控件
現(xiàn)在你已經(jīng)設(shè)置好了你的工程歹河,是時(shí)候給庫(kù)添加些功能了齿椅。既然這個(gè)教程的目的是講訴如何構(gòu)建一個(gè) framework,而不是如何構(gòu)建一個(gè) UI 控件启泣,那你會(huì)借用些上篇教程的一些代碼涣脚。在你之前下載的 zip 文件你會(huì)找到 RWKnobControl 目錄。把它拖到 Xcode 的?RWUIControls?組別寥茫。
現(xiàn)在你需要分享主控件頭RWKnobControl.h 澎媒,有如下幾步要做。首先從Project 組中拖拽Copy Headers 到Public 組波桩。
另一種方式戒努,當(dāng)你編輯文件的時(shí)候會(huì)發(fā)現(xiàn)更改?Target Membership?面板中的值會(huì)更方便。當(dāng)你開(kāi)發(fā)庫(kù)繼續(xù)添加文件的時(shí)候這會(huì)非常方便镐躲。
用控件的頭文件做的另一件事就是把?RWUIControls.h?它添加到庫(kù)的主頭文件中。這樣開(kāi)發(fā)者使用你的庫(kù)時(shí)只需要像下面這樣包含這一個(gè)文件就行敌蚜,而不是一堆桥滨。
#import <RWUIControls/RWUIControls.h>
因此,把下面的代碼添加到?RWUIControls.h
// Knob Control? ??
#import? <RWUIControls/RWKnobControl.h>
配置 Build 設(shè)置
現(xiàn)在你非常接近這個(gè)工程的編譯部分了弛车。然而齐媒,有幾個(gè)確保庫(kù)盡可能對(duì)用戶(hù)友好的設(shè)置需要配置蛤奢。
首先害碾,你需要提供一個(gè)目錄名給你公有頭文件將要拷貝到那里去。這確保當(dāng)你使用靜態(tài)庫(kù)的時(shí)候能定位到相關(guān)的頭文件领追。
單擊工程導(dǎo)航欄的工程贫奠,然后選擇?RWUIControls?靜態(tài)庫(kù)目標(biāo)唬血。選擇?Build Setting?標(biāo)簽,然后搜索?public header唤崭。雙擊?Public Header Folder Path?設(shè)置并輸入下面的路徑:
之后你會(huì)看到這個(gè)目錄拷恨。
現(xiàn)在你需要改變一些其他的設(shè)置,尤其是那些保留在二進(jìn)制庫(kù)中的谢肾。編譯器給了你移除無(wú)用代碼的選項(xiàng)腕侄,指那些從不會(huì)訪問(wèn)到的代碼。并且你還能移除 debug 符號(hào)芦疏,例如函數(shù)名和其他 debug 時(shí)相關(guān)的細(xì)節(jié)冕杠。
既然你創(chuàng)建 framework 給其他人使用,那最好把它們都禁用了然后讓用戶(hù)自行選擇最適合他們工程的配置酸茴。要做這些的話分预,跟之前一樣使用搜索就行,更新下面的設(shè)置:
Dead Code Stripping?– 設(shè)為 NO
Strip Debug Symbols During Copy?– 設(shè)為 NO for all configurations
Strip Style – 設(shè)為 Non-Global Symbols
構(gòu)建運(yùn)行薪捍。你仍然什么東西看沒(méi)看到笼痹,但這仍然是件好事,這足以說(shuō)明工程成功的構(gòu)建的并且沒(méi)有警告和錯(cuò)誤酪穿。
要構(gòu)建的話与倡,選擇構(gòu)建目標(biāo)為?iOS Device?并按下?cmd+B?來(lái)執(zhí)行構(gòu)建。一旦完成昆稿,項(xiàng)目導(dǎo)航器的 Products 組別里的?libRWUIControls.a?會(huì)從紅色變?yōu)楹谏淖@表示文件已生成。右鍵?libRWUIControls.a?并且選擇?Show in Finder溉潭。
創(chuàng)建一個(gè)依賴(lài)開(kāi)發(fā)項(xiàng)目
當(dāng)你不能親眼看到你在做什么的時(shí)候馋贤,為 iOS 開(kāi)發(fā)一個(gè) UI 控件庫(kù)極其的困難,現(xiàn)在似乎就是這樣畏陕。
沒(méi)人要你盲目的工作配乓,因此在這個(gè)部分你將會(huì)創(chuàng)建一個(gè)新的 Xcode 工程,它會(huì)用到你剛創(chuàng)建的庫(kù)。這能讓你通過(guò)一個(gè)示例 app 來(lái)開(kāi)發(fā) framework犹芹。自然地崎页,這個(gè) app 的代碼會(huì)完全的與庫(kù)本身的代碼分離開(kāi)來(lái),這樣一來(lái)會(huì)讓結(jié)構(gòu)更清晰腰埂。
關(guān)閉靜態(tài)庫(kù)工程飒焦。然后創(chuàng)建一個(gè)新的工程。選擇?iOS/Application/Single View Application屿笼,并取名為?UIControlDevApp牺荠。設(shè)置類(lèi)前綴為?RW?并指定僅 iPhone 可用。最后保存到?RWUIControls?相同的目錄驴一。
把?RWUIControls.xcodeproj?拖到?UIControlDevApp?組別來(lái)把?RWUIControls?作為一個(gè)依賴(lài)項(xiàng)休雌。
注意:你不能在兩個(gè)不同的窗口中打開(kāi)同一個(gè)工程。如果你發(fā)現(xiàn)你不能切換到庫(kù)工程肝断,請(qǐng)檢查你沒(méi)有在另一個(gè) Xcode 窗口中打開(kāi)它杈曲。
你可以簡(jiǎn)單的拷貝代碼而不是重新創(chuàng)建上一篇教程的 app。首先選擇?Main.storyboard孝情,RWViewController.h?和?RWViewController.m?然后刪除它們鱼蝉。接著拷貝?DevApp?文件夾到?UIControlDevApp?組別。
現(xiàn)在添加靜態(tài)庫(kù)作為示例 app 的依賴(lài)構(gòu)建:
* 在工程中選擇?UIControlDevApp?工程箫荡。
* 導(dǎo)航至?UIControlDevApp?目標(biāo)的?Build Phases?標(biāo)簽魁亦。
* 打開(kāi)?Target Dependencies?面板并單擊 + 來(lái)顯示選擇器。
* 找到?RWUIControls?靜態(tài)庫(kù)羔挡,單擊?Add?來(lái)添加洁奈。這個(gè)動(dòng)作表示當(dāng)構(gòu)建示例 app 的時(shí)候,Xcode 會(huì)檢查是否靜態(tài)庫(kù)需要重新構(gòu)建绞灼。
為了鏈接靜態(tài)庫(kù)利术,展開(kāi)?Link Binary With Libraries?面板并再次點(diǎn)擊 +。選擇?libRWUIControls.a?單擊添加低矮。
這個(gè)行為會(huì)讓 Xcode 把示例 app 與靜態(tài)庫(kù)鏈接起來(lái)印叁,就像鏈接系統(tǒng) framework 一樣比如?UIKit。
構(gòu)建運(yùn)行军掂。你會(huì)看到跟上一 篇教程中熟悉的畫(huà)面轮蜕。
嵌套工程的好處就是你能夠在不離開(kāi)示例 app 工程的情況下繼續(xù)開(kāi)發(fā)靜態(tài)庫(kù),正如你在不同的部位維護(hù)代碼一樣蝗锥。你每次構(gòu)建項(xiàng)目的時(shí)候跃洛,你也要同時(shí)檢查 public/project 頭成員是否正確設(shè)置。如果丟失了任何必須的頭文件那么示例 app 將不會(huì)成功構(gòu)建终议。
創(chuàng)建 Framework
現(xiàn)在, 你可能會(huì)不耐煩地敲打你的腳趾并且想要知道 framework 到底什么時(shí)候才會(huì)開(kāi)始汇竭。這可以理解葱蝗,因?yàn)榈侥壳盀橹鼓阕隽艘淮蠖褨|西但還沒(méi)有看到 framework。
好的细燎,某些東西要開(kāi)始變化了两曼,馬上就來(lái)了。到現(xiàn)在你還沒(méi)有創(chuàng)建一個(gè) framework 的原因是因?yàn)樗褪且粋€(gè)靜態(tài)庫(kù)和頭文件的集合 – 正是你之前所做的找颓。
制作一個(gè) framework 會(huì)有幾點(diǎn)特別的地方:
目錄結(jié)構(gòu)合愈。Frameworks 有著 Xcode 認(rèn)可的特殊目錄結(jié)構(gòu)叮贩。你會(huì)創(chuàng)建一個(gè)構(gòu)建任務(wù)击狮,這將為你創(chuàng)建這種結(jié)構(gòu)。
當(dāng)你構(gòu)建庫(kù)的時(shí)候益老,它只會(huì)生成當(dāng)前必須的架構(gòu)彪蓬,例如 i386,arm7捺萌,等等档冬。為了讓一個(gè)框架有效,在構(gòu)建的時(shí)候它需要包含所有需要運(yùn)行的架構(gòu)桃纯。你將會(huì)創(chuàng)建一個(gè)新的產(chǎn)品酷誓,它將構(gòu)建必須的架構(gòu)并把它們放到框架中。
在這個(gè)部分會(huì)有大量的神奇腳本态坦,但我會(huì)講慢點(diǎn)盐数,它們不會(huì)很復(fù)雜。
框架結(jié)構(gòu)
正如之前提到的伞梯,一個(gè)框架有著特殊的目錄結(jié)構(gòu)玫氢,看起來(lái)像是這樣
現(xiàn)在在靜態(tài)庫(kù)編譯過(guò)程中要給它添加一個(gè)腳本。選擇?RWUIControls?工程谜诫,并選擇?RWUIControls靜態(tài)庫(kù)目標(biāo)漾峡。選擇?Build Phases?標(biāo)簽并通過(guò)選擇?Editor/Add Build Phase/Add Run Script Build Phase?來(lái)添加一個(gè)新的腳本。
在 Build Phases 部分創(chuàng)建了一個(gè)新的面板喻旷,這能讓你在編譯階段的某個(gè)時(shí)刻運(yùn)行一個(gè)任意的 Bash 腳本生逸。如果你想在編譯過(guò)程中改變腳本的運(yùn)行時(shí)刻就在列表中拖動(dòng)面板。對(duì)于框架工程來(lái)說(shuō)且预,在最后運(yùn)行腳本就行槽袄,因此你可以默認(rèn)放置即可。
雙擊重命名面板標(biāo)題為?Build Framework辣之。
把下面的 Bash 腳本粘貼到腳本框中:
set -e
export FRAMEWORK_LOCN="${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.framework"
# Create the path to the real Headers die
mkdir -p "${FRAMEWORK_LOCN}/Versions/A/Headers"
# Create the required symlinks
/bin/ln -sfh A "${FRAMEWORK_LOCN}/Versions/Current"
/bin/ln -sfh Versions/Current/Headers "${FRAMEWORK_LOCN}/Headers"
/bin/ln -sfh "Versions/Current/${PRODUCT_NAME}" \
???????????? "${FRAMEWORK_LOCN}/${PRODUCT_NAME}"
# Copy the public headers into the framework
/bin/cp -a "${TARGET_BUILD_DIR}/${PUBLIC_HEADERS_FOLDER_PATH}/" \
?????????? "${FRAMEWORK_LOCN}/Versions/A/Headers"
這段腳本首先創(chuàng)建了?RWUIControls.framework/Versions/A/Headers?目錄掰伸,然后創(chuàng)建了一個(gè)框架所必須的三個(gè)語(yǔ)法鏈接
Versions/Current => A
Headers => Versions/Current/Headers
RWUIControls => Versions/Current/RWUIControls
最后,公有頭文件從你之前指定的公有頭文件路徑拷貝到?Versions/A/Headers?目錄怀估。-a參數(shù)確保了在拷貝的時(shí)候編輯時(shí)間不會(huì)改變狮鸭,從而防止不必要的重新構(gòu)建合搅。
現(xiàn)在,選擇?RWUIControls?靜態(tài)庫(kù)方案和?iOS Device?構(gòu)建目標(biāo)歧蕉,然后通過(guò)?cmd+B?構(gòu)建灾部。
右鍵?libRWUIControls.a?并在?Finder?中顯示。
在構(gòu)建目錄中你可以訪問(wèn)到?RWUIControls.framework惯退,并確認(rèn)目錄的結(jié)構(gòu)顯示的是正確的:
在完成你框架的道路上這真是一個(gè)質(zhì)的飛躍赌髓,但你會(huì)發(fā)現(xiàn)仍然沒(méi)有一個(gè)靜態(tài)庫(kù)。這就是接下來(lái)要做的催跪。
多架構(gòu)構(gòu)建
iOS app 需要在不同的架構(gòu)上運(yùn)行:
arm7: 用于 iOS 7 所支持的最老的設(shè)備
arm7s: 用于 iPhone 5 和 5C
arm64: 用于 iPhone 5S 和 iPhone 6 等 64-bit ARM 處理器
i386: 用于 32-bit 模擬器
x86_64: 用于 64-bit 模擬器
每種架構(gòu)都需要不同的二進(jìn)制文件锁蠕,并且當(dāng)你構(gòu)建一個(gè) app 的時(shí)候,無(wú)論你當(dāng)前是何種設(shè)備 Xcode 都會(huì)正確的構(gòu)建相應(yīng)的架構(gòu)懊蒸。
這意味著構(gòu)建會(huì)很快荣倾。當(dāng)你歸檔 app 或構(gòu)建 release 模式的 app 時(shí),Xcode 會(huì)構(gòu)建所有的三種 ARM 架構(gòu)骑丸,從而讓 app 運(yùn)行到大部分設(shè)備上舌仍。那其他的版本呢?
自然地通危,當(dāng)你構(gòu)建框架時(shí)铸豁,你想要開(kāi)發(fā)者能夠盡可能使用所有的架構(gòu),對(duì)嗎菊碟?如果是這樣那表示你會(huì)得到同行的尊敬與敬佩节芥。
因此你需要讓 Xcode 構(gòu)建所有的五種架構(gòu)。這個(gè)過(guò)程會(huì)創(chuàng)建一個(gè)所謂的臃腫的庫(kù)框沟,它包含有每個(gè)架構(gòu)部分藏古。啊哈!
注意:其實(shí)這里強(qiáng)調(diào)的另一個(gè)原因是要?jiǎng)?chuàng)建一個(gè)依賴(lài)靜態(tài)庫(kù)的示例 app:這個(gè)庫(kù)只為示例 app 需要的架構(gòu)構(gòu)建忍燥,并只會(huì)在某些東西改變的時(shí)候才重新編譯拧晕。為什么這會(huì)令你異常興奮?因?yàn)檫@會(huì)讓開(kāi)發(fā)周期盡可能的縮短梅垄。
單擊 RWUIControls 工程厂捞,創(chuàng)建一個(gè)新的目標(biāo)(target)。
選擇?iOS/Other/Aggregate, 單擊?Next?并命名目標(biāo)為?Framework队丝。
注意:為什么要使用?Aggregate?目標(biāo)來(lái)構(gòu)建一個(gè) Framework 為什么不直接新建靡馁?因?yàn)?Frameworks 對(duì) OS X 的支持更好,這個(gè)事實(shí)體現(xiàn)在 Xcode 為 OS X 應(yīng)用提供了一個(gè)非常方便直接的 Cocoa Framework 構(gòu)建目標(biāo)机久。為了解決這個(gè)問(wèn)題臭墨,你要使用?Aggregate?構(gòu)建目標(biāo)(target)來(lái)做為編譯框架目錄結(jié)構(gòu)的 bash 腳本的鉤子(hook)。你開(kāi)始明白這里面瘋狂的地方了嗎膘盖?
無(wú)論何時(shí)創(chuàng)建一個(gè)新的 framework 目標(biāo)(target)都必須確保添加了靜態(tài)庫(kù)依賴(lài)胧弛。選擇 Framework 目標(biāo)(target)和?Build Phases?標(biāo)簽尤误。展開(kāi)?Target Dependencies?面板并添加靜態(tài)庫(kù)依賴(lài)。
這個(gè)目標(biāo)的主要構(gòu)建部分是多平臺(tái)編譯结缚,你將會(huì)用到腳本來(lái)執(zhí)行损晤。正如你之前所做的,在?Build Phases?中創(chuàng)建一個(gè)?Run Script红竭。
雙擊尤勋,把名字命名為?MultiPlatform Build。
粘貼下面的腳本到腳本框中:
set -e
# If we're already inside this script then die
if [ -n "$RW_MULTIPLATFORM_BUILD_IN_PROGRESS" ]; then
??exit 0
fi
export RW_MULTIPLATFORM_BUILD_IN_PROGRESS=1
RW_FRAMEWORK_NAME=${PROJECT_NAME}
RW_INPUT_STATIC_LIB="lib${PROJECT_NAME}.a"
RW_FRAMEWORK_LOCATION="${BUILT_PRODUCTS_DIR}/${RW_FRAMEWORK_NAME}.framework"
set -e?確保如果腳本的某部分失敗了那就讓整個(gè)腳本都失敗茵宪。這能幫你避免生成不完全的 framework最冰。
接下來(lái),RW_MULTIPLATFORM_BUILD_IN_PROGRESS?變量決定是否腳本有被遞歸的調(diào)用眉厨。如果有锌奴,那就退出執(zhí)行兽狭。
然后就是設(shè)置一些變量憾股。框架的名字將會(huì)跟工程名字一樣箕慧,例如?RWUIControls服球,還有靜態(tài)庫(kù)是?libRWUIControls.a。
接下來(lái)的腳本會(huì)設(shè)置些工程隨后會(huì)用到的函數(shù)颠焦。把下面的代碼添加到腳本框的底部:
function build_static_library {
????# Will rebuild the static library as specified
????#???? build_static_library sdk
????xcrun xcodebuild -project "${PROJECT_FILE_PATH}" \
???????????????????? -target "${TARGET_NAME}" \
???????????????????? -configuration "${CONFIGURATION}" \
???????????????????? -sdk "${1}" \
???????????????????? ONLY_ACTIVE_ARCH=NO \
???????????????????? BUILD_DIR="${BUILD_DIR}" \
???????????????????? OBJROOT="${OBJROOT}" \
???????????????????? BUILD_ROOT="${BUILD_ROOT}" \
???????????????????? SYMROOT="${SYMROOT}" $ACTION
}
function make_fat_library {
????# Will smash 2 static libs together
????#???? make_fat_library in1 in2 out
????xcrun lipo -create "${1}" "${2}" -output "${3}"
}
build_static_library?需要?SDK?作為參數(shù)斩熊,例如?iphoneos7.0,然后會(huì)構(gòu)建相應(yīng)的靜態(tài)庫(kù)伐庭。大部分參數(shù)都是直接從當(dāng)前的構(gòu)建任務(wù)中傳進(jìn)來(lái)粉渠,但不同的地方在于?ONLY_ACTIVE_ARCH?是用來(lái)確保為當(dāng)前的 SDK 構(gòu)建所有的架構(gòu)。
make_fat_library?使用?lipo?把兩個(gè)靜態(tài)庫(kù)變成一個(gè)圾另。它的參數(shù)是兩個(gè)輸入庫(kù)后面緊跟著輸出位置霸株。點(diǎn)擊來(lái)了解更多關(guān)于?lilp?的信息。
下個(gè)部分的腳本確定了更多變量集乔,為了你能使用上面兩個(gè)方法去件。你需要知道其他的 SDK 是什么,例如?iphoneos7.0?應(yīng)該跳轉(zhuǎn)到?iphonesimulator7.0?反之亦然扰路,還要定位 SDK 的構(gòu)建目錄尤溜。
# 1 - Extract the platform (iphoneos/iphonesimulator) from the SDK name
if [[ "$SDK_NAME" =~ ([A-Za-z]+) ]]; then
??RW_SDK_PLATFORM=${BASH_REMATCH[1]}
else
??echo "Could not find platform name from SDK_NAME: $SDK_NAME"
??exit 1
fi
# 2 - Extract the version from the SDK
if [[ "$SDK_NAME" =~ ([0-9]+.*$) ]]; then
??RW_SDK_VERSION=${BASH_REMATCH[1]}
else
??echo "Could not find sdk version from SDK_NAME: $SDK_NAME"
??exit 1
fi
# 3 - Determine the other platform
if [ "$RW_SDK_PLATFORM" == "iphoneos" ]; then
??RW_OTHER_PLATFORM=iphonesimulator
else
??RW_OTHER_PLATFORM=iphoneos
fi
# 4 - Find the build directory
if [[ "$BUILT_PRODUCTS_DIR" =~ (.*)$RW_SDK_PLATFORM$ ]]; then
??RW_OTHER_BUILT_PRODUCTS_DIR="${BASH_REMATCH[1]}${RW_OTHER_PLATFORM}"
else
??echo "Could not find other platform build directory."
??exit 1
fi
這四個(gè)語(yǔ)句看起來(lái)都非常相似,它們使用字符串比較和正則表達(dá)式來(lái)確定?RW_OTHER_PLATFORM?和?RW_OTHER_BUILT_PRODUCTS_DIR?的值汗唱。
這四個(gè)?if?語(yǔ)句的詳細(xì)解釋?zhuān)?/p>
SDK_NAME將會(huì)是?iphoneos7.0?或?iphonesimulator6.1宫莱。這個(gè)正則表達(dá)式從字符串的開(kāi)頭處開(kāi)始提取非數(shù)字字符。因此哩罪,它的結(jié)果是?iphoneos?或者?iphonesimulator授霸。
這個(gè)正則表達(dá)式從?SDK_NAME?變量取得數(shù)字版本號(hào)肥印,例如 7.0 或 6.1 等等。
這是簡(jiǎn)單的?iphonesimulator?和?iphoneos?之間的字符串比較绝葡,反之亦然深碱。
從產(chǎn)品構(gòu)建目錄路徑的末尾處得到平臺(tái)名稱(chēng)并用其他平臺(tái)替換。這個(gè)確保其他平臺(tái)的構(gòu)建目錄能被找到藏畅。當(dāng)加入兩個(gè)靜態(tài)庫(kù)的時(shí)候這至關(guān)重要敷硅。
現(xiàn)在你可以為其他平臺(tái)編譯了,隨后會(huì)加入產(chǎn)生的靜態(tài)庫(kù)愉阎。
把下面的腳本添加到末尾處:
# Build the other platform.
build_static_library "${RW_OTHER_PLATFORM}${RW_SDK_VERSION}"
# If we're currently building for iphonesimulator, then need to rebuild
#?? to ensure that we get both i386 and x86_64
if [ "$RW_SDK_PLATFORM" == "iphonesimulator" ]; then
????build_static_library "${SDK_NAME}"
fi
# Join the 2 static libs into 1 and push into the .framework
make_fat_library "${BUILT_PRODUCTS_DIR}/${RW_INPUT_STATIC_LIB}" \
???????????????? "${RW_OTHER_BUILT_PRODUCTS_DIR}/${RW_INPUT_STATIC_LIB}" \
???????????????? "${RW_FRAMEWORK_LOCATION}/Versions/A/${RW_FRAMEWORK_NAME}"
首先通過(guò)之前定義好的函數(shù)來(lái)編譯其他平臺(tái)绞蹦。
如果你當(dāng)前要為模擬器編譯,那默認(rèn)的 Xcode 只會(huì)為那個(gè)系統(tǒng)編譯榜旦,例如 i386 或者 x86_64幽七。為了編譯所有的架構(gòu),第二部分調(diào)用?build_static_library?用?iphonesimulator SDK?重新編譯溅呢,來(lái)確保編譯了所有架構(gòu)澡屡。
最后調(diào)用?make_fat_library?函數(shù)把當(dāng)前構(gòu)建目錄的靜態(tài)庫(kù)和其他構(gòu)建目錄加到一起來(lái)制作完整的多架構(gòu)靜態(tài)庫(kù)。這個(gè)會(huì)放到 framework 里面咐旧。
最后是個(gè)簡(jiǎn)單的拷貝命令的腳本驶鹉。在末尾添加下面的腳本:
# Ensure that the framework is present in both platform's build directories
cp -a "${RW_FRAMEWORK_LOCATION}/Versions/A/${RW_FRAMEWORK_NAME}" \
??????"${RW_OTHER_BUILT_PRODUCTS_DIR}/${RW_FRAMEWORK_NAME}.framework/Versions/A/${RW_FRAMEWORK_NAME}"
# Copy the framework to the user's desktop
ditto "${RW_FRAMEWORK_LOCATION}" "${HOME}/Desktop/${RW_FRAMEWORK_NAME}.framework"
第一個(gè)命令保證 framework 出現(xiàn)在多平臺(tái)的構(gòu)建目錄里。
第二個(gè)部分拷貝完成的 framework 到用戶(hù)的桌面铣墨。這是可選步驟室埋,但我發(fā)現(xiàn)把 framework 放到某個(gè)容易訪問(wèn)的地方會(huì)非常友好。
選擇?Framework?集合(aggregate) 方案伊约,并按下 cmd+B 來(lái)編譯框架姚淆。
編譯完成后桌面會(huì)出現(xiàn)?RWUIControls.framework。
為了檢查多平臺(tái)時(shí)候正確編譯屡律,啟動(dòng)終端并執(zhí)行以下操作:
$ cd ~/Desktop/RWUIControls.framework
$ RWUIControls.framework??xcrun lipo -info RWUIControls
第一行是切換到框架目錄腌逢,第二行使用了 lipo 命令來(lái)得到關(guān)于?RWUIControls?庫(kù)的相關(guān)信息。這會(huì)列出這個(gè)庫(kù)里出現(xiàn)的所有部分疹尾。
你能看到這兒有五個(gè)部分:i386, x86_64, arm7, arm7s and arm64上忍,正好是你在編譯的時(shí)候設(shè)置的。你之前運(yùn)行過(guò)?lipo -info?命令纳本,你會(huì)看到這幾個(gè)部分的子集窍蓝。
如何使用框架
好,現(xiàn)在你已經(jīng)有了一個(gè)框架和一些靜態(tài)庫(kù)繁成,它們可以?xún)?yōu)雅的解決你可能還沒(méi)遇到的問(wèn)題吓笙。但是這樣有什么意義呢?
使用框架的一個(gè)主要優(yōu)點(diǎn)就是使用起來(lái)很簡(jiǎn)單〗硗螅現(xiàn)在面睛,你使用RWUIControls.framework創(chuàng)建一個(gè)簡(jiǎn)單的IOS app絮蒿。
用Xcode創(chuàng)建一個(gè)新項(xiàng)目,選擇 File/New/Project在選擇iOS/Application/Single View Application叁鉴。調(diào)出你的新app的ImageViewer土涝;讓它只能適配iPhone 并且存放在與上兩個(gè)項(xiàng)目相同的文件夾下。這個(gè)app將給你展示如何使用RWKnobControl手動(dòng)的旋轉(zhuǎn)一張圖片幌墓。
查找ImageViewer 目錄下之前下好的zip文件但壮,里面有樣圖。把sampleImage.jpg 從finder里拖到Xcode的ImageViewer 組里常侣。
檢查Copy items into destination group’s folder 蜡饵,然后點(diǎn)擊Finish 按鈕完成導(dǎo)入。
用同樣的步驟導(dǎo)入框架胳施,把RWUIControls.framework 從桌面拖到Xcode的Frameworks 組里溯祸。同樣的,在Copy items into destination group’s folder 之前舞肆,確保你已經(jīng)檢查過(guò)
打開(kāi)RWViewController.m 并替換如下代碼:
#import "RWViewController.h"
#import <RWUIControls/RWUIControls.h>
@interface RWViewController ()
@property (nonatomic, strong) UIImageView *imageView;
@property (nonatomic, strong) RWKnobControl *rotationKnob;
@end
@implementation RWViewController
- (void)viewDidLoad
{
? ? [super viewDidLoad];
? ? // Create UIImageView
? ? CGRect frame = self.view.bounds;
? ? frame.size.height *= 2/3.0;
? ? self.imageView = [[UIImageView alloc] initWithFrame:CGRectInset(frame, 0, 20)];
? ? self.imageView.image = [UIImage imageNamed:@"sampleImage.jpg"];
? ? self.imageView.contentMode = UIViewContentModeScaleAspectFit;
? ? [self.view addSubview:self.imageView];
? ? // Create RWKnobControl
? ? frame.origin.y += frame.size.height;
? ? frame.size.height /= 2;
? ? frame.size.width? = frame.size.height;
? ? self.rotationKnob = [[RWKnobControl alloc] initWithFrame:CGRectInset(frame, 10, 10)];
? ? CGPoint center = self.rotationKnob.center;
? ? center.x = CGRectGetMidX(self.view.bounds);
? ? self.rotationKnob.center = center;
? ? [self.view addSubview:self.rotationKnob];
? ? // Set up config on RWKnobControl
? ? self.rotationKnob.minimumValue = -M_PI_4;
? ? self.rotationKnob.maximumValue = M_PI_4;
? ? [self.rotationKnob addTarget:self
? ? ? ? ? ? ? ? ? ? ? ? ? action:@selector(rotationAngleChanged:)
? ? ? ? ? ? ? ? forControlEvents:UIControlEventValueChanged];
}
- (void)rotationAngleChanged:(id)sender
{
? ? self.imageView.transform = CGAffineTransformMakeRotation(self.rotationKnob.value);
}
- (NSUInteger)supportedInterfaceOrientations
{
? ? return UIInterfaceOrientationMaskPortrait;
}
@end
這個(gè)簡(jiǎn)單的視圖控制器還需要做如下操作:
這個(gè)簡(jiǎn)單的視圖控制器還需要做如下操作:
導(dǎo)入#import? <RWUIControls/RWUIControls.h>?這個(gè)頭文件
設(shè)置UIImageView?和RWKnobControl 的私有屬性成hold
創(chuàng)建UIImageView并設(shè)置成你之前添加到項(xiàng)目里的示例圖片
創(chuàng)建RWKnobControl?焦辅,適當(dāng)?shù)恼{(diào)整它的位置。
設(shè)置下按鈕控件的屬性胆绊,包括rotationAngleChanged:方法的事件監(jiān)聽(tīng)處理
rotationAngleChanged:方法僅僅監(jiān)聽(tīng)UIImageView?的transform?屬性氨鹏,因此圖片可以隨著按鈕的移動(dòng)而旋轉(zhuǎn)。
更多的使用RWKnobControl?的細(xì)節(jié)請(qǐng)看上一章压状,里面解釋了怎么創(chuàng)建它。
Build并且運(yùn)行跟继。你就會(huì)看到一個(gè)簡(jiǎn)單的app种冬,隨著按鈕的改變而造成圖片的旋轉(zhuǎn)。
使用資源包
你注意到RWUIControls 僅由代碼和頭文件構(gòu)成了么舔糖?你沒(méi)有使用其他的資源(圖片之類(lèi))娱两。在IOS上framework只能柏涵一個(gè)頭文件和一個(gè)靜態(tài)庫(kù)。
現(xiàn)在系好安全帶金吗,我們馬上要起飛了十兢。在這一節(jié)中,你會(huì)學(xué)習(xí)到如何使用框架本身的資源包突破這個(gè)限制摇庙。
為RWUIControls 包創(chuàng)建一個(gè)新的UIView 旱物,把它放在右上角,設(shè)置圖片為一條緞帶卫袒。
創(chuàng)建bundle
資源加到bundle里之后宵呛,RWUIControls 項(xiàng)目里會(huì)形成額外的target
打開(kāi)UIControlDevApp 項(xiàng)目。選擇RWUIControls 子項(xiàng)目夕凝。點(diǎn)擊 Add Target按鈕宝穗,然后依次點(diǎn)擊OS X/Framework and Library/Bundle户秤。輸入RWUIControlsResources。 然后從框架選項(xiàng)卡中選擇Core Foundation
bundle建立之后逮矛,需要做一些設(shè)置鸡号。選擇RWUIControlsResources target然后點(diǎn)擊 Build Settings 選項(xiàng)卡。找到base sdk须鼎,選擇Base SDK這一行膜蠢,然后按下delete。這樣就能從OSX系統(tǒng)變?yōu)镮OS系統(tǒng)莉兰。
你還需要把product name改為RWUIControls. 找到product name雙擊修改挑围。把${TARGET_NAME} 替換成RWUIControls
圖片默認(rèn)情況下會(huì)有兩個(gè)分辨率,這可能造成一些有趣的結(jié)果糖荒;比如當(dāng)你包含一個(gè)retina @2x的版本時(shí)杉辙,它們會(huì)結(jié)合成一個(gè)多分辨率的TIFF格式,那并不是一件好事捶朵。找到hidpi?然后把COMBINE_HIDPI_IMAGES 設(shè)置為NO
當(dāng)你創(chuàng)建frameword時(shí)蜘矢,bundle也會(huì)建立框架并把它添加成target的附屬。選擇Framework target综看,點(diǎn)擊Build Phases選項(xiàng)卡品腹。展開(kāi)Target Dependencies 面板,點(diǎn)擊 +, 然后選擇RWUIControlsResources 添加為附屬红碑。
現(xiàn)在舞吭,在Framework target的Build Phases里,打開(kāi)MultiPlatform Build 面板析珊,添加如下代碼在末尾:
# Copy the resources bundle to the user's desktop
ditto "${BUILT_PRODUCTS_DIR}/${RW_FRAMEWORK_NAME}.bundle" \
??????"${HOME}/Desktop/${RW_FRAMEWORK_NAME}.bundle"
這個(gè)命令會(huì)復(fù)制built bundle帶用戶(hù)桌面∠叟福現(xiàn)在,建立框架忠寻,你就能看到bundle 出現(xiàn)在桌面上了惧浴。
導(dǎo)入Bundle
為了再開(kāi)發(fā)新的bundle,也要能在示例APP里用它奕剃。就需要你把它添加為附屬衷旅,然后根據(jù)app添加到相應(yīng)的對(duì)象里。
在導(dǎo)航欄項(xiàng)目中纵朋,選擇UIControlDevApp 項(xiàng)目柿顶,然后點(diǎn)擊UIControlDevApp target。擴(kuò)展RWUIControls 組倡蝙,把RWUIControls.bundle拖到 Copy Bundle Resources 面板內(nèi)九串。
在Target Dependencies 面板點(diǎn)擊加號(hào),添加成新的附屬,然后選擇RWUIControlsResources
建立一個(gè)Ribbon 視圖
從RWUIControls 項(xiàng)目里的RWUIControls 組里拖拽你之前下載好的zip文件到RWRibbon 目錄猪钮。
勾選 Copy the items into the destination group’s folder,確保他們能復(fù)制到RWUIControls 靜態(tài)包中品山。
源碼中一個(gè)很重要的地方是如何引用圖片。如果你看一眼RWRibbonView.m 文件里的addRibbonView函數(shù)烤低,你就能看到下面這行:
UIImage *image = [UIImage imageNamed:@"RWUIControls.bundle/RWRibbon"];
bundle就像一個(gè)目錄肘交,因此引用bundle里的圖片真的很簡(jiǎn)單。
想要從bundle添加圖片扑馁,可以在右邊的面板中選擇它們涯呻,它們應(yīng)該屬于RWUIControlsResources target。
我們來(lái)討論一下框架的權(quán)限范圍應(yīng)不應(yīng)該是公開(kāi)的腻要?好复罐,現(xiàn)在你需要導(dǎo)出RWRibbon.h 頭文件,選擇這個(gè)文件雄家,然后從Target Membership 面板的下拉菜單里選擇Public 效诅。
最后,你需要添加這個(gè)頭到框架的頭文件趟济。打開(kāi)RWUIControls.h 然后添加下面代碼:
// RWRibbon
#import <RWUIControls/RWRibbonView.h>
添加Ribbon到示例APP
打開(kāi)UIControlDevApp 項(xiàng)目里的RWViewController.m 乱投。然后在@interface?區(qū)間里添加下面的變量:
RWRibbonView??*_ribbonView;
創(chuàng)建ribbon視圖,添加下面代碼到viewDidLoad 的末尾
// Creates a sample ribbon view
_ribbonView = [[RWRibbonView alloc] initWithFrame:self.ribbonViewContainer.bounds];
[self.ribbonViewContainer addSubview:_ribbonView];
// Need to check that it actually works :)
UIView *sampleView = [[UIView alloc] initWithFrame:_ribbonView.bounds];
sampleView.backgroundColor = [UIColor lightGrayColor];
[_ribbonView addSubview:sampleView];
構(gòu)建顷编,運(yùn)行UIControlDevApp 戚炫,在下面,你就能看到一個(gè)新的ribbon控制器媳纬。
在ImageViewer里使用Bundle
最后想要和你分享的是怎么樣使用另一個(gè)app里的bundle双肤,我們用你之前創(chuàng)建的ImageViewer app來(lái)示范
首先確認(rèn)下你的框架和bundle是不是最新的,之后選擇Framework 然后按下cmd+B構(gòu)建它
打開(kāi)ImageViewer 項(xiàng)目层宫,找到Frameworks組里的RWUIControls.framework 選項(xiàng)杨伙,使用 Move to Trash 刪除它。然后從你的桌面拖拽RWUIControls.framework 到你的Frameworks 組里萌腿。這是一定要做的,因?yàn)樾聦?dǎo)入的框架和你之前的有許多的不同抖苦。
注意:如果Xcode拒絕添加框架毁菱,你沒(méi)有正確的刪除之前的。發(fā)送了這種情況锌历,你可以從Finder打開(kāi)ImageViewer 目錄贮庞,找到這個(gè)框架,然后刪除它究西。
從桌面拖拽bundle導(dǎo)入到ImageViewer 組窗慎。選擇Copy items into destination group’s folder 并且確保勾選了ImageViewer 選框。
你可以添加ribbon到圖片里,要像選擇它遮斥,你可以在RWViewController.m 代碼里做幾個(gè)簡(jiǎn)單的改變峦失。
打開(kāi)UIImageView?和RWRibbonView ,修改imageView?的類(lèi)型:
@property (nonatomic, strong) RWRibbonView *imageView;
為了創(chuàng)建和配置UIImageView术吗,用下面的代碼替換viewDidLoad?方法:
[super viewDidLoad];
// Create UIImageView
CGRect frame = self.view.bounds;
frame.size.height *= 2/3.0;
self.imageView = [[RWRibbonView alloc] initWithFrame:CGRectInset(frame, 0, 20)];
UIImageView *iv = [[UIImageView alloc] initWithFrame:self.imageView.bounds];
iv.image = [UIImage imageNamed:@"sampleImage.jpg"];
iv.contentMode = UIViewContentModeScaleAspectFit;
[self.imageView addSubview:iv];
[self.view addSubview:self.imageView];
構(gòu)建運(yùn)行app尉辑,現(xiàn)在你就能看到使用了theRWUIControls 框架中的RWKnobControl 和RWRibbonView 的效果了。