不会开机的男孩

AutoLayout 相关概念介绍和动画demo

| Comments

前言

最近Apple的动作还是蛮多的,其中有3条很有意思。

  • iOS8中设备旋转,布局的变化
  • 可能的iPhone6屏幕的变化,iPhone和iPad Mac开发越来越趋于统一
  • Xcode6中Interface Builder的变化(IB中显示自定义View)

cocoa touch 开发中适配各种屏幕尺寸已经是能够预测的了,那么跟进AutoLayout 也就是必备技能了。

传统的布局是如何做的

一开始接触iOS的时候,我还是蛮喜欢他的布局系统。简单来说,一个图像,我们通过中心点坐标,旋转角度和轮廓大小来定义他在窗口中的位置

这里的坐标和笛卡尔坐标系不同的是Y的方向

1-1 The default layer geometries for iOS

这里表示了anchorPoint含义,用于表示position相对bounds的位置,比如(0.5, 0.5)表示中心,(0,0)表示左上角

1-2 The default unit coordinate systems for iOS

下面表示了frame bounds position anchorPoint之间的关系,你可能觉得这个anchorPoint似乎没有什么用

1-3, 1-4 How the anchor point affects the layer’s position property

但是当我们旋转一个View的时候,好处就来了

1-5 , 1-6 How the anchor point affects the layer’s position property

传统布局的问题

传统布局是非常高效的,组合各种变化,可以轻易得实现任意的2D动画,当然也可以轻易的解决静态的布局问题。但是在面对多个屏幕,屏幕旋转时,或是需要在2个View 中间动态增加一个View的时候显得非常繁琐。需要不断的写一些计算距离,位置的代码(甚至还有一些magic number)。网上有很多例子,比如beginning-auto-layout-part-1-of-2,或是大家在平时工作中遇到的3.5inch和4inch屏幕之间的适配。

AutoLayout

AutoLayout使用非常简单,Xcode的支持也非常直观。但是因为和之前的方式有很大的不同,新手一开始很容易遇到一大堆的异常,crash在main函数里面,让人非常沮丧。但是在了解AutoLayout之后,就会发现这是一个非常非常elegant的布局解决方案,也很容易理解为什么crash,以及应该如何debug。

constraints 约束

AutoLayout 是一个描述各种约束的行为,比如,一个View 距离父View上边距多少,相邻之间的间隔,各个View之间的宽高关系等等。这一系列的条件就是为了最终确定之前提到的传统布局中需要的东西,这个View的大小,位置。所以,当我们设置的条件不足,或是条件冲突时,就会产生异常。

Intrinsic Content Size 固有大小

在使用AutoLayout的时候,UILabel 我们只需要设定他的position,不需要设置宽高,而一个自定义的UIView,我们不仅仅需要位置,还需要设定宽高,这是为什么呢?

每一个View 都有一个特别的属性叫做Intrinsic Content Size,这个可以理解成是一个View的最合适而且最小的宽度和高度。对于UILabe来说,就是至少得把我设定的文字都显示完整吧,所以系统只需要知道UILabel的位置。而UIView的Intrinsic Content是(0,0)所以需要设置UIView的宽高(或是设定周围的边距等等其他关系可以让系统知道这个View应该多宽,多高)。而Intrinsic Content Size,也是未来自定义View显示到Xcode中必须设置的属性之一。

Phases of Display

使用AutoLayout之后,把view显示到屏幕上面大体分成3步。

  • Update constraints
  • Layout views
  • Display

一般来说layoutSubviews负责布局,比如调整View之间的距离,大小,drawRect负责绘制,比如使用什么颜色。而AutoLayout则是在layout之前增加了一个设定约束的过程,也就是上面提到了update constraints

1-7

在view的layoutSubView中,如果我们调用了[super layoutSubView] 系统就把设定的这些约束计算成每个view的bounds,center属性。当然我们也可以基于AutoLayout的结果,再做布局的调整。

1-8

Display 不是这篇文章的重点,这里略过

Alignment Rect

仔细阅读文档的同学会发现在Apple AutoLayout document中可以看到Alignment Rect 这个家伙。 AutoLayout中的Left,Right等约束,并不是针对View的frame。而是根据Alignment Rect。在绝大多数情况下Alignment = Frame。但是如果对某些需要交互的元素,而图片素材很小的时候,就可以利用Alignment把交互区域变大。可以参考UIImage 中的 imageWithAlignmentRectInsets

1-9

Animation

AutoLayout也可以配合传统的animation方法,整体代码结构如下。

1
2
3
4
5
6
7
8
  [self.view layoutIfNeeded];
  [UIView animateWithDuration:0.3f
                   animations:^{

                     //... update constraints  

                     [self.view layoutIfNeeded];
                   }];

使用AutoLayout也可以轻易的实现之前的设置frame很难实现的动画效果。比如下面的例子(很奇怪,优酷吃掉了后面几秒的动画…)

使用之前传统的动画,实现这个过程,需要计算所有subView之间的距离,位置。而且在修改一个view的frame时,很难做到和其他View的移动速度同步。除非是custom layoutsubview。做起来相当麻烦。但是用AutoLayout则非常简洁直观,只需要设置第一个View的position,然后其他view约定好高度和间隔依次排列就好了。

demo code

当然AutoLayout做动画的时候有的地方也很麻烦,比如希望旋转view A 的时候,或是使用transform时,很容易产生奇怪的结果。一般来说会设置一个host View通过AutoLayout设定位置,然后在旋转view A。一句话就是混合起来,各取优点。

其他

  • Compression Resistance
  • Content Hugging
  • 优先级

简单的来说Compression Resistance 设置view有多大意愿(优先级),愿意压缩里面的内容。Content Hugging设置view 有多大愿意(优先级),愿意显示里面内容之外的部分。

stackoverflow上面有一个很清晰的通过UIButton解释的[例子],可以很容易理解这2个属性。

参考

你和别人是相同的

| Comments

最近又洗刷了我的一个人生信条。当然其实很早很早,大概2年前就已经动摇了,只是最近彻底想明白了,很开心,记录下来。

很多人认为自己的独一无二的,包括我自己。这个独一无二的理解主要部分是认识到了人与人之间的不同。这种心理在面对选择的时候可以更好的follow your heart,虽然自己的认识很浅薄,但是却受到了上天的眷恋,好运的几个选择都看上去不错。但是上天是公平的,总会有做出错误选择的时候。这时候不得不思考是不是自己从一开始就错误了。

现在看来当然是错的,因为每个人本来天生就是不一样的,这不是明摆着么,不同的长相,不同的家庭,往基础了说,DNA 每个人都完全完全不一样。每个人都会有自己的特殊情况。但是往长远了看,人们的社会地位是一样的。

所以,从某种角度来看,你和别人是相同的

在遇到困难的时候,人们往往觉得自己是最痛苦,最迷茫的那个人(相同的倒霉事情人们往往觉得自己最痛苦,而发生在别人身上的时候却不这么认为,所以得出这个结论并不难),这个时候其实最治愈的话不是一些什么解决方案,而是“你不是一个人”。当真正的意识到这些事情别人也会遇到,别人也会有相同的感受时,才能真正的面对眼前的问题,并最后找到解决办法。相反,当好运的时候,相信这个世界上还有和你一样交好运的人,其实这并不是一件什么值得炫耀的 :)

你和别人是相同的最重要的好处在于,你可以更容易的听取别人的意见,更容易去做改变。

李笑来老师有一段我很喜欢的对井底之蛙的解释。

井底之蛙是不可能知道这世界还有很多其他的井,与它所在的那个井没什么不同。可能是大井,可能是小井;井口可能是方的,可能是圆的,但有一点是相同的,并且至关重要的,这些井的深度是青蛙跳不出去的深度。那只井底之蛙于是更不可能知道其他的井里可能有着与自己一模一样的青蛙,也可能是大一点的,也可能是小一点的;它(们)也不可能知道哪只(哪些)青蛙与自己有着一模一样的想法——尽管它可能从细微角度出发,都是独一无二的,并非100%相同的青蛙。

一开始知道自己是一只青蛙的时候,还是挺沮丧的。因为在面对现在信息膨胀的社会,我就不拿那些太成功的例子来说好了,就拿简单的考试来说。很多人都说考试是一件痛苦的事情,要大量的记背,枯燥又乏味。我们因为做不了这么苦的事情,所以考试没有考好。这是一个非常常见的说辞。类似还有很多,比如人家2个人感情好是因为经历了很多人没有经历的事情,我反正平平淡淡的,就没机会拥有好的感情等等。这些说法本质上就是承认了你和别人是不同的,并以此为理由而拒绝改变

大家不能接受平平淡淡,似乎所有的成就必须经历一段常人不能忍受的苦难。所以很多人越来越愿意追求刺激,迫切的寻找翻身的机会。但是不能不面对的就是,绝大多数情况下生活都是平平淡淡的,而这些平平淡淡生活时候的态度则决定了一个人是否能够挺过不好的事情,是否有机会抓到好运。

最近自己最大的收获,就是感恩,发现自己的人生路虽然走过了大量的弯路,自己的人生信条有超级多的不足,居然还是“成功”的活到现在,还真是受到了上天的保佑,真的应该好好珍惜这份来之不易的运气。当然受到上天眷顾的人也不仅仅是我一个人。其实每个人都受到过上天的眷顾,只是看发现了没有。

承认你和别人是相同的 需要极大的勇气~

Facebook Pop & Tweaks Demo

| Comments

示例代码

最近facebook开源了2个很有价值的project popTweaks。 facebook提供了一个非常赞的topic-Building Paper

这篇文章来简单介绍一下pop的使用,最后使用Tweaks来微小调整动画参数来达到我们最希望的效果。

这是我们最后的效果:

pop is powerful

这个动画效果很简单,有很多方式都可以做到,但是pop来实现它,只需要下面几行代码。

1
2
3
4
5
6
7
  POPBasicAnimation *animation = [POPBasicAnimation animation];
  animation.property = [self animationProperty];
  animation.fromValue = @(0);
  animation.toValue = @(8000);
  animation.duration = 2.0f;

  [self.numberLabel pop_addAnimation:animation forKey:@"numberLabelAnimation"];
1
2
3
4
5
6
7
8
9
10
11
12
  - (POPMutableAnimatableProperty *)animationProperty {
  return [POPMutableAnimatableProperty
      propertyWithName:@"com.curer.test"
           initializer:^(POPMutableAnimatableProperty *prop) {
               prop.writeBlock = ^(id obj, const CGFloat values[]) {
                 UILabel *label = (UILabel *)obj;
                 NSNumber *number = @(values[0]);
                 int num = [number intValue];
                 label.text = [@(num) stringValue];
               };
           }];
}

哈哈,搞定了。pop太强大了。但是细心的同学会发现动画似乎不是我们想要的,我们希望做到那种一开始很快速很激动,最后却有一点慢慢的“欲求不能”的感觉。

很直观的,我们使用了万能的EaseOut动画

1
2
3
4
5
6
7
  POPBasicAnimation *animation = [POPBasicAnimation animation];
  animation.property = [self animationProperty];
  animation.fromValue = @(0);
  animation.toValue = @(8000);
  animation.duration = 2.0f;
  //增加animation 时间函数控制
  animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];

增加了一行代码,但是发现这个动画变化的时间还是不能让我们满意,一开始变化的还是慢,后面变化的又有点快。

how can we better build animation

动画的实现其实很简单,抛开性能,就是一个个不断变化的图片,对于我们这个简单的动画,就是一个从0到8000的变化,如果x轴为时间,y轴为大小。我们第一个动画其实是这个样子

easeout好一点是这样子

我们其实希望是这个样子

CAMediaTimingFunction 实际上还提供另一个方法,不是很常用,但是却非常适合我们现在的场景。

1
+ (id)functionWithControlPoints:(float)c1x :(float)c1y :(float)c2x :(float)c2y;

这里我们描述的“时间函数”其实就是贝塞尔曲线

这里推荐一个网站可以很直观的生成贝塞尔曲线。 这里我们得到了参数(.12,1,.11,.94)。

1
2
3
4
5
6
7
  POPBasicAnimation *animation = [POPBasicAnimation animation];
  animation.property = [self animationProperty];
  animation.fromValue = @(0);
  animation.toValue = @(8000);
  animation.duration = 2.0f;
  //修改animation 时间函数
  animation.timingFunction = [CAMediaTimingFunction functionWithControlPoints:0.12 :1: 0.11:0.94];

这里我们已经得到我们想要的动画效果了。而且看上去相当不错。

how can we better build animation

如何才能有更好的效果呢?动画的速度,时间,等等参数都会影响到动画的效果是不是会完美。如何判断动画效果是否足够好。的确是个很难的问题。而解决这个问题的关键,不在于工程师自己折腾,应该找专业的人来做。而这时Tweaks就闪亮登场了。

初始化的时候创建2个tweak用来动态调整时间和目标数值。并修改一下默认的UIWindow为FBTweakShakeWindow

1
2
3
4
5
6
7
8
9
10
11
12
  //reset window
  self.window = [[FBTweakShakeWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];

  FBTweak *animationDurationTweak =
      FBTweakInline(@"Content", @"Animation", @"Duration", 2.0, 1.0, 3.0);
  animationDurationTweak.stepValue = [NSNumber numberWithFloat:0.1f];
  animationDurationTweak.precisionValue = [NSNumber numberWithFloat:3.0f];

  FBTweak *animationToValueTweak =
      FBTweakInline(@"Content", @"Animation", @"ToValue", 8000, 1000, 10000);
  animationToValueTweak.stepValue = @(1000);
  animationDurationTweak.precisionValue = [NSNumber numberWithFloat:1.0f];

再把原来创建动画的代码稍微修正一下

1
2
3
4
5
6
7
8
9
10
11
12
  POPBasicAnimation *animation = [POPBasicAnimation animation];
  animation.property = [self animationProperty];
  animation.timingFunction = [CAMediaTimingFunction functionWithControlPoints:0.12 :1: 0.11:0.94];
  animation.fromValue = @(0);

  double animationDuration =
      FBTweakValue(@"Content", @"Animation", @"Duration", 2.0);
  animation.toValue =
      @(FBTweakValue(@"Content", @"Animation", @"ToValue", 8000));
  animation.duration = animationDuration;

  [self.numberLabel pop_addAnimation:animation forKey:@"numberLabelAnimation"];

这样当摇晃手机的时候就可以动态调整动画参数了,最后数据会保存在plist :)。

越简单的越强大~

Design Patterns in iOS — Class Clusters

| Comments

我对设计模式一直都是一个若有若无的感觉,特别是在手机端开发,觉得用处不是很大,认为设计模式是为了大规模团队合作,分工才能体现出效果。设计模式可以通过分不同的“层”让大家协同开发,相互之间不产生影响。但是最近看法有点改变,觉得还是需要多少了解一些。

天天使用的framework确实是一个庞大的项目,从framework的设计中可以找到很多设计模式的影子,而且还是一个很好的生产化的例子。这里先介绍 Class Clusters

Class Clusters 几乎涉及到iOS日常的所有开发过程中,也可能正是这样,导致我们很容易把它彻底遗忘。这里就拿最常用的 NSString 来讲。

1
2
3
4
5
6
7
8
9
10
  NSString *string1 = @"helloworld";
  NSString *string2 = [[NSString alloc] initWithFormat:@":%@", @"helloworld"];
  NSString *string3 = [NSHomeDirectory() stringByAppendingPathComponent:string1];
  NSTextStorage *storage = [[NSTextStorage alloc] initWithString:string1];
  NSString *string4 = [storage string];

  NSLog(@"%@", [[string1 class] description]);
  NSLog(@"%@", [[string2 class] description]);
  NSLog(@"%@", [[string3 class] description]);
  NSLog(@"%@", [[string4 class] description]);

不知道有多少人试过哈,string3的返回还是让我吃了一惊。下面的结果是在Xcode5.1 SDK7.1 下的结果。

__NSCFConstantString
__NSCFString
NSPathStore2
NSBigMutableString

通过上面的方法创建的 NSString 最后都产生了不同的子类。有人可能会奇怪为什么需要不同的 NSString。因为对于大部分的以阅读内容为主的App来讲,很大部分资源消耗在了字符串处理上面(存储,解析,比较等等),所以对于字符串的存储需要有不同的方式来满足不同的情况,这样才能有性能上的提高。

Note: 设想一下,在这些场景上面,如果Apple直接把这些类扔给开发者,会有什么问题呢?

那么开发者需要自己在不同的场景决定使用不同的子类,不仅学习成本提高,而且也容易生成性能不太好的代码。
现在简单的 NSString 就可以直接覆盖上面的所有场景。而且随着iOS的软硬件的后续开发,开发者还可以在不修改代码的情况下获得性能提升。

既然看到了它的强大之处,那么就开始了解吧。 既然这是第一篇DesignPattern那么就从最简单开始 :)

Abstract Classes

这里引用一下Mike的内容

An abstract class is a class which is not fully functional on its own. It must be subclassed, and the subclass must fill out the missing functionality.

An abstract class is not necessarily an empty shell. It can still contain a lot of functionality all on its own, but it’s not complete without a subclass to fill in the holes.

Abstract Class 的概念很简单,类中所有的方法不需要全部有具体的实现,相当于定义了很多的接口。比如一开始的 NSString

Class Clusters

A class cluster is a hierarchy of classes capped off by a public abstract class. The public class provides an interface and a lot of auxiliary functionality, and then core functionality is implemented by private subclasses. The public class then provides creation methods which return instances of the private subclasses, so that the public class can be used without knowledge of those subclasses.

Clusters的角色不仅要实现 Abstract Class 的方法,还需要自己实现自己的特殊化需求。Abstract Class 负责提供一个“外壳”,真正“干活”的就是Cluster class。这样外部就只需要了解Abstract Class就可以了。

NSString Benefits

比如 __NSCFConstantString 负责 const string,类似 @”helloworld”这样的字符串。这样的字符串有一个特点,不会被修改,当真正处理的时候,可以分配大小合适的内存,甚至可以分配在只读 data segment上面,而不需要分配在堆上面,如果有相同的字符串引用就可以完全赋值相同的地址。那么在retainCount上面的处理也就和其他字符串处理有很大不同。

NSPathStore2 看上去是处理有Path相关的字符串,因为没有源代码,这里我们可以大胆猜测一下,path相关的主要是做字符串的拼接操作,而这些字符串通常很长,占用空间大,但是重复的概率缺很高,那么就可以缓存一些字符串,这样可以减少一些内存的分配释放开销。

How to use

The class cluster architecture involves a trade-off between simplicity and extensibility: Having a few public classes stand in for a multitude of private ones makes it easier to learn and use the classes in a framework but somewhat harder to create subclasses within any of the clusters.

就像Apple文档中提到的,Class Cluster 是在简单和扩展性上面做了一个妥协。Class Clusters 的子类化比较麻烦,而且也看上去也非常trick,Apple 更推荐的方法是用组合的方法来扩展。

大家都知道设计模式有一个非常重的坑就是被过渡设计。Class Cluster 可以帮我们

  • 减少了if else 这样缺乏扩展性的代码
  • 增加新功能支持不影响其他代码

那么这个非常适合应用在适配上面,比如不同屏幕的适配,不同厂家可能的不同的需求。

1
2
3
4
5
6
7
8
9
10
11
+ (id)alloc {
    if ([self class] == [SFSSearchTVC class]) {
        if ([UIDevice currentDevice] systemMajorVersion] < 7) {
            return [SFSSearchTVC6 alloc];
        } else if ([UIDevice currentDevice] systemMajorVersion] == 7) {
            return [SFSSearchTVC7 alloc];
        }
    }

    return [super alloc];
}

上面是代码来自BJ Miller’s blog A Cluster to Remove Clutter 是用于适配iOS6,iOS7的简单例子。

Conclusion

很多设计模式都很像,也很容易糊涂,比如工厂模式和Class Clusters在某些地方就很类似,我自己也并不能很好的分清楚。 设计模式的本质是为了解耦。不管使用哪个设计模式,我们最后追求的都是简单、容易维护和扩展的代码。

Octopress Notebox Plugin

| Comments

用了这么长时间的octopress总该扩展一点点事情了,在Blogging的时候,总有一些信息是需要被特殊标记的,但是我并不喜欢简单的加粗或是斜体。类似的东西在Apple Document中有很多

Apple Document Note sample

这里我就把这个功能照搬到octopress中了。

1.在plugins目录创建一个notebox.rb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
module Jekyll
  class Notebox < Liquid::Block

    def initialize(name, id, tokens)
      super
      @id = id
    end

    def render(context)
      stressText = paragraphize(super)

      source = "<div class='notebox'><p><strong>Note: </strong>#{stressText}</p></div>"
      source

    end

    def paragraphize(input)
      "#{input.lstrip.rstrip.gsub(/\n\n/, '</p><p>').gsub(/\n/, '<br/>')}"
    end

  end
end

Liquid::Template.register_tag('notebox', Jekyll::Notebox)

2.在sass/custom中的文件_stype.scss的最后添加下面的代码

1
2
3
4
5
6
7
8
9
.notebox {
  border:1px;
  border-style: solid;
  border-color: #5088C5;
  background-color:#fff;
  margin:.75em 0 1.5em;
  padding:.75em .667em .75em .750em;
  text-align:left;
}

3.markdown的语法(因为格式问题写成了%,需要替换成%

{% notebox %}
the text to note
{% endnotebox %}

效果

Note: text