iOS 完整項目制作Framework ( 下 )

原博地址 :http://ios.jobbole.com/81583/?

本篇主要是根據對制作完整版的項目作為SDK靜態庫的詳細整理,與上篇文章并沒有太大關聯,有興趣的可以從這篇文章開始一步一步的學習制作 (PS 項目中所提到的demo ,這里并沒有引入,因為博主沒有從原博客中發現其鏈接可下載地址 ,還望見諒)

完成這篇教程,你將:

用 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并且用下面的代碼替換文件內容:

Objective-C

1

#import

這句代碼導入了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它添加到庫的主頭文件中。這樣開發者使用你的庫時只需要像下面這樣包含這一個文件就行,而不是一堆。

Objective-C

1

#import

因此,把下面的代碼添加到RWUIControls.h

Objective-C

1

2// Knob Control

#import

配置 Build 設置

現在你非常接近這個工程的編譯部分了。然而,有幾個確保庫盡可能對用戶友好的設置需要配置。

首先,你需要提供一個目錄名給你公有頭文件將要拷貝到那里去。這確保當你使用靜態庫的時候能定位到相關的頭文件。

單擊工程導航欄的工程,然后選擇RWUIControls靜態庫目標。選擇Build Setting標簽,然后搜索public header。雙擊Public Header Folder Path設置并輸入下面的路徑:

Objective-C

1

include/$(PROJECT_NAME)

之后你會看到這個目錄。

現在你需要改變一些其他的設置,尤其是那些保留在二進制庫中的。編譯器給了你移除無用代碼的選項,指那些從不會訪問到的代碼。并且你還能移除 debug 符號,例如函數名和其他 debug 時相關的細節。

既然你創建 framework 給其他人使用,那最好把它們都禁用了然后讓用戶自行選擇最適合他們工程的配置。要做這些的話,跟之前一樣使用搜索就行,更新下面的設置:

Dead Code Stripping– 設為 NO

Strip Debug Symbols During Copy– 設為 NO for all configurations

Strip 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 腳本粘貼到腳本框中:

Shell

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16set-e

exportFRAMEWORK_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-sfhA"${FRAMEWORK_LOCN}/Versions/Current"

/bin/ln-sfhVersions/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。

粘貼下面的腳本到腳本框中:

Shell

1

2

3

4

5

6

7

8

9

10

11set-e

# If we're already inside this script then die

if[-n"$RW_MULTIPLATFORM_BUILD_IN_PROGRESS"];then

exit0

fi

exportRW_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。

接下來的腳本會設置些工程隨后會用到的函數。把下面的代碼添加到腳本框的底部:

Objective-C

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19functionbuild_static_library{

# Will rebuild the static library as specified

#???? build_static_library sdk

xcrunxcodebuild-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

}

functionmake_fat_library{

# Will smash 2 static libs together

#???? make_fat_library in1 in2 out

xcrunlipo-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 的構建目錄。

Objective-C

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"

exit1

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"

exit1

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."

exit1

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之間的字符串比較,反之亦然。

從產品構建目錄路徑的末尾處得到平臺名稱并用其他平臺替換。這個確保其他平臺的構建目錄能被找到。當加入兩個靜態庫的時候這至關重要。

現在你可以為其他平臺編譯了,隨后會加入產生的靜態庫。

把下面的腳本添加到末尾處:

Objective-C

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 里面。

最后是個簡單的拷貝命令的腳本。在末尾添加下面的腳本:

Objective-C

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。

為了檢查多平臺時候正確編譯,啟動終端并執行以下操作:

Objective-C

1

2$cd~/Desktop/RWUIControls.framework

$RWUIControls.frameworkxcrunlipo-infoRWUIControls

第一行是切換到框架目錄,第二行使用了 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 并替換如下代碼:

Objective-C

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

@interfaceRWViewController()

@property(nonatomic,strong)UIImageView*imageView;

@property(nonatomic,strong)RWKnobControl*rotationKnob;

@end

@implementationRWViewController

-(void)viewDidLoad

{

[superviewDidLoad];

// Create UIImageView

CGRectframe=self.view.bounds;

frame.size.height*=2/3.0;

self.imageView=[[UIImageViewalloc]initWithFrame:CGRectInset(frame,0,20)];

self.imageView.image=[UIImageimageNamed:@"sampleImage.jpg"];

self.imageView.contentMode=UIViewContentModeScaleAspectFit;

[self.viewaddSubview:self.imageView];

// Create RWKnobControl

frame.origin.y+=frame.size.height;

frame.size.height/=2;

frame.size.width=frame.size.height;

self.rotationKnob=[[RWKnobControlalloc]initWithFrame:CGRectInset(frame,10,10)];

CGPointcenter=self.rotationKnob.center;

center.x=CGRectGetMidX(self.view.bounds);

self.rotationKnob.center=center;

[self.viewaddSubview:self.rotationKnob];

// Set up config on RWKnobControl

self.rotationKnob.minimumValue=-M_PI_4;

self.rotationKnob.maximumValue=M_PI_4;

[self.rotationKnobaddTarget:self

action:@selector(rotationAngleChanged:)

forControlEvents:UIControlEventValueChanged];

}

-(void)rotationAngleChanged:(id)sender

{

self.imageView.transform=CGAffineTransformMakeRotation(self.rotationKnob.value);

}

-(NSUInteger)supportedInterfaceOrientations

{

returnUIInterfaceOrientationMaskPortrait;

}

@end

這個簡單的視圖控制器還需要做如下操作:

這個簡單的視圖控制器還需要做如下操作:

導入#import 這個頭文件

設置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 面板,添加如下代碼在末尾:

Objective-C

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函數,你就能看到下面這行:

Objective-C

1

UIImage*image=[UIImageimageNamed:@"RWUIControls.bundle/RWRibbon"];

bundle就像一個目錄,因此引用bundle里的圖片真的很簡單。

想要從bundle添加圖片,可以在右邊的面板中選擇它們,它們應該屬于RWUIControlsResources target。

我們來討論一下框架的權限范圍應不應該是公開的?好,現在你需要導出RWRibbon.h 頭文件,選擇這個文件,然后從Target Membership 面板的下拉菜單里選擇Public 。

最后,你需要添加這個頭到框架的頭文件。打開RWUIControls.h 然后添加下面代碼:

Objective-C

1

2// RWRibbon

#import <RWUIControls/RWRibbonView.h>

添加Ribbon到示例APP

打開UIControlDevApp 項目里的RWViewController.m 。然后在@interface區間里添加下面的變量:

Objective-C

1

RWRibbonView*_ribbonView;

創建ribbon視圖,添加下面代碼到viewDidLoad 的末尾

Objective-C

1

2

3

4

5

6

7// Creates a sample ribbon view

_ribbonView=[[RWRibbonViewalloc]initWithFrame:self.ribbonViewContainer.bounds];

[self.ribbonViewContaineraddSubview:_ribbonView];

// Need to check that it actually works :)

UIView*sampleView=[[UIViewalloc]initWithFrame:_ribbonView.bounds];

sampleView.backgroundColor=[UIColorlightGrayColor];

[_ribbonViewaddSubview: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的類型:

Objective-C

1

@property(nonatomic,strong)RWRibbonView*imageView;

為了創建和配置UIImageView,用下面的代碼替換viewDidLoad方法:

Objective-C

1

2

3

4

5

6

7

8

9

10[superviewDidLoad];

// Create UIImageView

CGRectframe=self.view.bounds;

frame.size.height*=2/3.0;

self.imageView=[[RWRibbonViewalloc]initWithFrame:CGRectInset(frame,0,20)];

UIImageView*iv=[[UIImageViewalloc]initWithFrame:self.imageView.bounds];

iv.image=[UIImageimageNamed:@"sampleImage.jpg"];

iv.contentMode=UIViewContentModeScaleAspectFit;

[self.imageViewaddSubview:iv];

[self.viewaddSubview:self.imageView];

構建運行app,現在你就能看到使用了theRWUIControls 框架中的RWKnobControl 和RWRibbonView 的效果了。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,818評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,185評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,656評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,647評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,446評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,951評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,041評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,189評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,718評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,602評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,800評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,316評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,045評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,419評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,671評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,420評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,755評論 2 371

推薦閱讀更多精彩內容