iOS中的代码自动布局一直用的Masonry,最近发现MyLinearLayout,SDAutoLayout功能更强大,Masonry更多的只是NSLayoutConstraint的简易书写版本,没有更多的封装功能,这里实践下用三个库写实现同样的界面。
属性 | Masonry | MyLinearLayout | SDAutoLayout |
---|---|---|---|
左侧 | left | myLeftMargin | leftSpaceToView |
右侧 | right | myRightMargin | rightSpaceToView |
上侧 | top | myTopMargin | topSpaceToView |
下侧 | bottom | myBottomMargin | bottomSpaceToView |
横向中点 | centerX | myCenterXOffset | centerXIs |
纵向中点 | centerY | myCenterYOffset | centerYIs |
本文会用三种框架实现两个常用的情景 :UIScrollView自动计算contentSize,UITableview高度自适应 来体验下
这里借用下forkingdog的数据源与图片
Masonry
这个没什么好说,具体会体现在代码里,贴两个写的很好的用法教程
-
在UIScrollView顺序排列一些view并自动计算contentSize
这个直接拿链接里的例子,加上自己的理解注释
UIScrollView *scrollView = [UIScrollView new];
scrollView.backgroundColor = [UIColor whiteColor];
[self.view addSubview:scrollView];
/*先加到父视图上,再设置约束,这个步骤必须在前!!否则会崩溃
SDAutoLayout一样也是必须先加到父视图上,才能设置自动布局
*/
[scrollView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self.view).with.insets(UIEdgeInsetsMake(5,5,5,5));//设置scrollView大小与父视图内边距为5
}];
/*
创建一个视图容器container加到scrollView里
container的edges与scrollView相同,这个决定container的位置,与scrollView各边距为0
container的width与height才是决定它的大小,而这个也决定了scrollView的contentSize
在这里(伪代码)
scrollView.contentSize = CGSizeMake(container.witdth + container.左边距 + container.右边距,
container.height + container.顶部边距 + container.底部边距,)
而container的width与scrollView相同,且左边距右边距为0,所以实际上横向不能滑动
container的height由其子视图决定,具体分析下for循环里的代码
*/
UIView *container = [UIView new];
[scrollView addSubview:container];
[container mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(scrollView);
make.width.equalTo(scrollView);
}];
int count = 5;
//自上而下的顺序依次排列视图
UIView *lastView = nil;
for ( int i = 1 ; i <= count ; ++i ){
UIView *subv = [UIView new];
subv.backgroundColor = [UIColor colorWithHue:( arc4random() % 256 / 256.0 )
saturation:( arc4random() % 128 / 256.0 ) + 0.5
brightness:( arc4random() % 128 / 256.0 ) + 0.5
alpha:1];
//先加入到父视图,然后再设置其约束
[container addSubview:subv];
[subv mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.and.right.equalTo(container);//左右边距为0,相当于宽度与父视图与相等
make.height.mas_equalTo(@(100+(i*20)));//设置高度
if ( lastView ){
make.top.mas_equalTo(lastView.mas_bottom);//设置排列在上一视图的下端
}
else{
make.top.mas_equalTo(container.mas_top);//设置在顶端的子视图与父视图顶部边距相同
}
}];
lastView = subv;
}
/*最后设置一下最底部的lastView与父视图container底部的距离,以此能自动计算出container的高度,
也就算出了scrollView的contentSize,确定了能滑动的范围
*/
[container mas_makeConstraints:^(MASConstraintMaker *make) {
make.bottom.equalTo(lastView.mas_bottom);
}];
-
UITableview 高度自适应
Masonry搭配UITableView-FDTemplateLayoutCell使用
MyLinearLayout
一套基于对frame属性的设置,并通过重载layoutSubviews函数来实现对子视图进行布局的布局框架。
- #######在UIScrollView顺序排列一些view并自动计算contentSize
/*这样写居然没有任何效果...
UIScrollView *scrollView = [UIScrollView new];
scrollView.backgroundColor = [UIColor whiteColor];
scrollView.myMargin = 5;
[self.view addSubview:scrollView];
*/
/*第一种方式 就正常设置frame
UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:self.view.bounds];
scrollView.backgroundColor = [UIColor whiteColor];
scrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; //让uiscrollView的尺寸总是保持和父视图一致。
[self.view addSubview:scrollView];
*/
//第二种方式 反正就要滚动,设置self.view = scrollView
UIScrollView *scrollView = [UIScrollView new];
scrollView.backgroundColor = [UIColor whiteColor];
self.view = scrollView;
/*由于我们是要实现从上而下按顺序添加视图 所以这里涉及到线性布局MyLinearLayout
线性布局是一种里面的子视图按添加的顺序从上到下或者从左到右依次排列的单列(单行)布局视图
*/
/*MyLayoutViewOrientation_Vert = 0, //整体从上到下
MyLayoutViewOrientation_Horz = 1, //整体从左到右
*/
MyLinearLayout *contentLayout = [MyLinearLayout linearLayoutWithOrientation:MyLayoutViewOrientation_Vert];
contentLayout.padding = UIEdgeInsetsMake(5, 5, 5 ,5); //设置布局内的子视图离自己的边距.
contentLayout.myLeftMargin = 0;
contentLayout.myRightMargin = 0; //同时指定左右边距为0表示宽度和父视图一样宽
/*
heightDime 这种加上Dime的属性,是相当于设置自动布局尺寸,
而myHeight则是直接设置frame里的height
*/
contentLayout.heightDime.lBound(scrollView.heightDime, 10, 1); //高度虽然是wrapContentHeight的。但是最小的高度不能低于父视图的高度加10.
[scrollView addSubview:contentLayout];
int count = 5;
//自上而下的顺序依次排列视图
for ( int i = 1 ; i <= count ; ++i ){
UIView *subv = [UIView new];
subv.backgroundColor = [UIColor colorWithHue:( arc4random() % 256 / 256.0 )
saturation:( arc4random() % 128 / 256.0 ) + 0.5
brightness:( arc4random() % 128 / 256.0 ) + 0.5
alpha:1];
/*
由于MyLinearLayout都帮我们处理好了上下图关系,
只需要设置下位置大小就好
*/
subv.myHeight = 100+(i*20);
subv.myTopMargin = 0;
subv.myLeftMargin = 0;
subv.myRightMargin = 0;
[contentLayout addSubview:subv];
}
-
UITableview 高度自适应
代码片段
MyLayoutFeedCell *cell = (MyLayoutFeedCell*)[tableView dequeueReusableCellWithIdentifier:kMyLayoutFeedCellIdentify];
if (cell == nil) {
cell = [[MyLayoutFeedCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:kMyLayoutFeedCellIdentify];
}
cell.entity = self.dataSource[indexPath.row];
return cell;
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
MyLayoutFeedCell *cell = (MyLayoutFeedCell *)[self tableView:tableView cellForRowAtIndexPath:indexPath];
//通过布局视图的estimateLayoutRect函数能够评估出UITableViewCell的动态高度。estimateLayoutRect并不会进行布局
//而只是评估布局的尺寸,这里的宽度不传0的原因是上面的UITableViewCell在建立时默认的宽度是320(不管任何尺寸都如此),因此如果我们
//传递了宽度为0的话则会按320的宽度来评估UITableViewCell的动态高度,这样当在375和414的宽度时评估出来的高度将不会正确,因此这里需要
//指定出真实的宽度尺寸;而高度设置为0的意思是表示高度不是固定值需要评估出来。
//UITableViewCell的动态高度评估不局限于线性布局,相对布局也是同样适用的。
CGRect rect = [cell.rootLayout estimateLayoutRect:CGSizeMake(tableView.frame.size.width, 0)];
return rect.size.height + 1; //如果使用系统自带的分割线,请返回rect.size.height+1
}
SDAutoLayout
实际上一样也是通过对frame的设置,为UIView写了一个SDAutoLayout分类,在+(void)load交换layoutSubviews方法,实现自己的sd_layoutSubviews
@implementation UIView (SDAutoLayout)
+ (void)load
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSArray *selStringsArray = @[@"layoutSubviews"];
[selStringsArray enumerateObjectsUsingBlock:^(NSString *selString, NSUInteger idx, BOOL *stop) {
NSString *mySelString = [@"sd_" stringByAppendingString:selString];
Method originalMethod = class_getInstanceMethod(self, NSSelectorFromString(selString));
Method myMethod = class_getInstanceMethod(self, NSSelectorFromString(mySelString));
method_exchangeImplementations(originalMethod, myMethod);
}];
});
}
- #######在UIScrollView顺序排列一些view并自动计算contentSize
UIScrollView *scrollView = [UIScrollView new];
scrollView.backgroundColor = [UIColor whiteColor];
// SDAutoLayout 与 Masonry相同,先加入到父视图,后设置布局
[self.view addSubview:scrollView];
scrollView.sd_layout.spaceToSuperView(UIEdgeInsetsMake(5,5,5,5));//设置scrollView与父视图各边距为0,即确定了大小与位置
int count = 5;
//自上而下的顺序依次排列视图
UIView *lastView = nil;
for ( int i = 1 ; i <= count ; ++i ){
UIView *subv = [UIView new];
subv.backgroundColor = [UIColor colorWithHue:( arc4random() % 256 / 256.0 )
saturation:( arc4random() % 128 / 256.0 ) + 0.5
brightness:( arc4random() % 128 / 256.0 ) + 0.5
alpha:1];
[scrollView addSubview:subv];
subv.sd_layout
.leftSpaceToView(scrollView,0)
.rightSpaceToView(scrollView,0)
.heightIs(100+(i*20));
if ( lastView ){
subv.sd_layout.topSpaceToView(lastView,0);//设置排列在上一视图的下端
}
else{
subv.sd_layout.topSpaceToView(scrollView,0);//设置在顶端的子视图与父视图顶部边距相同
}
lastView = subv;
}
// scrollview自动contentsize
[scrollView setupAutoContentSizeWithBottomView:lastView bottomMargin:0];
-
UITableview 高度自适应
代码片段
SDAutoLayoutFeedCell *cell = [tableView dequeueReusableCellWithIdentifier:kSDAutoLayoutFeedCellIdentify forIndexPath:indexPath];
cell.entity = self.dataSource[indexPath.row];
// 此步设置用于实现cell的frame缓存,可以让tableview滑动更加流畅
[cell useCellFrameCacheWithIndexPath:indexPath tableView:tableView];
return cell;
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
// cell自适应步骤2 model 为模型实例, keyPath 为 model 的属性名,通过 kvc 统一赋值接口
return [self.tableView cellHeightForIndexPath:indexPath model:self.dataSource[indexPath.row] keyPath:@"entity" cellClass:[SDAutoLayoutFeedCell class] contentViewWidth:[self cellContentViewWith]];
}
总结
这两个库都有丰富的代码例子和教程,都还在迭代更新中
MyLinearLayout
SDAutoLayout
MyLinearLayout的入门稍微高点,一来就是基于线性布局构建,还多达6种布局,相对布局,框架布局,表格布局,流式布局,浮动布局,当然它的功能是最强大的,布局思想也是很好,甚至可以替代UITableView,UICollectionView,也加入了IBInspectable,可以在界面上布局。虽然如此,学习成本较高,而且自然不能完全替代UITableView,UICollectionView,很多一些动画效果手势交互体验是无法做到的。
SDAutoLayout 看介绍是已经有很多项目用上了,仔细看发现并没有封装太多的功能,顶多就是等距或者等宽排列一组视图,一些基本用法思想基本与NSLayoutConstraint类似,易上手。
Masonry 加上UITableView,UICollectionView,UIStackView布局还是我的首选吧,很多复杂的界面只要用这几个配合好都能很好实现。
但是还是愿意尝试下新功能,分享MyLinearLayout的官方例子中对于流标签的实现,就非常简洁
代码片段
self.flowLayout = [[MyFlowLayout alloc] initWithOrientation:MyLayoutViewOrientation_Vert arrangedCount:0];
self.flowLayout.myMargin = 0;
self.flowLayout.myTopMargin = 64;
self.flowLayout.subviewMargin = 10;
/**
* 设置布局视图四周的内边距值。所谓内边距是指布局视图内的所有子视图离布局视图四周的边距。通过为布局视图设置内边距可以减少为所有子视图设置外边距的工作,而外边距则是指视图离父视图四周的距离。
*/
self.flowLayout.topPadding = 20;
self.flowLayout.leftPadding = 20;
self.flowLayout.rightPadding = 15;
[self.view addSubview:self.flowLayout];
[[DataManager shareManager].tagTexts enumerateObjectsUsingBlock:^(NSString * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
[self createTagButton:obj];
}];