在上一篇教程裡,你學到了如何建立一個可複用的把手控制元件。然而,還不是很容易讓其他開發者方便的複用這個控制元件。
一種共享它的方式就是直接提供原始碼檔案。然而,這不是特別優雅。有可能你不想共享程式碼的實現細節。此外,開發者可能不想看見所有的東西,他們只是想繼承一部分程式碼到自己的程式碼庫裡。
另一種方式是把你的程式碼編譯成靜態庫來讓開發者新增到他們的專案中去。然而這要求你來提供公共標頭檔案,這樣顯得非常的笨拙。
你需要有一種簡潔的方式來編譯你的程式碼,並且它還要方便的共享和在多個專案間複用。你需要的是隻是將靜態庫和它的標頭檔案打包在一個檔案裡,然後只需要把這個檔案新增到工程裡就能立即開始使用。
好訊息是這篇教程就是圍繞這展開的。通過製作 framework,能夠幫你解決這些迫在眉睫的問題。OS X 對製作 framework 有著良好的支援, Xcode 提供了一個工程模板,它包含有一個預設的構建目標還可以容納資原始檔,例如圖片,音訊和字型。你能夠為 iOS 建立一個 framework,但這有點棘手,如果你跟著我一步一步來,你將會學到如何解決這些阻礙。
完成這篇教程,你將:
- 用 Xcode 構建一個基本靜態庫工程
- 構建一個依賴與這個靜態庫的 app
- 探索如何把靜態庫轉換成一個完整的 framework
- 最後,你將會看到如何把一個圖片檔案打包到 framework 中的資源包中
現在開始
這篇教程的主要目的是解釋如何在 iOS 專案中建立一個可複用的 framework,因此不會像本站中其他教程一樣,只會有少量的 Objective-C 程式碼,用來闡述所涉及的概念。
在這裡可以先下載好 RWKnobControl
資原始檔。當你在 建立靜態庫專案
這個部分裡建立第一個工程的過程中,你會看到如何使用它們。
你要建立的所有的這些程式碼和工程檔案在 Github 上都能訪問到,並且還有每個構建部分的獨立提交。
什麼是 Framework?
Framework 是一種資源集合,它把一個靜態庫和它的標頭檔案彙整合一個單一結構,這樣 Xcode 能夠很容易的將其整合到你的工程中去。
在 OS X 中允許我們建立動態連結庫。通過動態連結,framework 能夠顯式的實時更新而無需應用程式重新連結它們。在執行時,一份靜態庫的副本被所有程式共享,這能夠顯著減少記憶體使用提升系統效能。如你所見,這是個強大的東西。
在 iOS 中你不能用這種方式給系統新增自定義的 framework,因此只能使用 Apple 提供的動態庫。
然而,這不意味著 framework 跟 iOS 就毫無相關了。對於在不同 app 中進行程式碼複用,靜態連結庫仍然是一個便捷的打包方式。
既然 framework 本質上是對靜態庫的一站式購物,那這篇教程中首要事情你瞭解如何建立和使用靜態庫。當這篇教程進展到構建 framework 時,你會知道接下來會發生什麼。
建立靜態庫工程
開啟 Xcode 並且通過點選 FileNewProject
和 iOSFramework and LibraryCocoa Touch Static Library
來建立一個新的靜態庫工程。
把工程命名為 RWUIControls
並且儲存工程到一個空目錄。
一個靜態庫工程由標頭檔案和實現檔案組成,它們由工程自己建立編譯。
為了讓開發者更方便的使用你的庫和框架,你需要匯入一個標頭檔案來訪問所有的你希望公開的類,好讓他們只需訪問這個標頭檔案就行。
當建立靜態庫工程的時候,Xcode 新增了 RWUIControls.h
和 RWUIControls.m
。你不需要實現檔案,因此右鍵 RWUIControls.m
選擇刪除,按提示把它移到垃圾箱中。
開啟 RWUIControls.h
並且用下面的程式碼替換檔案內容:
1 |
#import <UIKit/UIKit.h> |
這句程式碼匯入了 UIKit
的傘型標頭檔案,它包含有自身所需要的庫。當你建立不同的元件類時,你要把它們新增到這個檔案裡,這樣能夠確保它們讓這個庫的使用者能訪問。
你構建這個工程時會依賴 UIKit
,但 Xcode 靜態庫工程沒有預設的連結到 UIkit
。為了修正這個問題,要新增 UIKit
作為一個依賴。選擇工程的導航器,並且在主皮膚選擇 RWUIControls
目標。
單擊 Build Phases
然後展開 Link Binary With Libraries
部分。單擊 +
來新增一個新的框架,查詢 UIKit.framework
,單擊 add
新增。
如果不繫結到標頭檔案的話,靜態包是沒有用的。這些編譯好的類和方法是包含在二進位制檔案中。你建立的類,有一些你可以在外部使用,另一個則只能在包內使用。
接下來,你需要在構件時新增引用,把公開的標頭檔案放到編譯者能使用的地方。最後,你要複製這些東西到框架裡。
當你在Xcode裡看到Build Phases 時,選擇 EditorAdd Build PhaseAdd Copy Headers Build Phase.
注意:如果你發現選項變灰了,試試點選下方的空白區域看看,然後再嘗試一遍。
把 RWUIControls.h
從導航器拖到皮膚的 Public
部分。這確保這個標頭檔案對任何使用你庫的使用者都可用。
注意:這可能有點多此一舉,但把包含有你工程所有公開類標頭檔案的標頭檔案放到公有部分非常重要。否則,開發者在企圖使用這個庫的時候會發生編譯錯誤。這對任何人都不是開玩笑的,當 Xcode 讀取公有頭的時又不能讀取你忘記新增的公有檔案。
建立一個 UI 控制元件
現在你已經設定好了你的工程,是時候給庫新增些功能了。既然這個教程的目的是講訴如何構建一個 framework,而不是如何構建一個 UI 控制元件,那你會借用些上篇教程的一些程式碼。在你之前下載的 zip 檔案你會找到 RWKnobControl 目錄。把它拖到 Xcode 的 RWUIControls
組別。
選擇 Copy items into destination group’s folder
並確保要拷貝的新檔案勾選了響應的單選框。
這會同時把實現檔案新增到編譯列表,預設的標頭檔案在 Project group
。這意味著它們都是私有的。
注意:這三個部分的命名如果不拆分開理解會有點令人誤解。Public
如你所預料的。Private
頭仍然會暴露你的標頭檔案,這有點讓人困惑。Project
頭是你工程用到的特定私有檔案,這有點諷刺。因此,你會慢慢發現要麼標頭檔案是放到 Public
要麼放在 Project
部分。
現在你需要分享主控制元件頭RWKnobControl.h ,有如下幾步要做。首先從Project 組中拖拽Copy Headers 到Public 組。
另一種方式,當你編輯檔案的時候會發現更改 Target Membership
皮膚中的值會更方便。當你開發庫繼續新增檔案的時候這會非常方便。
注意:在你往庫中新增新的類時,記得保持成員是最新的。儘可能減少公有的標頭檔案,並確保其餘的在 Project
組。
用控制元件的標頭檔案做的另一件事就是把 RWUIControls.h
它新增到庫的主標頭檔案中。這樣開發者使用你的庫時只需要像下面這樣包含這一個檔案就行,而不是一堆。
1 |
#import <RWUIControls/RWUIControls.h> |
因此,把下面的程式碼新增到 RWUIControls.h
1 2 |
// Knob Control #import <RWUIControls/RWKnobControl.h> |
配置 Build 設定
現在你非常接近這個工程的編譯部分了。然而,有幾個確保庫儘可能對使用者友好的設定需要配置。
首先,你需要提供一個目錄名給你公有標頭檔案將要拷貝到那裡去。這確保當你使用靜態庫的時候能定位到相關的標頭檔案。
單擊工程導航欄的工程,然後選擇 RWUIControls
靜態庫目標。選擇 Build Setting
標籤,然後搜尋 public header。雙擊 Public Header Folder Path
設定並輸入下面的路徑:
1 |
include/$(PROJECT_NAME) |
之後你會看到這個目錄。
現在你需要改變一些其他的設定,尤其是那些保留在二進位制庫中的。編譯器給了你移除無用程式碼的選項,指那些從不會訪問到的程式碼。並且你還能移除 debug 符號,例如函式名和其他 debug 時相關的細節。
既然你建立 framework 給其他人使用,那最好把它們都禁用瞭然後讓使用者自行選擇最適合他們工程的配置。要做這些的話,跟之前一樣使用搜尋就行,更新下面的設定:
Dead Code Stripping
– 設為 NOStrip Debug Symbols During Copy
– 設為 NO for all configurationsStrip Style – 設為 Non-Global Symbols
構建執行。你仍然什麼東西看沒看到,但這仍然是件好事,這足以說明工程成功的構建的並且沒有警告和錯誤。
要構建的話,選擇構建目標為 iOS Device
並按下 cmd+B
來執行構建。一旦完成,專案導航器的 Products 組別裡的 libRWUIControls.a
會從紅色變為黑色,這表示檔案已生成。右鍵 libRWUIControls.a
並且選擇 Show in Finder
。
在這個目錄中你能看到生成的靜態庫,libRWUIControls.a
,並且公有標頭檔案單獨放在 include/RWUIControls
。
建立一個依賴開發專案
當你不能親眼看到你在做什麼的時候,為 iOS 開發一個 UI 控制元件庫極其的困難,現在似乎就是這樣。
沒人要你盲目的工作,因此在這個部分你將會建立一個新的 Xcode 工程,它會用到你剛建立的庫。這能讓你通過一個示例 app 來開發 framework。自然地,這個 app 的程式碼會完全的與庫本身的程式碼分離開來,這樣一來會讓結構更清晰。
關閉靜態庫工程。然後建立一個新的工程。選擇 iOS/Application/Single View Application
,並取名為 UIControlDevApp
。設定類字首為 RW
並指定僅 iPhone 可用。最後儲存到 RWUIControls
相同的目錄。
把 RWUIControls.xcodeproj
拖到 UIControlDevApp
組別來把 RWUIControls
作為一個依賴項。
注意:你不能在兩個不同的視窗中開啟同一個工程。如果你發現你不能切換到庫工程,請檢查你沒有在另一個 Xcode 視窗中開啟它。
你可以簡單的拷貝程式碼而不是重新建立上一篇教程的 app。首先選擇 Main.storyboard
,RWViewController.h
和 RWViewController.m
然後刪除它們。接著拷貝 DevApp
資料夾到 UIControlDevApp
組別。
現在新增靜態庫作為示例 app 的依賴構建:
- * 在工程中選擇
UIControlDevApp
工程。 - * 導航至
UIControlDevApp
目標的Build Phases
標籤。 - * 開啟
Target Dependencies
皮膚並單擊 + 來顯示選擇器。 - * 找到
RWUIControls
靜態庫,單擊Add
來新增。這個動作表示當構建示例 app 的時候,Xcode 會檢查是否靜態庫需要重新構建。
為了連結靜態庫,展開 Link Binary With Libraries
皮膚並再次點選 +。選擇 libRWUIControls.a
單擊新增。
這個行為會讓 Xcode 把示例 app 與靜態庫連結起來,就像連結系統 framework 一樣比如 UIKit
。
構建執行。你會看到跟上一 篇教程中熟悉的畫面。
巢狀工程的好處就是你能夠在不離開示例 app 工程的情況下繼續開發靜態庫,正如你在不同的部位維護程式碼一樣。你每次構建專案的時候,你也要同時檢查 public/project 頭成員是否正確設定。如果丟失了任何必須的標頭檔案那麼示例 app 將不會成功構建。
建立 Framework
現在, 你可能會不耐煩地敲打你的腳趾並且想要知道 framework 到底什麼時候才會開始。這可以理解,因為到目前為止你做了一大堆東西但還沒有看到 framework。
好的,某些東西要開始變化了,馬上就來了。到現在你還沒有建立一個 framework 的原因是因為它就是一個靜態庫和標頭檔案的集合 – 正是你之前所做的。
製作一個 framework 會有幾點特別的地方:
- 目錄結構。Frameworks 有著 Xcode 認可的特殊目錄結構。你會建立一個構建任務,這將為你建立這種結構。
- 當你構建庫的時候,它只會生成當前必須的架構,例如 i386,arm7,等等。為了讓一個框架有效,在構建的時候它需要包含所有需要執行的架構。你將會建立一個新的產品,它將構建必須的架構並把它們放到框架中。
在這個部分會有大量的神奇指令碼,但我會講慢點,它們不會很複雜。
框架結構
正如之前提到的,一個框架有著特殊的目錄結構,看起來像是這樣:
現在在靜態庫編譯過程中要給它新增一個指令碼。選擇 RWUIControls
工程,並選擇 RWUIControls
靜態庫目標。選擇 Build Phases
標籤並通過選擇 Editor/Add Build Phase/Add Run Script Build Phase
來新增一個新的指令碼。
在 Build Phases 部分建立了一個新的皮膚,這能讓你在編譯階段的某個時刻執行一個任意的 Bash 指令碼。如果你想在編譯過程中改變指令碼的執行時刻就在列表中拖動皮膚。對於框架工程來說,在最後執行指令碼就行,因此你可以預設放置即可。
雙擊重新命名皮膚標題為 Build Framework
。
把下面的 Bash 指令碼貼上到指令碼框中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
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" |
這段指令碼首先建立了 RWUIControls.framework/Versions/A/Headers
目錄,然後建立了一個框架所必須的三個語法連結
Versions/Current => A
Headers => Versions/Current/Headers
RWUIControls => Versions/Current/RWUIControls
最後,公有標頭檔案從你之前指定的公有標頭檔案路徑拷貝到 Versions/A/Headers
目錄。-a
引數確保了在拷貝的時候編輯時間不會改變,從而防止不必要的重新構建。
現在,選擇 RWUIControls
靜態庫方案和 iOS Device
構建目標,然後通過 cmd+B
構建。
右鍵 libRWUIControls.a
並在 Finder
中顯示。
在構建目錄中你可以訪問到 RWUIControls.framework
,並確認目錄的結構顯示的是正確的:
在完成你框架的道路上這真是一個質的飛躍,但你會發現仍然沒有一個靜態庫。這就是接下來要做的。
多架構構建
iOS app 需要在不同的架構上執行:
- arm7: 用於 iOS 7 所支援的最老的裝置
- arm7s: 用於 iPhone 5 和 5C
- arm64: 用於 iPhone 5S 和 iPhone 6 等 64-bit ARM 處理器
- i386: 用於 32-bit 模擬器
- x86_64: 用於 64-bit 模擬器
每種架構都需要不同的二進位制檔案,並且當你構建一個 app 的時候,無論你當前是何種裝置 Xcode 都會正確的構建相應的架構。
這意味著構建會很快。當你歸檔 app 或構建 release 模式的 app 時,Xcode 會構建所有的三種 ARM 架構,從而讓 app 執行到大部分裝置上。那其他的版本呢?
自然地,當你構建框架時,你想要開發者能夠儘可能使用所有的架構,對嗎?如果是這樣那表示你會得到同行的尊敬與敬佩。
因此你需要讓 Xcode 構建所有的五種架構。這個過程會建立一個所謂的臃腫的庫,它包含有每個架構部分。啊哈!
注意:其實這裡強調的另一個原因是要建立一個依賴靜態庫的示例 app:這個庫只為示例 app 需要的架構構建,並只會在某些東西改變的時候才重新編譯。為什麼這會令你異常興奮?因為這會讓開發週期儘可能的縮短。
單擊 RWUIControls 工程,建立一個新的目標(target)。
選擇 iOS/Other/Aggregate
, 單擊 Next
並命名目標為 Framework
。
注意:為什麼要使用 Aggregate
目標來構建一個 Framework 為什麼不直接新建?因為 Frameworks 對 OS X 的支援更好,這個事實體現在 Xcode 為 OS X 應用提供了一個非常方便直接的 Cocoa Framework 構建目標。為了解決這個問題,你要使用 Aggregate
構建目標(target)來做為編譯框架目錄結構的 bash 指令碼的鉤子(hook)。你開始明白這裡面瘋狂的地方了嗎?
無論何時建立一個新的 framework 目標(target)都必須確保新增了靜態庫依賴。選擇 Framework 目標(target)和 Build Phases
標籤。展開 Target Dependencies
皮膚並新增靜態庫依賴。
這個目標的主要構建部分是多平臺編譯,你將會用到指令碼來執行。正如你之前所做的,在 Build Phases
中建立一個 Run Script
。
雙擊,把名字命名為 MultiPlatform Build
。
貼上下面的指令碼到指令碼框中:
1 2 3 4 5 6 7 8 9 10 11 |
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
確保如果指令碼的某部分失敗了那就讓整個指令碼都失敗。這能幫你避免生成不完全的 framework。- 接下來,
RW_MULTIPLATFORM_BUILD_IN_PROGRESS
變數決定是否指令碼有被遞迴的呼叫。如果有,那就退出執行。 - 然後就是設定一些變數。框架的名字將會跟工程名字一樣,例如
RWUIControls
,還有靜態庫是libRWUIControls.a
。
接下來的指令碼會設定些工程隨後會用到的函式。把下面的程式碼新增到指令碼框的底部:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
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
作為引數,例如iphoneos7.0
,然後會構建相應的靜態庫。大部分引數都是直接從當前的構建任務中傳進來,但不同的地方在於ONLY_ACTIVE_ARCH
是用來確保為當前的 SDK 構建所有的架構。make_fat_library
使用lipo
把兩個靜態庫變成一個。它的引數是兩個輸入庫後面緊跟著輸出位置。點選來了解更多關於 lilp 的資訊。
下個部分的指令碼確定了更多變數,為了你能使用上面兩個方法。你需要知道其他的 SDK 是什麼,例如 iphoneos7.0
應該跳轉到 iphonesimulator7.0
反之亦然,還要定位 SDK 的構建目錄。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
# 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 |
這四個語句看起來都非常相似,它們使用字串比較和正規表示式來確定 RW_OTHER_PLATFORM
和 RW_OTHER_BUILT_PRODUCTS_DIR
的值。
這四個 if
語句的詳細解釋:
- SDK_NAME將會是
iphoneos7.0
或iphonesimulator6.1
。這個正規表示式從字串的開頭處開始提取非數字字元。因此,它的結果是iphoneos
或者iphonesimulator
。 - 這個正規表示式從
SDK_NAME
變數取得數字版本號,例如 7.0 或 6.1 等等。 - 這是簡單的
iphonesimulator
和iphoneos
之間的字串比較,反之亦然。 - 從產品構建目錄路徑的末尾處得到平臺名稱並用其他平臺替換。這個確保其他平臺的構建目錄能被找到。當加入兩個靜態庫的時候這至關重要。
現在你可以為其他平臺編譯了,隨後會加入產生的靜態庫。
把下面的指令碼新增到末尾處:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
# 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}" |
- 首先通過之前定義好的函式來編譯其他平臺。
- 如果你當前要為模擬器編譯,那預設的 Xcode 只會為那個系統編譯,例如 i386 或者 x86_64。為了編譯所有的架構,第二部分呼叫
build_static_library
用iphonesimulator SDK
重新編譯,來確保編譯了所有架構。 - 最後呼叫
make_fat_library
函式把當前構建目錄的靜態庫和其他構建目錄加到一起來製作完整的多架構靜態庫。這個會放到 framework 裡面。
最後是個簡單的拷貝命令的指令碼。在末尾新增下面的指令碼:
1 2 3 4 5 6 |
# 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" |
- 第一個命令保證 framework 出現在多平臺的構建目錄裡。
- 第二個部分拷貝完成的 framework 到使用者的桌面。這是可選步驟,但我發現把 framework 放到某個容易訪問的地方會非常友好。
選擇 Framework
集合(aggregate) 方案,並按下 cmd+B 來編譯框架。
編譯完成後桌面會出現 RWUIControls.framework
。
為了檢查多平臺時候正確編譯,啟動終端並執行以下操作:
1 2 |
$ cd ~/Desktop/RWUIControls.framework $ RWUIControls.framework xcrun lipo -info RWUIControls |
第一行是切換到框架目錄,第二行使用了 lipo 命令來得到關於 RWUIControls
庫的相關資訊。這會列出這個庫裡出現的所有部分。
你能看到這兒有五個部分:i386, x86_64, arm7, arm7s and arm64,正好是你在編譯的時候設定的。你之前執行過 lipo -info
命令,你會看到這幾個部分的子集。
如何使用框架
好,現在你已經有了一個框架和一些靜態庫,它們可以優雅的解決你可能還沒遇到的問題。但是這樣有什麼意義呢?
使用框架的一個主要優點就是使用起來很簡單。現在,你使用RWUIControls.framework建立一個簡單的IOS app。
用Xcode建立一個新專案,選擇 File/New/Project在選擇iOS/Application/Single View Application。調出你的新app的ImageViewer;讓它只能適配iPhone 並且存放在與上兩個專案相同的資料夾下。這個app將給你展示如何使用RWKnobControl手動的旋轉一張圖片。
查詢ImageViewer 目錄下之前下好的zip檔案,裡面有樣圖。把sampleImage.jpg 從finder裡拖到Xcode的ImageViewer 組裡。
檢查Copy items into destination group’s folder ,然後點選Finish 按鈕完成匯入。
用同樣的步驟匯入框架,把RWUIControls.framework 從桌面拖到Xcode的Frameworks 組裡。同樣的,在Copy items into destination group’s folder 之前,確保你已經檢查過
開啟RWViewController.m 並替換如下程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
#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 |
這個簡單的檢視控制器還需要做如下操作:
- 這個簡單的檢視控制器還需要做如下操作:
- 匯入
#import <RWUIControls/RWUIControls.h>
這個標頭檔案 - 設定
UIImageView
和RWKnobControl 的私有屬性成hold - 建立UIImageView並設定成你之前新增到專案裡的示例圖片
- 建立
RWKnobControl
,適當的調整它的位置。 - 設定下按鈕控制元件的屬性,包括rotationAngleChanged:方法的事件監聽處理
- rotationAngleChanged:方法僅僅監聽
UIImageView
的transform
屬性,因此圖片可以隨著按鈕的移動而旋轉。
更多的使用RWKnobControl
的細節請看上一章,裡面解釋了怎麼建立它。
Build並且執行。你就會看到一個簡單的app,隨著按鈕的改變而造成圖片的旋轉。
使用資源包
你注意到RWUIControls 僅由程式碼和標頭檔案構成了麼?你沒有使用其他的資源(圖片之類)。在IOS上framework只能柏涵一個標頭檔案和一個靜態庫。
現在繫好安全帶,我們馬上要起飛了。在這一節中,你會學習到如何使用框架本身的資源包突破這個限制。
為RWUIControls 包建立一個新的UIView ,把它放在右上角,設定圖片為一條緞帶。
建立bundle
資源加到bundle裡之後,RWUIControls 專案裡會形成額外的target
開啟UIControlDevApp 專案。選擇RWUIControls 子專案。點選 Add Target按鈕,然後依次點選OS X/Framework and Library/Bundle。輸入RWUIControlsResources。 然後從框架選項卡中選擇Core Foundation
bundle建立之後,需要做一些設定。選擇RWUIControlsResources target然後點選 Build Settings 選項卡。找到base sdk,選擇Base SDK這一行,然後按下delete。這樣就能從OSX系統變為IOS系統。
你還需要把product name改為RWUIControls. 找到product name雙擊修改。把${TARGET_NAME} 替換成RWUIControls
圖片預設情況下會有兩個解析度,這可能造成一些有趣的結果;比如當你包含一個retina @2x的版本時,它們會結合成一個多解析度的TIFF格式,那並不是一件好事。找到hidpi 然後把COMBINE_HIDPI_IMAGES 設定為NO
當你建立frameword時,bundle也會建立框架並把它新增成target的附屬。選擇Framework target,點選Build Phases選項卡。展開Target Dependencies 皮膚,點選 +, 然後選擇RWUIControlsResources 新增為附屬。
現在,在Framework target的Build Phases裡,開啟MultiPlatform Build 皮膚,新增如下程式碼在末尾:
1 2 3 |
# Copy the resources bundle to the user's desktop ditto "${BUILT_PRODUCTS_DIR}/${RW_FRAMEWORK_NAME}.bundle" \ "${HOME}/Desktop/${RW_FRAMEWORK_NAME}.bundle" |
這個命令會複製built bundle帶使用者桌面。現在,建立框架,你就能看到bundle 出現在桌面上了。
匯入Bundle
為了再開發新的bundle,也要能在示例APP裡用它。就需要你把它新增為附屬,然後根據app新增到相應的物件裡。
在導航欄專案中,選擇UIControlDevApp 專案,然後點選UIControlDevApp target。擴充套件RWUIControls 組,把RWUIControls.bundle拖到 Copy Bundle Resources 皮膚內。
在Target Dependencies 皮膚點選加號,新增成新的附屬,然後選擇RWUIControlsResources
建立一個Ribbon 檢視
從RWUIControls 專案裡的RWUIControls 組裡拖拽你之前下載好的zip檔案到RWRibbon 目錄。
勾選 Copy the items into the destination group’s folder,確保他們能複製到RWUIControls 靜態包中。
原始碼中一個很重要的地方是如何引用圖片。如果你看一眼RWRibbonView.m 檔案裡的addRibbonView函式,你就能看到下面這行:
1 |
UIImage *image = [UIImage imageNamed:@"RWUIControls.bundle/RWRibbon"]; |
bundle就像一個目錄,因此引用bundle裡的圖片真的很簡單。
想要從bundle新增圖片,可以在右邊的皮膚中選擇它們,它們應該屬於RWUIControlsResources target。
我們來討論一下框架的許可權範圍應不應該是公開的?好,現在你需要匯出RWRibbon.h 標頭檔案,選擇這個檔案,然後從Target Membership 皮膚的下拉選單裡選擇Public 。
最後,你需要新增這個頭到框架的標頭檔案。開啟RWUIControls.h 然後新增下面程式碼:
1 2 |
// RWRibbon #import <RWUIControls/RWRibbonView.h> |
新增Ribbon到示例APP
開啟UIControlDevApp 專案裡的RWViewController.m 。然後在@interface
區間裡新增下面的變數:
1 |
RWRibbonView *_ribbonView; |
建立ribbon檢視,新增下面程式碼到viewDidLoad 的末尾
1 2 3 4 5 6 7 |
// 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]; |
構建,執行UIControlDevApp ,在下面,你就能看到一個新的ribbon控制器。
在ImageViewer裡使用Bundle
最後想要和你分享的是怎麼樣使用另一個app裡的bundle,我們用你之前建立的ImageViewer app來示範
首先確認下你的框架和bundle是不是最新的,之後選擇Framework 然後按下cmd+B構建它
開啟ImageViewer 專案,找到Frameworks組裡的RWUIControls.framework 選項,使用 Move to Trash 刪除它。然後從你的桌面拖拽RWUIControls.framework 到你的Frameworks 組裡。這是一定要做的,因為新匯入的框架和你之前的有許多的不同。
注意:如果Xcode拒絕新增框架,你沒有正確的刪除之前的。傳送了這種情況,你可以從Finder開啟ImageViewer 目錄,找到這個框架,然後刪除它。
從桌面拖拽bundle匯入到ImageViewer 組。選擇Copy items into destination group’s folder 並且確保勾選了ImageViewer 選框。
你可以新增ribbon到圖片裡,要像選擇它,你可以在RWViewController.m 程式碼裡做幾個簡單的改變。
開啟UIImageView
和RWRibbonView ,修改imageView
的型別:
1 |
@property (nonatomic, strong) RWRibbonView *imageView; |
為了建立和配置UIImageView,用下面的程式碼替換viewDidLoad
方法:
1 2 3 4 5 6 7 8 9 10 |
[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]; |
構建執行app,現在你就能看到使用了theRWUIControls 框架中的RWKnobControl 和RWRibbonView 的效果了。