Coding源码学习第四部分(Masonry介绍与使用(一))
Masonry GitHub:https://github.com/SnapKit/Masonry
Masonry是一个轻量级的布局框架,拥有自己的描述语法,采用更优雅的链式语法封装自动布局,简洁明了并具有高可读性,而且同时支持 iOS 和 Mac OS X。Masonry是一个用代码写iOS或OS界面的库,可以代替Auto layout。
example :
1 [view1 mas_makeConstraints:^(MASConstraintMaker *make) { 2 3 // 设置和superView 的边缘的缩进量 4 make.edges.equalTo(superview).with.insets(padding); 5 6 7 }];
Masonry使用讲解
Masonry 支持的属性:
make 的属性:(MASConstraintMaker:)
1 @interface MASConstraintMaker : NSObject 2 3 /** 4 * The following properties return a new MASViewConstraint 5 * with the first item set to the makers associated view and the appropriate MASViewAttribute 6 */ 7 @property (nonatomic, strong, readonly) MASConstraint *left; 8 @property (nonatomic, strong, readonly) MASConstraint *top; 9 @property (nonatomic, strong, readonly) MASConstraint *right; 10 @property (nonatomic, strong, readonly) MASConstraint *bottom; 11 @property (nonatomic, strong, readonly) MASConstraint *leading; 12 @property (nonatomic, strong, readonly) MASConstraint *trailing; 13 @property (nonatomic, strong, readonly) MASConstraint *width; 14 @property (nonatomic, strong, readonly) MASConstraint *height; 15 @property (nonatomic, strong, readonly) MASConstraint *centerX; 16 @property (nonatomic, strong, readonly) MASConstraint *centerY; 17 @property (nonatomic, strong, readonly) MASConstraint *baseline;
其中leading与left trailing与right 在正常情况下是等价的 但是当一些布局是从右至左时, 则会对调 换句话说就是基本可以不理不用 用left和right就好了
需要添约束的View 的约束的属性:
1 @interface MAS_VIEW (MASAdditions) 2 3 /** 4 * following properties return a new MASViewAttribute with current view and appropriate NSLayoutAttribute 5 */ 6 @property (nonatomic, strong, readonly) MASViewAttribute *mas_left; 7 @property (nonatomic, strong, readonly) MASViewAttribute *mas_top; 8 @property (nonatomic, strong, readonly) MASViewAttribute *mas_right; 9 @property (nonatomic, strong, readonly) MASViewAttribute *mas_bottom; 10 @property (nonatomic, strong, readonly) MASViewAttribute *mas_leading; 11 @property (nonatomic, strong, readonly) MASViewAttribute *mas_trailing; 12 @property (nonatomic, strong, readonly) MASViewAttribute *mas_width; 13 @property (nonatomic, strong, readonly) MASViewAttribute *mas_height; 14 @property (nonatomic, strong, readonly) MASViewAttribute *mas_centerX; 15 @property (nonatomic, strong, readonly) MASViewAttribute *mas_centerY; 16 @property (nonatomic, strong, readonly) MASViewAttribute *mas_baseline; 17 @property (nonatomic, strong, readonly) MASViewAttribute *(^mas_attribute)(NSLayoutAttribute attr);
Masonry 提供了3个关于约束的方法:
1 - (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *make))block; // mas_makeConstraints 只负责新增约束 Autolayout不能同时存在两条针对于同一对象的约束 否则会报错 2 - (NSArray *)mas_updateConstraints:(void(^)(MASConstraintMaker *make))block; // mas_updateConstraints 针对上面的情况 会更新在block中出现的约束 不会导致出现两个相同约束的情况 3 - (NSArray *)mas_remakeConstraints:(void(^)(MASConstraintMaker *make))block; // mas_remakeConstraints 则会清除之前的所有约束 仅保留最新的约束 4 三种函数善加利用 就可以应对各种情况了
mas_makeConstraints 是给view添加约束,约束有几种,分别是边距,宽,高,左上右下距离,基准线。
添加过约束后可以有修正,修正有offset(位移)修正和multipliedBy(倍率)修正。
语法一般是 make.equalTo or make.greaterThanOrEqualTo or make.lessThanOrEqualTo + 倍数和位移修正。
注意点1:
使用 mas_makeConstraints方法的元素必须事先添加到父元素的中,例如[self.view addSubview:view];
注意点2:
mas_equalTo 和 equalTo 区别:mas_equalTo 比equalTo多了类型转换操作,一般来说,大多数时候两个方法都是 通用的,但是对于数值元素使用mas_equalTo。对于对象或是多个属性的处理,使用equalTo。特别是多个属性时,必须使用equalTo.
举例 make.left.and.right.equalTo(self.view) , make.height.mas_equalTo(@20);
1 #define mas_equalTo(...) equalTo(MASBoxValue((__VA_ARGS__))) 2 #define mas_greaterThanOrEqualTo(...) greaterThanOrEqualTo(MASBoxValue((__VA_ARGS__))) 3 #define mas_lessThanOrEqualTo(...) lessThanOrEqualTo(MASBoxValue((__VA_ARGS__))) 4 5 #define mas_offset(...) valueOffset(MASBoxValue((__VA_ARGS__)))
mas_equalTo 是一个宏,可以看到 mas_equalTo只是对其参数进行了一个BOX操作(装箱) MASBoxValue的定义具体可以看看源代码
所支持的类型 除了NSNumber支持的那些数值类型之外,就只支持CGPoint CGSize UIEdgeInsets
mas_greaterThanOrEqualTo 和 mas_lessThanOrEqualTo 这两个宏还有一个小技巧:
1 #ifdef MAS_SHORTHAND_GLOBALS 2 3 #define equalTo(...) mas_equalTo(__VA_ARGS__) 4 #define greaterThanOrEqualTo(...) mas_greaterThanOrEqualTo(__VA_ARGS__) 5 #define lessThanOrEqualTo(...) mas_lessThanOrEqualTo(__VA_ARGS__) 6 7 #define offset(...) mas_offset(__VA_ARGS__) 8 9 #endif
定义以下两个宏,在使用Masonry框架时就不需要加mas_前缀了
(定义宏一定要在引入Masonry.h文件之前).
1 //define this constant if you want to use Masonry without the 'mas_' prefix 2 #define MAS_SHORTHAND 3 //define this constant if you want to enable auto-boxing for default syntax 4 #define MAS_SHORTHAND_GLOBALS 5 6 // example 7 8 9 #define MAS_SHORTHAND 10 #define MAS_SHORTHAND_GLOBALS 11 #import "Masonry.h"
注意点3: 注意到方法with和and,这连个方法其实没有做任何操作,方法只是返回对象本身,这这个方法的左右完全是为了方法写的时候的可读性 。
make.left.and.right.equalTo(self.view);和make.left.right.equalTo(self.view);是完全一样的,但是明显的加了and方法的语句可读性 更好点。
1 #pragma mark - Semantic properties 2 3 - (MASConstraint *)with { 4 return self; 5 } 6 7 - (MASConstraint *)and { 8 return self; 9 }
小技巧:#define WS(weakSelf) __weak __typeof(&*self)weakSelf = self; 快速的定义一个weakSelf ,用于block 里面,防止循环引用。
示例:
(1) 居中显示一个View :
1 WS(weakSelf); // WS 是上面定义的一个宏 2 // 从此基本可以抛弃CGRectMake 了 3 UIView *sv = [UIView new]; 4 sv.backgroundColor = [UIColor blackColor]; 5 // 在做autoLayout 之前,一定要将view 添加到superView 上,否则会报错 6 [self.view addSubview:sv]; 7 // mas_makeConstraints 就是Masonry 的autoLayout 添加函数,将所需的约束添加到block 中就行了 8 [sv mas_makeConstraints:^(MASConstraintMaker *make) { 9 // 将sv 居中(很容易理解) 10 make.center.equalTo(weakSelf.view); 11 // 将size 设置为(400 ,400) 12 make.size.mas_equalTo(CGSizeMake(400, 400)); 13 }];
(2)让一个View 略小于其superView(边距是10)
1 // 让一个View 略小于其superView(边框为 10) 2 UIView *sv1 = [UIView new]; 3 sv1.backgroundColor = [UIColor redColor]; 4 [sv addSubview:sv1]; 5 [sv1 mas_makeConstraints:^(MASConstraintMaker *make) { 6 make.edges.equalTo(sv).with.insets(UIEdgeInsetsMake(10, 10, 10, 10)); 7 // 等价于 8 // make.top.equalTo(sv).with.offset(10); 9 // make.left.equalTo(sv).with.offset(10); 10 // make.bottom.equalTo(sv).with.offset(-10); 11 // make.right.equalTo(sv).with.offset(-10); 12 // 也等价于 13 // make.top.left.bottom.and.right.equalTo(sv).with.insets(UIEdgeInsetsMake(10, 10, 10, 10)); 14 }];
可以看到 edges 其实就是top,left,bottom,right的一个简化 分开写也可以 一句话更省事
那么为什么bottom和right里的offset是负数呢? 因为这里计算的是绝对的数值 计算的bottom需要小于sv的底部高度 所以要-10 同理用于right
(3)让两个高度为150 的View 垂直居中且等宽且等间隔排列 间隔是10 (自动计算其宽度)
1 // 初级 让两个高度为150 的View 垂直居中且等宽且等间隔排列 间隔为10 (自动计算其宽度) 2 int padding1 = 10; 3 UIView *sv2 = [UIView new]; 4 sv2.backgroundColor = [UIColor yellowColor]; 5 [sv addSubview:sv2]; 6 UIView *sv3 = [UIView new]; 7 sv3.backgroundColor = [UIColor yellowColor]; 8 [sv addSubview:sv3]; 9 10 [sv2 mas_makeConstraints:^(MASConstraintMaker *make) { 11 make.centerY.mas_equalTo(sv.mas_centerY); 12 make.left.equalTo(sv.mas_left).with.offset(padding1); 13 make.right.equalTo(sv3.mas_left).with.offset(-padding1); 14 make.height.mas_equalTo(@150); 15 make.width.equalTo(sv3); 16 }]; 17 18 [sv3 mas_makeConstraints:^(MASConstraintMaker *make) { 19 make.centerY.mas_equalTo(sv.mas_centerY); 20 make.left.equalTo(sv2.mas_right).with.offset(padding1); 21 make.right.equalTo(sv.mas_right).with.offset(-padding1); 22 make.height.mas_equalTo(@150); 23 make.width.equalTo(sv2); 24 }];
这里我们在两个子view之间互相设置的约束 可以看到他们的宽度在约束下自动的被计算出来了
(4)在UIScrollerView 顺序排列一些View 并自动计算contentSize
1 // 中级 在UIScrollerView 顺序排列一些View 并自动计算contentSize 2 UIScrollView *scrollView = [UIScrollView new]; 3 scrollView.backgroundColor = [UIColor whiteColor]; 4 [sv addSubview:scrollView]; 5 [scrollView mas_makeConstraints:^(MASConstraintMaker *make) { 6 make.edges.equalTo(sv).with.insets(UIEdgeInsetsMake(5, 5, 5, 5)); // 约束UIScrollerView 的窗口视图的大小四边各缩进sv 5 7 }]; 8 UIView *container = [UIView new]; // 起一个中间层的作用,能够自动计算UIScrollerView 的contentSize 9 [scrollView addSubview:container]; 10 [container mas_makeConstraints:^(MASConstraintMaker *make) { 11 make.edges.equalTo(scrollView); // left right top bottom 12 make.width.equalTo(scrollView); 13 }]; 14 15 int count = 10; 16 UIView *lastView = nil; 17 for (int i = 1; i <= count; i++) { 18 UIView *subV = [UIView new]; 19 [container addSubview:subV]; 20 subV.backgroundColor = [UIColor colorWithHue:(arc4random() % 256 / 256.0) saturation:(arc4random() % 128 / 256.0) brightness:(arc4random() % 128 / 256.0) alpha:1]; // 设置背景颜色 21 [subV mas_makeConstraints:^(MASConstraintMaker *make) { 22 make.left.and.right.equalTo(container); 23 make.height.mas_equalTo(@(20 * i)); // 每个子View 的高度不断增大一倍 24 if (lastView) { 25 make.top.mas_equalTo(lastView.mas_bottom); // lastView 做中间View 的传递,确定每个小View 的top 26 } else { 27 make.top.mas_equalTo(container.mas_top); // 第一个小View 的top 和container 相等 28 } 29 }]; 30 lastView = subV; 31 } 32 33 [container mas_makeConstraints:^(MASConstraintMaker *make) { 34 make.bottom.equalTo(lastView.mas_bottom); // container 的bottom 和最后一个小View 的bottom 相等 35 }];
(5)横向或者纵向等间隙的排列一组View
autoLayout并没有直接提供等间隙排列的方法(Masonry的官方demo中也没有对应的案例) 但是参考案例3 我们可以通过一个小技巧来实现这个目的 为此写一个Category
1 #import "UIView+Masonry_LJC.h" 2 #import "Masonry.h" 3 4 @implementation UIView (Masonry_LJC) 5 6 - (void)distributeSpacingHorizontallyWith:(NSArray *)views { 7 NSMutableArray *spaces = [NSMutableArray arrayWithCapacity:views.count + 1]; 8 9 for (int i = 0; i < views.count + 1; ++i) { 10 UIView *v = [UIView new]; 11 [spaces addObject:v]; 12 [self addSubview:v]; 13 14 [v mas_makeConstraints:^(MASConstraintMaker *make) { 15 make.width.equalTo(v.mas_height); // 宽高相等 16 }]; 17 } 18 19 UIView *v0 = spaces[0]; 20 21 __weak __typeof (&*self) ws = self; 22 23 [v0 mas_makeConstraints:^(MASConstraintMaker *make) { 24 make.left.equalTo(ws.mas_left); 25 make.centerY.equalTo(((UIView *)views[0]).mas_centerY); 26 }]; 27 28 UIView *lastSpace = v0; 29 for (int i = 0; i < views.count; ++i) { 30 UIView *obj = views[i]; 31 UIView *space = spaces[i + 1]; 32 33 [obj mas_makeConstraints:^(MASConstraintMaker *make) { 34 make.left.equalTo(lastSpace.mas_right); 35 }]; 36 37 [space mas_makeConstraints:^(MASConstraintMaker *make) { 38 make.left.equalTo(obj.mas_right); 39 make.centerY.equalTo(obj.mas_centerY); 40 make.width.equalTo(v0); 41 }]; 42 43 lastSpace = space; 44 } 45 46 [lastSpace mas_makeConstraints:^(MASConstraintMaker *make) { 47 make.right.equalTo(ws.mas_right); 48 }]; 49 } 50 51 - (void)distributeSpacingVerticallyWith:(NSArray *)views { 52 NSMutableArray *spaces = [NSMutableArray arrayWithCapacity:views.count + 1]; 53 54 for (int i = 0; i < views.count + 1; ++i) { 55 UIView *v = [UIView new]; 56 [spaces addObject:v]; 57 [self addSubview:v]; 58 59 [v mas_makeConstraints:^(MASConstraintMaker *make) { 60 make.width.equalTo(v.mas_height); 61 }]; 62 } 63 64 UIView *v0 = spaces[0]; 65 66 __weak __typeof (&*self) ws = self; 67 68 [v0 mas_makeConstraints:^(MASConstraintMaker *make) { 69 make.top.equalTo(ws.mas_top); 70 make.centerX.equalTo(((UIView *)views[0]).mas_centerX); 71 }]; 72 73 UIView *lastSpace = v0; 74 for (int i = 0; i < views.count; ++i) { 75 UIView *obj = views[i]; 76 UIView *space = spaces[i + 1]; 77 78 [obj mas_makeConstraints:^(MASConstraintMaker *make) { 79 make.top.equalTo(lastSpace.mas_bottom); 80 }]; 81 82 [space mas_makeConstraints:^(MASConstraintMaker *make) { 83 make.top.equalTo(obj.mas_bottom); 84 make.centerX.equalTo(obj.mas_centerX); 85 make.height.equalTo(v0); 86 }]; 87 88 lastSpace = space; 89 } 90 91 [lastSpace mas_makeConstraints:^(MASConstraintMaker *make) { 92 make.bottom.equalTo(ws.mas_bottom); 93 }]; 94 } 95 @end
测试:
1 UIView *sv11 = [UIView new]; 2 UIView *sv12 = [UIView new]; 3 UIView *sv13 = [UIView new]; 4 UIView *sv21 = [UIView new]; 5 UIView *sv31 = [UIView new]; 6 sv11.backgroundColor = [UIColor redColor]; 7 sv12.backgroundColor = [UIColor redColor]; 8 sv13.backgroundColor = [UIColor redColor]; 9 sv21.backgroundColor = [UIColor redColor]; 10 sv31.backgroundColor = [UIColor redColor]; 11 [sv addSubview:sv11]; 12 [sv addSubview:sv12]; 13 [sv addSubview:sv13]; 14 [sv addSubview:sv21]; 15 [sv addSubview:sv31]; 16 //给予不同的大小 测试效果 17 [sv11 mas_makeConstraints:^(MASConstraintMaker *make) { 18 make.centerY.equalTo(@[sv12,sv13]); 19 make.centerX.equalTo(@[sv21,sv31]); 20 make.size.mas_equalTo(CGSizeMake(40, 40)); 21 }]; 22 [sv12 mas_makeConstraints:^(MASConstraintMaker *make) { 23 make.size.mas_equalTo(CGSizeMake(70, 20)); 24 }]; 25 [sv13 mas_makeConstraints:^(MASConstraintMaker *make) { 26 make.size.mas_equalTo(CGSizeMake(50, 50)); 27 }]; 28 [sv21 mas_makeConstraints:^(MASConstraintMaker *make) { 29 make.size.mas_equalTo(CGSizeMake(50, 20)); 30 }]; 31 [sv31 mas_makeConstraints:^(MASConstraintMaker *make) { 32 make.size.mas_equalTo(CGSizeMake(40, 60)); 33 }]; 34 [sv distributeSpacingHorizontallyWith:@[sv11,sv12,sv13]]; 35 [sv distributeSpacingVerticallyWith:@[sv11,sv21,sv31]];
可以看下这个较复杂页面的约束,其中的桃红色的线条都是约束线:
本篇先到此,下篇做更深入的学习。