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阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容