#iOS界面布局
目前来说iOS目前几种主流的UI布局:
- Frame布局
- AutoLayout
- 使用代码实现AutoLayout
- 使用xib或者StoryBoard
在代码可读性上 frame > 使用代码实现AutoLayout > 使用xib或者StoryBoard
在开发效率上 使用xib或者StoryBoard > 使用代码实现AutoLayout > Frame布局
AutoLayout
是通常是通过定义一系列的约束(constrains)来进行定的。和Frame定位一样,它同样需要你提供位置和尺寸信息,但是和Frame不同的是,它不是让你直接提供x,y,width,height,而是通过你给的约束来推断出相应的尺寸和位置。
AutoLayout本质
一次函数:
y = ax + b
item1.attribute1 = multiplier × item2.attribute2 + constant
看一段原生的代码实现AutoLayout(NSLayoutConstraint)
VFL是这么写的:
1 2 3 4 5 6 7
| NSDictionary *viewsDictionary = [NSDictionary dictionaryWithObjectsAndKeys:label1,@"label1",label2,@"label2",label3,@"label3",label4,@"label4", nil]; NSDictionary *metrics = [NSDictionary dictionaryWithObject:@(88) forKey:@"labelHeight"]; NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|[label1(labelHeight)]-[label2(labelHeight)]-[label3(labelHeight)]-[label4(labelHeight)]-(>=10)-|" options:NSLayoutFormatAlignAllLeft metrics:metrics views:viewsDictionary]; [view addConstraints:constraints];
|
还有一种写法:
1 2 3 4 5 6 7 8 9 10
| view.translatesAutoresizingMaskIntoConstraints = NO; NSLayoutConstraint * constraint = [NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:view attribute:NSLayoutAttributeTop multiplier:1.0 constant:10]; [view addConstraint:constraint];
|
苹果好像也发现这样写有点尴尬,于是在iOS8和iOS9中对autolayout的api进行改动:
1 2 3 4 5 6 7 8 9
| [NSLayoutConstraint constraintWithItem:view1 attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:view2 attribute:NSLayoutAttributeCenterX multiplier:1 constant:10]; view.translatesAutoresizingMaskIntoConstraints = NO; [view.widthAnchor constraintEqualToConstant:100]; [view.heightAnchor constraintEqualToConstant:100]; [view.centerXAnchor constraintEqualToAnchor:view1.centerXAnchor]; [view.centerYAnchor constraintEqualToAnchor:view1.centerYAnchor];
|
一个view的位置确定,正常情况下需要来自内部或者外部的4条约束才能确定其位置。所以使用原生代码实现起来太过繁琐,所以为了解决这个问题,将NSLayoutConstraint简化封装使用就非常有必要了,开源项目中有很多项目已经很成熟了,比如Masonry和purelayout等,下面简单介绍Masonry。
Masonry的介绍
Masonry是iOS在控件布局中经常使用的一个轻量级框架,Masonry让NSLayoutConstraint使用起来更为简洁。Masonry简化了NSLayoutConstraint的使用方式,让我们可以以链式的方式为我们的控件指定约束。
使用Masonry完成上面的代码
1 2 3
| [view mas_makeConstraints:^(MASConstraintMaker *make) { make.top.equalTo(@(10)); }];
|
当然一条约束什么都干不了,完成一个View的布局,至少有4个来自内部或者外部的约束,比如:
1 2 3 4 5 6
| [view mas_makeConstraints:^(MASConstraintMaker *make) { make.top.equalTo(@(10)); make.left.equalTo(@(10)); make.bottom.equalTo(@(-10)); make.right.equalTo(@(-10)); }];
|
Masonry最重要的4个方法
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
| * Finds the closest common superview between this view and another view * * @param view other view * * @return returns nil if common superview could not be found */ - (instancetype)mas_closestCommonSuperview:(MAS_VIEW *)view; * Creates a MASConstraintMaker with the callee view. * Any constraints defined are added to the view or the appropriate superview once the block has finished executing * * @param block scope within which you can build up the constraints which you wish to apply to the view. * * @return Array of created MASConstraints */ - (NSArray *)mas_makeConstraints:(MASConstraintMakerConfigBlock)block; * Creates a MASConstraintMaker with the callee view. * Any constraints defined are added to the view or the appropriate superview once the block has finished executing. * If an existing constraint exists then it will be updated instead. * * @param block scope within which you can build up the constraints which you wish to apply to the view. * * @return Array of created/updated MASConstraints */ - (NSArray *)mas_updateConstraints:(MASConstraintMakerConfigBlock)block; * Creates a MASConstraintMaker with the callee view. * Any constraints defined are added to the view or the appropriate superview once the block has finished executing. * All constraints previously installed for the view will be removed. * * @param block scope within which you can build up the constraints which you wish to apply to the view. * * @return Array of created/updated MASConstraints */ - (NSArray *)mas_remakeConstraints:(MASConstraintMakerConfigBlock)block;
|
MASViewConstraint的对象链式调用
如何实现链式调用
Masonry中大量的使用了链式调用的,比如
1
| view.top.left.equalTo(superView).offset(10);
|
上面的这种方式就是链式调用,而且像equalTo(superView)这种形式也不是Objective-C中函数调用的方式,在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 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
| class ClassB; interface ClassA : NSObject property(nonatomic, readonly) ClassA *(^aaa)(BOOL enable); property(nonatomic, readonly) ClassA *(^bbb)(NSString* str); @property(nonatomic, readonly) ClassB *(^ccc)(NSString* str); @implement ClassA - (ClassA *(^)(BOOL))aaa { return ^(BOOL enable) { if (enable) { NSLog(@"ClassA yes"); } else { NSLog(@"ClassA no"); } return self; } } - (ClassA *(^)(NSString *))bbb { return ^(NSString *str)) { NSLog(@"%@", str); return self; } } - (ClassB * (^)(NSString *))ccc { return ^(NSString *str) { NSLog(@"%@", str); ClassB* b = [[ClassB alloc] initWithString:ccc]; return b; } } @interface ClassB : NSObject @property(nonatomic, readonly) ClassB *(^ddd)(BOOL enable); - (id)initWithString:(NSString *)str; @implement ClassB - (ClassB *(^)(BOOL))ddd { return ^(BOOL enable) { if (enable) { NSLog(@"ClassB yes"); } else { NSLog(@"ClassB no"); } return self; } } id a = [ClassA new]; a.aaa(YES).bbb(@"HelloWorld!").ccc(@"Objective-C").ddd(NO)
|
offset(10)这种调用方式是如何实现的呢?
1 2 3 4 5 6
| - (MASConstraint * (^)(CGFloat))offset { return ^id(CGFloat offset){ self.offset = offset; return self; }; }
|
不难发现,offset是一个getter方法的名,offset函数的返回值是一个匿名Block, 也就是offset后边的()。这个匿名闭包有一个CGFloat的参数,为了支持链式调用该匿名闭包返回一个MASConstraint的对象。
优化UITableViewCell高度计算的那些事:
https://github.com/forkingdog/UITableView-FDTemplateLayoutCell
http://blog.sunnyxx.com/2015/05/17/cell-height-calculation/