iOS动画解释

我们写的应用程序往往都不是静态的,因为它们需要适应用户的需求以及为执行各种任务而改变状态。

在这些状态之间转换时,清晰的揭示正在发生什么是非常重要的。而不是在页面之间跳跃,动画帮助我们解释用户从哪里来,要到哪里去。

键盘在 view 中滑进滑出给了我们一个错觉,让我们以为它是简单的被隐藏在屏幕下方的,并且是手机很自然的一个部分。View controller 转场加强了我们的应用程序的导航结构,并且给了用户正在移向哪个方向的提示。微妙的反弹和碰撞使界面栩栩如生,并且激发出了物理的质感。要是没有这些的话,我们就只有一个没有视觉修饰的干巴巴环境了。

动画是叙述你的应用的故事的绝佳方式,在了解动画背后的基本原理之后,设计它们会轻松很多。

首要任务

在这篇文章 (以及这个话题中其余大多数文章) 中,我们将特别地针对 Core Animation 进行探讨。虽然你将看到的很多东西也可以用更高层级的 UIKit 的方法来完成,但是 Core Animation 将会让你更好的理解正在发生什么。它以一种更明确的方式来描述动画,这对这篇文章读者以及你自己的代码的读者来说都非常有用。

在看动画如何与我们在屏幕上看到的内容交互之前,我们需要快速浏览一下 Core Animation 的 CALayer,这是动画产生作用的地方。

你大概知道 UIView 实例,以及 layer-backed 的 NSView,修改它们的 layer 来委托强大的 Core Graphics 框架来进行渲染。然而,你务必要理解,当把动画添加到一个 layer 时,是不直接修改它的属性的。

取而代之,Core Animation 维护了两个平行 layer 层次结构: model layer tree(模型层树)presentation layer tree(表示层树)。前者中的 layers 反映了我们能直接看到的 layers 的状态,而后者的 layers 则是动画正在表现的值的近似。

实际上还有所谓的第三个 layer 树,叫做 rendering tree(渲染树)。因为它对 Core Animation 而言是私有的,所以我们在这里不讨论它。

考虑在 view 上增加一个渐出动画。如果在动画中的任意时刻,查看 layer 的 opacity 值,你是得不到与屏幕内容对应的透明度的。取而代之,你需要查看 presentation layer 以获得正确的结果。

虽然你可能不会去直接设置 presentation layer 的属性,但是使用它的当前值来创建新的动画或者在动画发生时与 layers 交互是非常有用的。

通过使用 -[CALayer presentationLayer]-[CALayer modelLayer],你可以在两个 layer 之间轻松切换。

基本动画

可能最常见的情况是将一个 view 的属性从一个值改变为另一个值,考虑下面这个例子:

在这里,我们让红色小火箭的 x-position 从 77.0 变为 455.0,刚好超过它的 parent view 的边。为了填充所有路径,我们需要确定我们的火箭在任意时刻所到达的位置。这通常使用线性插值法来完成:

也就是说,对于动画给定的一个分数 t,火箭的 x 坐标就是起始点的 x 坐标 77,加上一个到终点的距离 ∆x = 378 乘以该分数的值。

使用 CABasicAnimation,我们可以如下实现这个动画:

1
2
3
4
5
6
7
CABasicAnimation *animation = [CABasicAnimation animation];
animation.keyPath = @"position.x";
animation.fromValue = @77;
animation.toValue = @455;
animation.duration = 1;

[rocket.layer addAnimation:animation forKey:@"basic"];
Read on →

自定义Formatters

我们希望有一种快速的一次性的解决方案,可以把数据格式化为一种易读的格式。Foundation 框架中的就有 NSFormatter 可以很好地胜任这个工作。另外,在 Mac 上,Appkit 已经内建了 NSFormatter 的支持。

内建格式器

Foundation 框架中的 NSFormatter 是一个抽象类,它有两个已经实现的子类:NSNumberFormatterNSDateFormatter。现在我们先跳过这些,来实现我们自己的子类。

如果你想了解更多的相关知识,我推荐阅读 NSHipster

介绍

NSFormatter 除了抛出错误,其它什么事也不做。我还不知道有人想要用这个,当然如果它对你有用,就去用它吧。

因为我们不喜欢错误,我们在此实现一个 NSFormatter 的子类,它可以把 UIColor 实例转换成可读的名字。例如,以下代码可以返回字符串“Blue”:

KPAColorFormatter *colorFormatter = [[KPAColorFormatter alloc] init];
[colorFormatter stringForObjectValue:[UIColor blueColor]] // Blue

NSFormatter 的子类化有两个方法需要实现:stringForObjectValue:getObjectValue:ForString:errorDescription:。我们先开始介绍第一个方法,因为这个方法更常用。第二个方法,就我所知,经常用于 OS X 上,并且通常不是很有用,我们将稍后介绍。

初始化

首先,我们需要做些初始化的工作。由于没有事先定义好的字典可以把颜色映射至名字,这些工作将由我们来完成。为了简化,这些工作将在初始化方法中完成:

1
2
3
4
5
6
7
8
- (id)init;
{
    return [self initWithColors:@{
        [UIColor redColor]: @"Red",
        [UIColor blueColor]: @"Blue",
        [UIColor greenColor]: @"Green"
    }];
}
Read on →

消息传递机制

每个应用或多或少都由一些需要相互传递消息的对象结合起来以完成任务。在这篇文章里,我们将介绍所有可用的消息传递机制,并通过例子来介绍怎样在苹果的框架里使用。我们还会选择一些最佳范例来介绍什么时候该用什么机制。

虽然这一期的主题是关于 Foundation 框架的,但是我们会超出 Foundation 的消息传递机制 (KVO 和 通知) 来讲一讲 delegation,block 和 target-action 几种机制。

当然,有些情况下该使用什么机制没有唯一的答案,所以应该按照自己的喜好去试试。另外大多数情况下该使用什么机制应该是很清楚的。

本文中,我们会常常提及“接收者”和“发送者”。它们在消息传递中的意思可以通过以下的例子解释:一个 table view 是发送者,它的 delegate 就是接收者。Core Data managed object context 是它所发出的 notification 的发送者,获取 notification 的就是接收者。一个滑块 (slider) 是 action 消息的发送者,而实现这个 action (方法)的是它的接收者。任何修改一个支持 KVO 的对象的对象是发送者,这个 KVO 对象的观察者就是接收者。明白精髓了吗?

几种消息传递机制

首先我们来看看每种机制的具体特点。在这个基础上,下一节我们会画一个流程图来帮我们在具体情况下正确选择应该使用的机制。最后,我们会介绍一些苹果框架里的例子并且解释为什么在那些用例中会选择这样的机制。

KVO

KVO 是提供对象属性被改变时的通知的机制。KVO 的实现在 Foundation 中,很多基于 Foundation 的框架都依赖它。想要了解更多有关 KVO 的最佳实践,请阅读本期 Daniel 写的 KVO 和 KVC 文章

如果只对某个对象的值的改变感兴趣的话,就可以使用 KVO 消息传递。不过有一些前提:第一,接收者(接收对象改变的通知的对象)需要知道发送者 (值会改变的对象);第二,接收者需要知道发送者的生命周期,因为它需要在发送者被销毁前注销观察者身份。如果这两个要去符合的话,这个消息传递机制可以一对多(多个观察者可以注册观察同一个对象的变化)

If you plan to use KVO on Core Data objects, you have to know that things work a bit differently here. This has to do with Core Data’s faulting mechanism. Once a managed object turns into a fault, it will fire the observers on its properties although their values haven’t changed.

如果要在 Core Data 上使用 KVO 的话,方法会有些许差别。这和 Core Data 的 faulting 机制有关。一旦一个 managed object 被 faulting 处理的话,即使它的属性没有被改变,它还是会触发相应的观察者。

编者注 把属性值先取入缓存中,在对象需要的时候再进行一次访问,这在 Core Data 中是默认行为,这种技术称为 Faulting。这么做可以避免降低内存开销,但是如果你确定将访问结果对象的具体属性值时,可以禁用 Faults 以提高获取性能。关于这个技术更多的情况,请移步官方文档

通知

Read on →

语言标签

当我们处理自然语言(相对于程序语言而言)的时候会遇到一项挑战,即涵义模棱两可。程序语言是被设计成为有且只有一个可能解释的语言,而人类语言可能由于模糊性和不确定性衍生出很多问题。这是由于有时候你并不想确切地告诉别人你对某事物的想法。在社交场合这完全没有问题,但是当你试图使用计算机来处理人类语言的话,就会非常痛苦。

词法标识(token)就是一个简单的例子。程序语言的词法分析对于标识表示什么,它是什么类型(语句分隔符,标识符,保留关键字等等)是什么有着明确的规则。而自然语言则远不能如此清晰可辩。can’t 是一个还是两个标识?并且根据你做出的判断,cannot 或者 can not 这两个应该是相同意思的词又各是几个标识呢?很多复合词都可以写成一个词(比如:bookshelf),或者两个词(比如:lawn mower),甚至还可以用连字符来连接(比如:life-cycle)。有些字符 (比如说连字符或者右肩单撇号),可以有很多种解释,而如何选择正确字符往往取决于上下文语言环境(撇号在一个单词的最后是表示所有格符号还是后单引号?)

句子的情况同样不怎么好:如果简单认为句号是用来结束一个句子的话,在我们使用缩写或是序数的时候就悲剧了。虽然通常情况下,我们是可以解决这个问题的,但是对有些句子而言,除非将整个段落彻底分析,否则无法真正确定这些句子的意思。我们人类甚至也无法有意识地考虑这些问题。

不过我们希望能够处理人类语言,因为在跟软件交流的时候,使用人类语言对用户更加友好。我们更愿意直接告诉计算机要做什么,让计算机为我们分析报纸文章,并对我们感兴趣的新闻做个总结,而不是通过敲击键盘或者点击小小的按钮(或者在小小的虚拟键盘上打字)来让计算机为我们做这些事。其中有些还在我们的能力范围之外(至少在苹果为我们提供与 Siri 交互的 API 之前)。但是有些已经成为可能,那就是 NSLinguisticTagger

NSLinguisticTagger 是 Foundation 框架中命名极为不当的类之一,这是因为它远远不止是一个小小的词性 tagger,而是集词法分析,分词器,命名实体识别及词性标注为一体的类。换句话说,它几乎可以满足你处理某些计算机语言处理的全部要求。

为了展示 NSLinguisticTagger 类的用法,我们会开发一个灵活的工具用来搜索。我们有一个充满了文本(比如新闻,电邮,或者其他的任意文本)的集合,然后我们输入一个单词,这个单词将返回所有包含这个单词的句子。我们会忽略功能词(比如 theof 或者 and),因为它们在这个语言环境中太过于常见,没有什么用处。我们目前要实现的是第一步:从一个单独文件中提取相关单词。由此可以迅速地扩展到提供完整功能。

GitHub 上有源代码和样本文本。这是《卫报》上一篇关于中英贸易的文章。当用软件分析这份文本时,你会发现,它并不是总是运行良好,不过,出现运行故障完全正常:人类语言和任何正式语言都不同,人类语言凌乱复杂,无法简单划归到整齐划一的规则系统。很多理论问题(哪怕就像词性一样基础的问题)在某种程度上是无法解决的,这是由于我们仍然对如何才能最好地描述语言还所知甚少。比如说,词的分类是以拉丁语为依据的,但这并不意味着就必定适合英语。它们充其量只是大概近似而已。不过从很多实际的目的来看,这样就已经足够了,不需要让人怎么担心了。

Read on →

View-Layer协作

在 iOS 中,所有的 view 都是由一个底层的 layer 来驱动的。view 和它的 layer 之间有着紧密的联系,view 其实直接从 layer 对象中获取了绝大多数它所需要的数据。在 iOS 中也有一些单独的 layer,比如 AVCaptureVideoPreviewLayerCAShapeLayer,它们不需要附加到 view 上就可以在屏幕上显示内容。两种情况下其实都是 layer 在起决定作用。当然了,附加到 view 上的 layer 和单独的 layer 在行为上还是稍有不同的。

基本上你改变一个单独的 layer 的任何属性的时候,都会触发一个从旧的值过渡到新值的简单动画(这就是所谓的可动画 animatable)。然而,如果你改变的是 view 中 layer 的同一个属性,它只会从这一帧直接跳变到下一帧。尽管两种情况中都有 layer,但是当 layer 附加在 view 上时,它的默认的隐式动画的 layer 行为就不起作用了。

animatable;几乎所有的层的属性都是隐性可动画的。你可以在文档中看到它们的简介是以 ‘animatable’ 结尾的。这不仅包括了比如位置,尺寸,颜色或者透明度这样的绝大多数的数值属性,甚至也囊括了像 isHidden 和 doubleSided 这样的布尔值。 像 paths 这样的属性也是 animatable 的,但是它不支持隐式动画。

在 Core Animation 编程指南的 “How to Animate Layer-Backed Views” 中,对为什么会这样做出了一个解释:

UIView 默认情况下禁止了 layer 动画,但是在 animation block 中又重新启用了它们

这正是我们所看到的行为;当一个属性在动画 block 之外被改变时,没有动画,但是当属性在动画 block 内被改变时,就带上了动画。对于这是如何发生的这一问题的答案十分简单和优雅,它优美地阐明和揭示了 view 和 layer 之间是如何协同工作和被精心设计的。

无论何时一个可动画的 layer 属性改变时,layer 都会寻找并运行合适的 ‘action’ 来实行这个改变。在 Core Animation 的专业术语中就把这样的动画统称为动作 (action,或者 CAAction)。

CAAction:技术上来说,这是一个接口,并可以用来做各种事情。但是实际中,某种程度上你可以只把它理解为用来处理动画。

layer 将像文档中所写的的那样去寻找动作,整个过程分为五个步骤。第一步中的在 view 和 layer 中交互的部分是最有意思的:

layer 通过向它的 delegate 发送 actionForLayer:forKey: 消息来询问提供一个对应属性变化的 action。delegate 可以通过返回以下三者之一来进行响应:

Read on →

CollectionView布局动画

UICollectionView 和相关类的设置非常灵活和强大。但是灵活性一旦增强,某种程度上也增加了其复杂性: UICollectionView 比老式的 UITableView 更有深度,适用性也更强。

Collection View 深入太多了,事实上,Ole BegemanAsh Furrow 之前曾在 objc.io 上发表过 自定义 Collection View 布局UICollectionView + UIKit 力学,但是我依然有一些他们没有提及的内容可以写。在这篇文章中,我假设你已经非常熟悉 UICollectionView 的基本布局,并且至少阅读了苹果精彩的编程指南以及 Ole 之前的文章

本文的第一部分将集中讨论并举例说明如何用不同的类和方法来共同帮助实现一些常见的 UICollectionView 动画。在第二部分,我们将看一下带有 collection views 的 view controller 转场动画以及在 useLayoutToLayoutNavigationTransitions 可用时使用其进行转场,如果不可用时,我们会实现一个自定义转场动画。

你可以在 GitHub 中找到本文提到的两个示例工程:

Collection View 布局动画

标准 UICollectionViewFlowLayout 除了动画是非常容易自定义的,苹果选择了一种安全的途径去实现一个简单的淡入淡出动画作为所有布局的默认动画。如果你想实现自定义动画,最好的办法是子类化 UICollectionViewFlowLayout 并且在适当的地方实现你的动画。让我们通过一些例子来了解 UICollectionViewFlowLayout 子类中的一些方法如何协助完成自定义动画。

插入删除元素

Read on →

自定义ViewController动画

话题 #5 中,Chris Eidhof 向我们介绍了 iOS7 引入的新特性自定义 View Controller 转场. 他给出了一个 结论

我们在本文只探讨了在 navigation controller 中的两个 view controller 之间的转场动画,但是这些做法在 tab bar controller 或者任何你自己定义的 view controller 容器也是通用的

尽管从技术角度来讲,使用 iOS 7 的 API,你可以对自定义容器中的 view controllers 做自定义转场,但是这不是能直接使用的,实现这种效果非常不容易。

请注意我正在讨论的自定义视图控制器容器 (custom container view controllers) 都是 UIViewController 的直接子类,而不是 UITabBarController 或者 UINavigationController 的子类。

对于你自定义的继承于 UIViewController 的容器子类,并没有现成可用的 API 允许一个任意的动画控制器 (animation controller) 将一个子视图控制器自动转场到另外一个,不管是可交互式的转场还是不可交互式的转场。 我甚至都觉着苹果根本就不想支持这种方式。苹果支持下面的这几种转场方式:

  • Navigation controller 推入和推出页面
  • Tab bar controller 选择的改变
  • Modal 页面的展示和消失

在本文中,我将向你展示如何自定义视图控制器容器,并且使其支持第三方的动画控制器。

如果你需要复习一下 iOS 5 引入的视图控制器容器,请阅读话题#1Ricky Gregersen 写的文章 “View Controller 容器”。

预热准备

看到这里,你可能对上文我们说到的一些问题犯嘀咕,让我来告诉你答案吧:

为什么我们不直接继承 UINavigationControllerUITabBarController,并且使用它们提供的功能的?

Read on →

Layer中动画概述

默认情况下,CALayer 及其子类的绝大部分标准属性都可以执行动画,无论是添加一个 CAAnimation 到 Layer(显式动画),亦或是为属性指定一个动作然后修改它(隐式动画)。

但有时候我们希望能同时为好几个属性添加动画,使它们看起来像是一个动画一样;或者,我们需要执行的动画不能通过使用标准 Layer 属性动画来实现。

在本文中,我们将讨论如何子类化 CALayer 并添加我们自己的属性,以便比较容易地创建那些如果以其他方式实现起来会很麻烦的动画效果。

一般说来,我们希望添加到 CALayer 的子类上的可动画属性有三种类型:

  • 能间接动画 Layer (或其子类)的一个或多个标准属性的属性。
  • 能触发 Layer 背后的图像(即 contents 属性)重绘的属性。
  • 不涉及 Layer 重绘或对任何已有属性执行动画的属性。

间接属性动画

能间接修改其它标准 Layer 属性的自定义属性是这些选项中最简单的。它们仅仅只是自定义 setter 方法。然后将它们的输入转换为适用于创建动画的一个或多个不同的值。

如果被我们设置的属性已经预设好标准动画,那我们完全不需要编写任何实际的动画代码,因为我们修改这些属性后,它们就会继承任何被配置在当前 CATransaction 上的动画设置,并且自动执行动画。

换句话说,即使 CALayer 不知道如何对我们自定义的属性进行动画,它依然能对因自定义属性被改变而引起的其它可见副作用进行动画,而这恰好就是我们所需要的。

为了演示这种方法,让我们来创建一个简单的模拟时钟,之后我们可以使用被声明为 NSDate 类型 time 属性来设置它的时间。我会将从创建一个静态的时钟面盘开始。这个时钟包含三个 CAShapeLayer 实例 —— 一个用于时钟面盘的圆形 Layer 和两个用于时针和分针的长方形 Sublayer。

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
@interface ClockFace: CAShapeLayer

@property (nonatomic, strong) NSDate *time;

@end

@interface ClockFace ()

// 私有属性
@property (nonatomic, strong) CAShapeLayer *hourHand;
@property (nonatomic, strong) CAShapeLayer *minuteHand;

@end

@implementation ClockFace

- (id)init
{
    if ((self = [super init]))
    {
        self.bounds = CGRectMake(0, 0, 200, 200);
        self.path = [UIBezierPath bezierPathWithOvalInRect:self.bounds].CGPath;
        self.fillColor = [UIColor whiteColor].CGColor;
        self.strokeColor = [UIColor blackColor].CGColor;
        self.lineWidth = 4;

        self.hourHand = [CAShapeLayer layer];
        self.hourHand.path = [UIBezierPath bezierPathWithRect:CGRectMake(-2, -70, 4, 70)].CGPath;
        self.hourHand.fillColor = [UIColor blackColor].CGColor;
        self.hourHand.position = CGPointMake(self.bounds.size.width / 2, self.bounds.size.height / 2);
        [self addSublayer:self.hourHand];

        self.minuteHand = [CAShapeLayer layer];
        self.minuteHand.path = [UIBezierPath bezierPathWithRect:CGRectMake(-1, -90, 2, 90)].CGPath;
        self.minuteHand.fillColor = [UIColor blackColor].CGColor;
        self.minuteHand.position = CGPointMake(self.bounds.size.width / 2, self.bounds.size.height / 2);
        [self addSublayer:self.minuteHand];
    }
    return self;
}

@end
Read on →

理解Frame在布局的作用

Frame是布局的核心。每个开发者都使用frame定位和改变UIViewCALayer的大小。在本文中我将把焦点集中在CALayer上,因为它是UIView的底层实现,view.frame简单的返回了view.layer.frame。此外,我不会讨论setFrame:方法。虽然看起来范围十分有限,但实际上有许多有趣的事情在平凡又古老的framegetter方法中发生。

Frame依赖于什么

众所周知,frame是一个派生属性,实际上它基于一些其他的属性。实际上在计算frame值的时候会参考4个(!)属性:boundsanchorPointtransform,和position

我们从bounds开始。bounds很棘手,它混合了层的内部和外部。bounds.size定义了层本身的面积,声明了它所存在的区域。设置masksToBoundsYES会把所有子层超出bounds范围的部分裁掉。另一方面,boundsorigin属性并不影响层本身的布局;然而它会影响它内部的子层的布局方式。bounds.origin定义了层内部坐标系的原点。

这里有一个例子展示了bounds.origin如何工作。例如我们定义bounds.originCGPointMake (20.0f, 30.0f)

bounds.originbounds.origin

如何定义本地坐标系?只要把层的左上角放到bounds.origin上就行了。

bounds.originbounds.origin

anchorPoint是一个稍微有点不同的讨厌鬼。首先,它的值标准化为0.0-1.0的范围内。获得以”点”为单位的值需要用bounds.size乘以标准化的值。更重要的是,anchorPoint定义了应用变换的坐标系的原点。

anchorPointanchorPoint

变换具有相同bounds但有不同anchorPoint的层(蓝色)会有很大区别(灰色)。

position是最简单的一个概念。它定义了经过bounds.sizeanchorPointtransform的混合后,添加到层中的最终位置。

精度的快速讨论

在写这篇博客的时候,我留意到有时我的计算结果和CoreAnimation返回的计算结果相比有所出入。有可能是我计算错误或者有精度问题。我理所当然的首先检查了精度问题。幸运的是我的直觉是正确的。CGFloat在32位架构上是一个float的类型定义(在64位架构上是double),而似乎CoreAnimation并没有理会CGFloat的实际类型而在内部直接使用了double

要证实这个猜测并不困难。使用Hooper工具检查CALayerframegetter方法的执行内容,我发现了一个叫做mat4_apply_to_rect的函数。然后我在这里设置了一个符号断点,实际上也就是在CA::Mat4Impl::mat4_apply_to_rect(double const*, double*)CA::Mat4Impl::mat4_apply_to_rect(float const*, float*)上分别设置了一个断点,以确定哪一个函数被执行。当在设备上运行代码的时候,断点停在了参数是double的函数中,即使使用的是32位ARM架构的iPhone。

在一些极端情况下,使用floatdouble的差异是显而易见的。然而因为我们的目标是对CoreAnimation进行逆向工程并得到完全相同的结果,所以我们也使用double。我们定义一些和CoreGraphics中相同的非常简单的结构体。

1
2
3
4
5
6
7
8
9
10
11
typedef struct MCSDoublePoint {
	double x, y;
} MCSDoublePoint;

typedef struct MCSDoubleSize {
	double width, height;
} MCSDoubleSize;

typedef struct MCSDoubleRect {
	MCSDoublePoint origin;   MCSDoubleSize size;
} MCSDoubleRect;
Read on →

CocoaPods安装和使用教程

目录

CocoaPods是什么?

当你开发iOS应用时,会经常使用到很多第三方开源类库,比如JSONKit,AFNetWorking等等。可能某个类库又用到其他类库,所以要使用它,必须得另外下载其他类库,而其他类库又用到其他类库,“子子孙孙无穷尽也”,这也许是比较特殊的情况。总之小编的意思就是,手动一个个去下载所需类库十分麻烦。另外一种常见情况是,你项目中用到的类库有更新,你必须得重新下载新版本,重新加入到项目中,十分麻烦。如果能有什么工具能解决这些恼人的问题,那将“善莫大焉”。所以,你需要 CocoaPods。

CocoaPods应该是iOS最常用最有名的类库管理工具了,上述两个烦人的问题,通过cocoaPods,只需要一行命令就可以完全解决,当然前提是你必须正确设置它。重要的是,绝大部分有名的开源类库,都支持CocoaPods。所以,作为iOS程序员的我们,掌握CocoaPods的使用是必不可少的基本技能了。

如何下载和安装CocoaPods?

在安装CocoaPods之前,首先要在本地安装好Ruby环境。至于如何在Mac中安装好Ruby环境,请google一下,本文不再涉及。

假如你在本地已经安装好Ruby环境,那么下载和安装CocoaPods将十分简单,只需要一行命令。在Terminator(也就是终端)中输入以下命令(注意,本文所有命令都是在终端中输入并运行的。什么,你不知道什么是终端?那请小编吃饭,小编告诉你):

Read on →