Masonry介绍与使用:Autolayout

时间:2021-05-10 16:32:32
 

前言

1 MagicNumber -> autoresizingMask -> autolayout

以上是纯手写代码所经历的关于页面布局的三个时期

在iphone1-iphone3gs时代 window的size固定为(320,480) 我们只需要简单计算一下相对位置就好了

在iphone4-iphone4s时代 苹果推出了retina屏 但是给了码农们非常大的福利:window的size不变

在iphone5-iphone5s时代 window的size变了(320,568) 这时autoresizingMask派上了用场(为啥这时候不用Autolayout? 因为还要支持ios5呗) 简单的适配一下即可

在iphone6+时代 window的width也发生了变化(相对5和5s的屏幕比例没有变化) 终于是时候抛弃autoresizingMask改用autolayout了(不用支持ios5了 相对于屏幕适配的多样性来说autoresizingMask也已经过时了)

那如何快速的上手autolayout呢? 说实话 当年ios6推出的同时新增了autolayout的特性 我看了一下官方文档和demo 就立马抛弃到一边了 因为实在过于的繁琐和啰嗦(有过经验的朋友肯定有同感)

直到iPhone6发布之后 我知道使用autolayout势在必行了 这时想起了以前在浏览Github看到过的一个第三方库Masonry 在花了几个小时的研究使用后 我就将autolayout掌握了(重点是我并没有学习任何的官方文档或者其他的关于autolayout的知识) 这就是我为什么要写下这篇文章来推荐它的原因.

介绍

Masonry 源码:https://github.com/Masonry/Masonry

Masonry是一个轻量级的布局框架 拥有自己的描述语法 采用更优雅的链式语法封装自动布局 简洁明了 并具有高可读性 而且同时支持 iOS 和 Max OS X。

我们先来看一段官方的sample code来认识一下Masonry

123 [view1 mas_makeConstraints:^(MASConstraintMaker *make) {    make.edges.equalTo(superview).with.insets(padding);}];

看到block里面的那句话: make edges equalTo superview with insets

通过链式的自然语言 就把view1给autolayout好了 是不是简单易懂?

使用

看一下Masonry支持哪一些属性

1234567891011 @property (nonatomic, strong, readonly) MASConstraint *left;@property (nonatomic, strong, readonly) MASConstraint *top;@property (nonatomic, strong, readonly) MASConstraint *right;@property (nonatomic, strong, readonly) MASConstraint *bottom;@property (nonatomic, strong, readonly) MASConstraint *leading;@property (nonatomic, strong, readonly) MASConstraint *trailing;@property (nonatomic, strong, readonly) MASConstraint *width;@property (nonatomic, strong, readonly) MASConstraint *height;@property (nonatomic, strong, readonly) MASConstraint *centerX;@property (nonatomic, strong, readonly) MASConstraint *centerY;@property (nonatomic, strong, readonly) MASConstraint *baseline;

这些属性与NSLayoutAttrubute的对照表如下

Masonry介绍与使用:Autolayout

其中leading与left trailing与right 在正常情况下是等价的 但是当一些布局是从右至左时(比如阿拉伯文?没有类似的经验) 则会对调 换句话说就是基本可以不理不用 用left和right就好了

在ios8发布后 又新增了一堆奇奇怪怪的属性(有兴趣的朋友可以去瞅瞅) Masonry暂时还不支持(不过你要支持ios6,ios7 就没必要去管那么多了)

在讲实例之前 先介绍一个MACRO

1 #define WS(weakSelf)  __weak __typeof(&*self)weakSelf = self;

快速的定义一个weakSelf 当然是用于block里面啦 下面进入正题(为了方便 我们测试的superView都是一个size为(300,300)的UIView)

下面 通过一些简单的实例来简单介绍如何轻松愉快的使用Masonry:

1. [基础] 居中显示一个view

1234567891011121314151617 - (void)viewDidLoad{    [super viewDidLoad];    // Do any additional setup after loading the view.         WS(ws);         UIView *sv = [UIView new];    [sv showPlaceHolder];    sv.backgroundColor = [UIColor blackColor];    [self.view addSubview:sv];    [sv mas_makeConstraints:^(MASConstraintMaker *make) {        make.center.equalTo(ws.view);        make.size.mas_equalTo(CGSizeMake(300, 300));    }];     }

代码效果

Masonry介绍与使用:Autolayout


使用我之间写的MMPlaceHolder 可以看到superview已经按照我们预期居中并且设置成了适当的大小

那么先看看这几行代码

123456789101112 //从此以后基本可以抛弃CGRectMake了UIView *sv = [UIView new];//在做autoLayout之前 一定要先将view添加到superview上 否则会报错[self.view addSubview:sv];//mas_makeConstraints就是Masonry的autolayout添加函数 将所需的约束添加到block中行了[sv mas_makeConstraints:^(MASConstraintMaker *make) {//将sv居中(很容易理解吧?)    make.center.equalTo(ws.view);         //将size设置成(300,300)    make.size.mas_equalTo(CGSizeMake(300, 300));}];

这里有两个问题要分解一下

首先在Masonry中能够添加autolayout约束有三个函数

123456789 - (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *make))block;- (NSArray *)mas_updateConstraints:(void(^)(MASConstraintMaker *make))block;- (NSArray *)mas_remakeConstraints:(void(^)(MASConstraintMaker *make))block;/*mas_makeConstraints 只负责新增约束 Autolayout不能同时存在两条针对于同一对象的约束 否则会报错 mas_updateConstraints 针对上面的情况 会更新在block中出现的约束 不会导致出现两个相同约束的情况mas_remakeConstraints 则会清除之前的所有约束 仅保留最新的约束三种函数善加利用 就可以应对各种情况了*/

其次 equalTo 和 mas_equalTo的区别在哪里呢? 其实 mas_equalTo是一个MACRO

1234 #define mas_equalTo(...)                 equalTo(MASBoxValue((__VA_ARGS__)))#define mas_greaterThanOrEqualTo(...)    greaterThanOrEqualTo(MASBoxValue((__VA_ARGS__)))#define mas_lessThanOrEqualTo(...)       lessThanOrEqualTo(MASBoxValue((__VA_ARGS__)))#define mas_offset(...)                  valueOffset(MASBoxValue((__VA_ARGS__)))

可以看到 mas_equalTo只是对其参数进行了一个BOX操作(装箱) MASBoxValue的定义具体可以看看源代码 太长就不贴出来了

所支持的类型 除了NSNumber支持的那些数值类型之外 就只支持CGPoint CGSize UIEdgeInsets

介绍完这几个问题 我们就继续往下了 PS:刚才定义的sv会成为我们接下来所有sample的superView

2. [初级] 让一个view略小于其superView(边距为10)

123456789101112131415161718 UIView *sv1 = [UIView new];[sv1 showPlaceHolder];sv1.backgroundColor = [UIColor redColor];[sv addSubview:sv1];[sv1 mas_makeConstraints:^(MASConstraintMaker *make) {    make.edges.equalTo(sv).with.insets(UIEdgeInsetsMake(10, 10, 10, 10));         /* 等价于    make.top.equalTo(sv).with.offset(10);    make.left.equalTo(sv).with.offset(10);    make.bottom.equalTo(sv).with.offset(-10);    make.right.equalTo(sv).with.offset(-10);    */         /* 也等价于    make.top.left.bottom.and.right.equalTo(sv).with.insets(UIEdgeInsetsMake(10, 10, 10, 10));    */}];

代码效果

Masonry介绍与使用:Autolayout

可以看到 edges 其实就是top,left,bottom,right的一个简化 分开写也可以 一句话更省事

那么为什么bottom和right里的offset是负数呢? 因为这里计算的是绝对的数值 计算的bottom需要小鱼sv的底部高度 所以要-10 同理用于right

这里有意思的地方是and和with 其实这两个函数什么事情都没做

123456 - (MASConstraint *)with {    return self;}- (MASConstraint *)and {    return self;}

但是用在这种链式语法中 就非常的巧妙和易懂 不得不佩服作者的心思(虽然我现在基本都会省略)

3. [初级] 让两个高度为150的view垂直居中且等宽且等间隔排列 间隔为10(自动计算其宽度)

123456789101112131415 int padding1 = 10;[sv2 mas_makeConstraints:^(MASConstraintMaker *make) {    make.centerY.mas_equalTo(sv.mas_centerY);    make.left.equalTo(sv.mas_left).with.offset(padding1);    make.right.equalTo(sv3.mas_left).with.offset(-padding1);    make.height.mas_equalTo(@150);    make.width.equalTo(sv3);}];[sv3 mas_makeConstraints:^(MASConstraintMaker *make) {    make.centerY.mas_equalTo(sv.mas_centerY);    make.left.equalTo(sv2.mas_right).with.offset(padding1);    make.right.equalTo(sv.mas_right).with.offset(-padding1);    make.height.mas_equalTo(@150);    make.width.equalTo(sv2);}];

代码效果

Masonry介绍与使用:Autolayout

这里我们在两个子view之间互相设置的约束 可以看到他们的宽度在约束下自动的被计算出来了

4. [中级] 在UIScrollView顺序排列一些view并自动计算contentSize

123456789101112131415161718192021222324252627282930313233343536373839404142 UIScrollView *scrollView = [UIScrollView new];scrollView.backgroundColor = [UIColor whiteColor];[sv addSubview:scrollView];[scrollView mas_makeConstraints:^(MASConstraintMaker *make) {    make.edges.equalTo(sv).with.insets(UIEdgeInsetsMake(5,5,5,5));}];UIView *container = [UIView new];[scrollView addSubview:container];[container mas_makeConstraints:^(MASConstraintMaker *make) {    make.edges.equalTo(scrollView);    make.width.equalTo(scrollView);}];int count = 10;UIView *lastView = nil;for ( int i = 1 ; i <= count ; ++i ){    UIView *subv = [UIView new];    [container addSubview:subv];    subv.backgroundColor = [UIColor colorWithHue:( arc4random() % 256 / 256.0 )                                      saturation:( arc4random() % 128 / 256.0 ) + 0.5                                      brightness:( arc4random() % 128 / 256.0 ) + 0.5                                           alpha:1];         [subv mas_makeConstraints:^(MASConstraintMaker *make) {        make.left.and.right.equalTo(container);        make.height.mas_equalTo(@(20*i));                 if ( lastView )        {            make.top.mas_equalTo(lastView.mas_bottom);        }        else        {            make.top.mas_equalTo(container.mas_top);        }    }];         lastView = subv;}[container mas_makeConstraints:^(MASConstraintMaker *make) {    make.bottom.equalTo(lastView.mas_bottom);}];

头部效果

Masonry介绍与使用:Autolayout


尾部效果

Masonry介绍与使用:Autolayout

从scrollView的scrollIndicator可以看出 scrollView的内部已如我们所想排列好了

这里的关键就在于container这个view起到了一个中间层的作用 能够自动的计算uiscrollView的contentSize

5. [高级] 横向或者纵向等间隙的排列一组view

很遗憾 autoLayout并没有直接提供等间隙排列的方法(Masonry的官方demo中也没有对应的案例) 但是参考案例3 我们可以通过一个小技巧来实现这个目的 为此我写了一个Category


1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798 @implementation UIView(Masonry_LJC)- (void) distributeSpacingHorizontallyWith:(NSArray*)views{    NSMutableArray *spaces = [NSMutableArray arrayWithCapacity:views.count+1];         for ( int i = 0 ; i < views.count+1 ; ++i )    {        UIView *v = [UIView new];        [spaces addObject:v];        [self addSubview:v];                 [v mas_makeConstraints:^(MASConstraintMaker *make) {            make.width.equalTo(v.mas_height);        }];    }             UIView *v0 = spaces[0];         __weak __typeof(&*self)ws = self;         [v0 mas_makeConstraints:^(MASConstraintMaker *make) {        make.left.equalTo(ws.mas_left);        make.centerY.equalTo(((UIView*)views[0]).mas_centerY);    }];         UIView *lastSpace = v0;    for ( int i = 0 ; i < views.count; ++i )    {        UIView *obj = views[i];        UIView *space = spaces[i+1];                 [obj mas_makeConstraints:^(MASConstraintMaker *make) {            make.left.equalTo(lastSpace.mas_right);        }];                 [space mas_makeConstraints:^(MASConstraintMaker *make) {            make.left.equalTo(obj.mas_right);            make.centerY.equalTo(obj.mas_centerY);            make.width.equalTo(v0);        }];                 lastSpace = space;    }         [lastSpace mas_makeConstraints:^(MASConstraintMaker *make) {        make.right.equalTo(ws.mas_right);    }];     }- (void) distributeSpacingVerticallyWith:(NSArray*)views{    NSMutableArray *spaces = [NSMutableArray arrayWithCapacity:views.count+1];         for ( int i = 0 ; i < views.count+1 ; ++i )    {        UIView *v = [UIView new];        [spaces addObject:v];        [self addSubview:v];                 [v mas_makeConstraints:^(MASConstraintMaker *make) {            make.width.equalTo(v.mas_height);        }];    }              UIView *v0 = spaces[0];         __weak __typeof(&*self)ws = self;         [v0 mas_makeConstraints:^(MASConstraintMaker *make) {        make.top.equalTo(ws.mas_top);        make.centerX.equalTo(((UIView*)views[0]).mas_centerX);    }];         UIView *lastSpace = v0;    for ( int i = 0 ; i < views.count; ++i )    {        UIView *obj = views[i];        UIView *space = spaces[i+1];                 [obj mas_makeConstraints:^(MASConstraintMaker *make) {            make.top.equalTo(lastSpace.mas_bottom);        }];                 [space mas_makeConstraints:^(MASConstraintMaker *make) {            make.top.equalTo(obj.mas_bottom);            make.centerX.equalTo(obj.mas_centerX);            make.height.equalTo(v0);        }];                 lastSpace = space;    }         [lastSpace mas_makeConstraints:^(MASConstraintMaker *make) {        make.bottom.equalTo(ws.mas_bottom);    }];}@end

简单的来测试一下

12345678910111213141516171819202122232425262728293031323334353637 UIView *sv11 = [UIView new];UIView *sv12 = [UIView new];UIView *sv13 = [UIView new];UIView *sv21 = [UIView new];UIView *sv31 = [UIView new];sv11.backgroundColor = [UIColor redColor];sv12.backgroundColor = [UIColor redColor];sv13.backgroundColor = [UIColor redColor];sv21.backgroundColor = [UIColor redColor];sv31.backgroundColor = [UIColor redColor];[sv addSubview:sv11];[sv addSubview:sv12];[sv addSubview:sv13];[sv addSubview:sv21];[sv addSubview:sv31];//给予不同的大小 测试效果[sv11 mas_makeConstraints:^(MASConstraintMaker *make) {    make.centerY.equalTo(@[sv12,sv13]);    make.centerX.equalTo(@[sv21,sv31]);    make.size.mas_equalTo(CGSizeMake(40, 40));}];[sv12 mas_makeConstraints:^(MASConstraintMaker *make) {    make.size.mas_equalTo(CGSizeMake(70, 20));}];[sv13 mas_makeConstraints:^(MASConstraintMaker *make) {    make.size.mas_equalTo(CGSizeMake(50, 50));}];[sv21 mas_makeConstraints:^(MASConstraintMaker *make) {    make.size.mas_equalTo(CGSizeMake(50, 20));}];[sv31 mas_makeConstraints:^(MASConstraintMaker *make) {    make.size.mas_equalTo(CGSizeMake(40, 60));}];[sv distributeSpacingHorizontallyWith:@[sv11,sv12,sv13]];[sv distributeSpacingVerticallyWith:@[sv11,sv21,sv31]];[sv showPlaceHolderWithAllSubviews];[sv hidePlaceHolder];

代码效果

Masonry介绍与使用:Autolayout

perfect! 简洁明了的达到了我们所要的效果

这里所用的技巧就是 使用空白的占位view来填充我们目标view的旁边 这点通过图上的空白标注可以看出来

小结

通过以上5个案例 我觉得已经把Masonry的常用功能介绍得差不多了 如果你觉得意犹未尽呢 请下载官方的demo来学习

总而言之 Masonry是一个非常优秀的autolayout库 能够节省大量的开发和学习时间 尤其适合我这种纯代码的iOSer 在iPhone6发布后引发的适配潮中 Masonry一定可以助你一臂之力 :)




实时显示iOS编写UI代码效果

编写iOS应用UI的方式大概有两种,一种是Storyboard/Xib,另一种是手写代码。采用Storyboard/Xib方式组织UI,由于提供可视化的特性,只要从UI库中拖动UI控件,便可以显示结果,极大地提高开发速度。但面临一个问题就是多人协作开发,由于所有的UI都放在同一个Storyboard文件中,使用Git/SVN合并代码就会出现冲突。多人协作开发还不是主要问题,有人提出可以创建多个Storyboard来分开UI编写,而Storyboard/Xib最主要问题是代码复用性比较差。所以有些人就选择手写UI代码,这样不仅可以解决多人协作开发问题,而且通过自定义控件在多个View使用。但每次手写UI代码后都要编译、构建和运行,最后在模拟器显示,这样会拖慢开发速度。如果每次修改UI控件后,保存修改便实时在模拟器显示修改后结果,就可以极大的提高编写UI的速度。

Masonry介绍与使用:Autolayout

Auto Layout

Auto Layout是什么

Auto Layout是一个基于constraint(约束)的布局系统,它根据UI元素之间约束关系来调整UI元素的位置和大小。

Auto Layout解决什么问题

  • 更容易适配不同分辨率设备的屏幕(iPhone 6 Plus, iPhone 6, iPhone 5s/5, iPhone 4s/4)

  • 当设备旋转时不需要做额外处理

  • 使用constraint来描述布局逻辑,更利于理解和清晰

如何使用Auto Layout

Auto Layout中约束的类对应是NSLayoutConstraint, 而创建NSLayoutConstraint对象主要有两种方式,第一种是

1234567 + (id)constraintWithItem:(id)view1               attribute:(NSLayoutAttribute)attribute1               relatedBy:(NSLayoutRelation)relation                  toItem:(id)view2               attribute:(NSLayoutAttribute)attribute2              multiplier:(CGFloat)multiplier                constant:(CGFloat)constant;

上面方法主要意思是,某个view1的attribute1等于(小于或等于/大于或等于)某个view2的attribute2的multiplier倍加上constant。而attribute主要由表示位置(上/下/左/右)和大小(宽/高)的以下几个值:

1234567891011121314 typedef enum: NSInteger {   NSLayoutAttributeLeft = 1,   NSLayoutAttributeRight,   NSLayoutAttributeTop,   NSLayoutAttributeBottom,   NSLayoutAttributeLeading,   NSLayoutAttributeTrailing,   NSLayoutAttributeWidth,   NSLayoutAttributeHeight,   NSLayoutAttributeCenterX,   NSLayoutAttributeCenterY,   NSLayoutAttributeBaseline,   NSLayoutAttributeNotAnAttribute = 0} NSLayoutAttribute;

简化一下,使用公式可以表达为:

1 view1.attribute1 = view2.attribute2 * multiplier + constant

第二种方式是:

1234 + (NSArray *)constraintsWithVisualFormat:(NSString *)format                                  options:(NSLayoutFormatOptions)opts                                  metrics:(NSDictionary *)metrics                                    views:(NSDictionary *)views;

这种方式主要是采用Visual Format Language(可视化格式语言)来描述约束布局,虽然语法比较简洁,但是可读性比较差和容易出错。

Auto Layout存在问题

虽然Auto Layout在布局view方面是非常强大和灵活,但是创建constraint的语法过于繁杂,引用Masonry一个例子:

12345678910111213141516171819202122232425262728293031323334353637 UIView *superview = self;UIView *view1 = [[UIView alloc] init];view1.translatesAutoresizingMaskIntoConstraints = NO;view1.backgroundColor = [UIColor greenColor];[superview addSubview:view1];UIEdgeInsets padding = UIEdgeInsetsMake(10, 10, 10, 10);[superview addConstraints:@[    //view1 constraints    [NSLayoutConstraint constraintWithItem:view1                                 attribute:NSLayoutAttributeTop                                 relatedBy:NSLayoutRelationEqual                                    toItem:superview                                 attribute:NSLayoutAttributeTop                                multiplier:1.0                                  constant:padding.top],    [NSLayoutConstraint constraintWithItem:view1                                 attribute:NSLayoutAttributeLeft                                 relatedBy:NSLayoutRelationEqual                                    toItem:superview                                 attribute:NSLayoutAttributeLeft                                multiplier:1.0                                  constant:padding.left],    [NSLayoutConstraint constraintWithItem:view1                                 attribute:NSLayoutAttributeBottom                                 relatedBy:NSLayoutRelationEqual                                    toItem:superview                                 attribute:NSLayoutAttributeBottom                                multiplier:1.0                                  constant:-padding.bottom],    [NSLayoutConstraint constraintWithItem:view1                                 attribute:NSLayoutAttributeRight                                 relatedBy:NSLayoutRelationEqual                                    toItem:superview                                 attribute:NSLayoutAttributeRight                                multiplier:1                                  constant:-padding.right], ]];

如此简单的一个例子都要编写这么多行代码,想象一下如果创建多个view的constraint时会多么痛苦啊。另一个方式是采用Visual Format Language (VFL),虽然语法比较简洁,但是可读性比较差和容易出错。

Masonry

为什么使用Masonry

Masonry是采用链式DSL(Domain-specific language)来封装NSLayoutConstraint,通过这种方式编写Auto Layout布局代码更加易读和简洁。

使用Masonry的MASConstraintMaker来表达相同constraint

1234567 UIEdgeInsets padding = UIEdgeInsetsMake(10, 10, 10, 10);[view1 mas_makeConstraints:^(MASConstraintMaker *make) {    make.top.equalTo(superview.mas_top).with.offset(padding.top); //with is an optional semantic filler    make.left.equalTo(superview.mas_left).with.offset(padding.left);    make.bottom.equalTo(superview.mas_bottom).with.offset(-padding.bottom);    make.right.equalTo(superview.mas_right).with.offset(-padding.right);}];

甚至可以更短

123 [view1 mas_makeConstraints:^(MASConstraintMaker *make) {    make.edges.equalTo(superview).with.insets(padding);}];

如何使用

使用Masonry创建constraint来定义布局的方式有三种:mas_makeConstraints,mas_updateConstraints,mas_remakeConstraints。

1. mas_makeConstraints

使用mas_makeConstraints创建constraint后,你可以使用局部变量或属性来保存以便下次引用它;如果创建多个constraints,你可以采用数组来保存它们。

1234567891011 // in public/private interface@property (nonatomic, strong) MASConstraint *topConstraint;...// when making constraints[view1 mas_makeConstraints:^(MASConstraintMaker *make) {    self.topConstraint = make.top.equalTo(superview.mas_top).with.offset(padding.top);    make.left.equalTo(superview.mas_left).with.offset(padding.left);}];...// then later you can call[self.topConstraint uninstall];

2. mas_updateConstraints

有时你需要更新constraint(例如,动画和调试)而不是创建固定constraint,可以使用mas_updateConstraints方法

1234567891011121314 // this is Apple's recommended place for adding/updating constraints// this method can get called multiple times in response to setNeedsUpdateConstraints// which can be called by UIKit internally or in your code if you need to trigger an update to your constraints- (void)updateConstraints {    [self.growingButton mas_updateConstraints:^(MASConstraintMaker *make) {        make.center.equalTo(self);        make.width.equalTo(@(self.buttonSize.width)).priorityLow();        make.height.equalTo(@(self.buttonSize.height)).priorityLow();        make.width.lessThanOrEqualTo(self);        make.height.lessThanOrEqualTo(self);    }];    //according to apple super should be called at end of method    [super updateConstraints];}

3. mas_remakeConstraints

mas_remakeConstraints与mas_updateConstraints比较相似,都是更新constraint。不过,mas_remakeConstraints是删除之前constraint,然后再添加新的constraint(适用于移动动画);而mas_updateConstraints只是更新constraint的值。

12345678910 - (void)changeButtonPosition {    [self.button mas_remakeConstraints:^(MASConstraintMaker *make) {        make.size.equalTo(self.buttonSize);        if (topLeft) {            make.top.and.left.offset(10);        else {            make.bottom.and.right.offset(-10);        }    }];}

想了解以上三个代码片段的更多细节,可以下载Masonry iOS Examples工程查阅。

Classy

Classy简介和特性

Classy是一个能与UIKit无缝结合stylesheet(样式)系统。它借鉴CSS的思想,但引入新的语法和命名规则。

灵活内嵌的语法

{ } : ; 这些语法符号是可选的,你可以选择适合自己的风格来表达stylesheet。

你可以使用{ } : ; 来限定stylesheet

123456789101112 $main-color = #e1e1e1;MYCustomView {  background-color: $main-color;  title-insets: 5, 10, 5, 10;  > UIProgressView.tinted {    progress-tint-color: black;    track-tint-color: yellow;  }}^UIButton.warning, UIView.warning ^UIButton {  title-color[state:highlighted]: #e3e3e3;}

或者你使用空格来限定stylesheet

123456789 $main-color = #e1e1e1MYCustomView   background-color $main-color  title-insets 5, 10, 5, 10  > UIProgressView.tinted     progress-tint-color black    track-tint-color yellow^UIButton.warning, UIView.warning ^UIButton   title-color[state:highlighted] #e3e3e3

默认样式

Classy在应用程序Bundle默认查找文件名为stylesheet.cas的样式文件。如果你采用这个文件名,你可以不用做任何东西就能加载样式文件。

但如果你想指定其他file path(样式文件名),你可以创建[CASStyler defaultStyler]

1 [CASStyler defaultStyler].filePath = [[NSBundle mainBundle] pathForResource:@"myStyles.cas" ofType:nil];

如果你还想当发生错误时,获取错误信息以便于调试,可以使用-(void)setFilePath:error:

123 NSError *error = nil;NSString filePath = [[NSBundle mainBundle] pathForResource:@"myStyles.cas" ofType:nil];[[CASStyler defaultStyler] setFilePath:filePath error:&error];

如果你是使用Storyboard/Xib组织UI界面,那就需要在main.m的int main(int argc, char * argv[])方法设置 filePath,这样可以确保在创建UIWindow之前加载stylesheet。否则(采用手写UI代码),你在 AppDelegate.m的- (BOOL)application:didFinishLaunchingWithOptions:方法设置filePath

Live Reload

Live Reload是实时显示编写UI代码效果的关键特性,它能够实时检查stylesheet文件变化,无需重新编译、构建和运行模拟器,从而极大提高开发速度。

为了启用Live Reload,你需要指定stylesheet路径,并且只运行在模拟器上。

1234 #if TARGET_IPHONE_SIMULATOR    NSString *absoluteFilePath = CASAbsoluteFilePath(@"../Styles/stylesheet.cas");    [CASStyler defaultStyler].watchFilePath = absoluteFilePath;#endif

Selectors

Style Selectors是指定哪个view使用哪种样式的方式。主要有三种方法来指定目标view:

  1. Object Class

  2. View Hierarchy

  3. Style Class

你可以混合使用三种方法,例子如下:

123456 /* match views * where class is UIButton or UIButton subclass * and styleClass is "large" * and superview class is UITabBar */UITabBar > ^UIButton.large { }

想了解具体如何使用,请查阅官网Selectors章节

为了避免与Objective-C的message selectors混淆,术语style selectors表示Classy stylesheets的selectors

Properties

Classy支持所有UIAppearance的属性和方法,也支持与UIAppearance无关的很多属性。Classy使用与UIKit相同属性命名,所以你不必考虑如何将style property映射到Objective-C的property。

UIPageControl类的属性如下:

12 @property (nonatomic,retain) UIColor *pageIndicatorTintColor;@property (nonatomic,retain) UIColor *currentPageIndicatorTintColor;

style property的名字采用与objective-c一样的名字

1234 UIPageControl {  pageIndicatorTintColor black  currentPageIndicatorTintColor purple}

style property的命名规则采用kebab case

UIPageControl {

  page-indicator-tint-color black

  current-page-indicator-tint-color purple

}

想了解具体如何使用,请查阅官网Properties章节

Keep it DRY(Don't Repeat Yourself)

在编程中一个很重要的原则就是避免重复,这不仅可以大量减少重复代码,并且使得代码更加容易复用和维护。Classy提供三种方式避免代码重复:grouping, nesting,variables

Grouping

如果有两个以上的style selectors共用相同的属性时

123456789 UISlider.info {  minimum-track-tint-color black  maximum-track-tint-color purple}UISlider.error {  minimum-track-tint-color black  maximum-track-tint-color purple  thumb-tint-color red}

我们可以提取相同的属性到分组style selector中

1234567 UISlider.info, UISlider.error {  minimum-track-tint-color black  maximum-track-tint-color purple}UISlider.error {  thumb-tint-color red}

Nesting

如果两个以上style selectors共用相同的view hierarchy时

123456789101112 UICollectionView {  background-color #a2a2a2}UICollectionView > UICollectionViewCell {  clips-to-bounds NO}UICollectionView > UICollectionViewCell UILabel {  text-color purple}UICollectionView > UICollectionViewCell UILabel.title {  font 20}

我们通过nesting方式将view hierarchies表达成这样方式

123456789101112 UICollectionView {  background-color #a2a2a2  > UICollectionViewCell {    clips-to-bounds NO    UILabel {      text-color purple      &.title {        font 20      }    }  }}

Variables

Classy让你通过定义variables来将多个相同的style property值存储以便共享。Variable命名规则如下:

  • 必须以大小写字母或$符号开头

  • 可以包含_,-或任何字母数字

123456789 // prefix with ' $ ' to help distinguish variables$brand-color = #e1e1e1// OR notinsets = 5, 10, 5, 10UIButton {  background-color $brand-color  contentEdgeInsets insets  background-image[state:selected] bg_button insets}

最后官方还提供一个实例来解释具体如何使用:Custom Views Example

ClassyLiveLayout

ClassyLiveLayout通过结合Classy stylesheets与Masonry一起使用,能够在运行的模拟器中微调Auto Layout约束实时显示效果的工具。

ClassyLiveLayout一个核心category:UIView+ClassyLayoutProperties,在UIView定义以下属性:

123456789 @property(nonatomic, assign) UIEdgeInsets cas_margin;@property(nonatomic, assign) CGSize cas_size;// shorthand properties for setting only a single constant value@property(nonatomic, assign) CGFloat cas_sizeWidth;@property(nonatomic, assign) CGFloat cas_sizeHeight;@property(nonatomic, assign) CGFloat cas_marginTop;@property(nonatomic, assign) CGFloat cas_marginLeft;@property(nonatomic, assign) CGFloat cas_marginBottom;@property(nonatomic, assign) CGFloat cas_marginRight;

cas_margin和cas_size分别表示UI元素的位置和大小,而其余的属性都是对两个属性进一步细分。我们可以从stylesheets中访问style properties来定义constraints布局,做到将数据与代码分离,有利于修改和复用代码。

123456789 UIView.blue-box {    cas_size: 80 100    cas_margin-top: 60    cas_margin-left: 50}UIView.red-box {    cas_size-width: 120    cas_margin-left: 20}

我们可以在updateConstraints或updateViewConstrains定义布局时引用style properties

123456789101112131415 - (void)updateViewConstraints {  [super updateViewConstraints];  [_blueBoxView mas_updateConstraints:^(MASConstraintMaker *make) {      make.width.equalTo(@(_blueBoxView.cas_size.width));      make.height.equalTo(@(_blueBoxView.cas_size.height));      make.top.equalTo(@(_blueBoxView.cas_margin.top));      make.left.equalTo(@(_blueBoxView.cas_margin.left));  }];  [_redBoxView mas_updateConstraints:^(MASConstraintMaker *make) {      make.width.equalTo(@(_redBoxView.cas_size.width));      make.height.equalTo(_blueBoxView);      make.top.equalTo(_blueBoxView);      make.left.equalTo(_blueBoxView.mas_right).with.offset(_redBoxView.cas_margin.left);  }];}

当定义view layouts时,将Auto Layout的constraints都放在stylesheets中实时加载(Live reload)。如果你修改constraints,无需重新编译、构建和运行模拟器便能实时看到修改后的效果。

示例工程

配置工程

由于需要引用Masonry,Classy和ClassyLiveLayout,Podfile配置如下:

123 pod 'Masonry''~> 0.6.1'pod 'Classy''~> 0.2.4'pod 'ClassyLiveLayout''~> 0.6.0'

编写代码

1. 添加stylesheet.cas文件到工程

当安装好Masonry,Classy和ClassyLiveLayout后,第一次运行项目会出现没有stylesheet.cas文件错误:

Masonry介绍与使用:Autolayout

只要向工程添加空的stylesheet.cas文件即可。

Masonry介绍与使用:Autolayout

2. 创建LiveView类,该类继承SHPAbstractView。

Masonry介绍与使用:Autolayout

在ViewController创建LiveView对象,然后被self.view引用。

Masonry介绍与使用:Autolayout

当编译运行时,在SHPAbstractView.h由于找不到UIView出现编译错误。

Masonry介绍与使用:Autolayout

只需引入UIKit便可以解决,但运行一下应用程序,出现一下错误:

Masonry介绍与使用:Autolayout

主要原因是任何自定义UIView继承SHPAbstractView都需要override两个方法:- (void)addSubviews和- (void)defineLayout,我们可以查看SHPAbstractView的源码可知:

Masonry介绍与使用:Autolayout

所以只要在LiveView.m文件覆盖两个方法即可

1234567 #pragma mark - Add subviews and define layout- (void)addSubviews{}- (void)defineLayout{}

3. LiveView类设计

LiveView主要由包含redBoxView和blueBoxView两个属性,redBoxView表示红色方块,blueBoxView表示蓝色方块。

12345 #import "SHPAbstractView.h"@interface LiveView : SHPAbstractView@property (strong, nonatomic) UIView *redBoxView;@property (strong, nonatomic) UIView *blueBoxView;@end

4. LiveView类实现

由于SHPAbstractView类如何初始化View已经做了处理,暴露两个接口- (void)addSubviews和-(void)defineLayout分别处理构建view hierarchy和定义布局,子类只要覆盖SHPAbstractView这两个方法就可以创建LiveView了。

但是我们将Auto Layout的constraints都放在stylesheets中实时加载(Live reload),即放在本工程的stylesheet.cas文件,将布局数据和布局代码分离。

12345678910 UIView.redBox {    cas_marginTop 50    cas_marginLeft 20    cas_size 100 100}UIView.blueBox {    cas_marginTop 50    cas_marginRight -20    cas_size 100 100}

有了constraints数据后,便可以在代码布局:

123456789101112131415161718192021222324252627282930313233343536373839404142 @implementation LiveView#pragma mark - Add subviews and define layout- (void)addSubviews{    self.backgroundColor = [UIColor whiteColor];    [self addSubview:self.redBoxView];    [self addSubview:self.blueBoxView];}- (void)defineLayout{    [self.redBoxView mas_updateConstraints:^(MASConstraintMaker* make){        make.top.equalTo(@(self.redBoxView.cas_marginTop));        make.left.equalTo(@(self.redBoxView.cas_marginLeft));        make.width.equalTo(@(self.redBoxView.cas_sizeWidth));        make.height.equalTo(@(self.redBoxView.cas_sizeHeight));    }];    [self.blueBoxView mas_updateConstraints:^(MASConstraintMaker *make){        make.top.equalTo(@(self.blueBoxView.cas_marginTop));        make.right.equalTo(@(self.blueBoxView.cas_marginRight));        make.width.equalTo(@(self.blueBoxView.cas_sizeWidth));        make.height.equalTo(@(self.blueBoxView.cas_sizeHeight));    }];}#pragma mark - Lazy initialization- (UIView*)redBoxView{    if (!_redBoxView) {        _redBoxView = [UIView new];        _redBoxView.cas_styleClass = @"redBox";        _redBoxView.backgroundColor = [UIColor redColor];    }    return _redBoxView;}- (UIView*)blueBoxView{    if (!_blueBoxView) {        _blueBoxView = [UIView new];        _blueBoxView.cas_styleClass = @"blueBox";        _blueBoxView.backgroundColor = [UIColor blueColor];    }    return _blueBoxView;}

5. 模拟器支持Live Reload

为了启用Live Reload,你需要指定stylesheet路径,并且只运行在模拟器上。

Masonry介绍与使用:Autolayout

最后效果

Masonry介绍与使用:Autolayout

示例代码存放地址:LiveAutoLayout

总结

之前手写UI代码每次更改一般都要重新编译、构建和运行模拟器才能看到效果,但结合使用Masonry,Classy和ClassLiveLayout之后,告别这个费时过程,极大地提高开发速度;不仅如此,我们将Auto Layout的constraints都放在stylesheets中实时加载(Live reload),将布局数据和布局代码分离,使得代码更加复用和维护。Classy还提供三种避免重复方法:Grouping, Nestting和Variable,尽可能复用样式数据。

这是本人第一次编写技术博客,可能有很多错误和漏洞,希望大家多多指点,也希望这篇文章能够帮助到大家。

扩展阅读


参考:http://www.cocoachina.com/ios/20141219/10702.html

http://www.cocoachina.com/ios/20150507/11777.html