众所周知,对于IOS开发者来讲,随着手机屏幕的尺寸在增多,不可避免的也须要考虑适配的问题了。这个问题在IOS6之前咱们能够经过autoresizingMask和frame进行组合来解决视图伸缩、旋转的适配,可是这个方案不完全仍是须要编写不少的代码来完成;而在IOS6之后推出了AutoLayout的解决方案,这个方案的实现和可操做性太过于复杂和繁琐不只编写的代码特别多,就是在XIB上进行布局约束也很麻烦和难以管理。因而乎有大牛就对AutoLayout进行了改造和精简推出了一些简化自动布局的框架好比:android
Masonry库,CocoaUI库git
我有幸的拜读和简单使用了一下这些库,发现确实有一些好处,能够简化视图布局的处理。但使用起来仍是有一些麻烦,这些库都是对IOS的AutoLayout进行封装而已。所以自己若是不对自动布局不很了解的话也很容易绕道复杂的约束冲突中去;IOS8后由于有了4.7寸,5.5寸的屏幕后推出了sizeClass来进行布局.程序员
那么有了这些后为何咱们仍是不能知足??还要提出MyLinearLayout解决方案?github
1.咱们的应用还要支持到6.0如下。编程
2.我是一个老程序员不想学习新的布局语言。数组
3.AutoLayout,size Class语法过于晦涩并且很差控制,个人frame甚至没法使用了,个人动画特效也很差处理了。xcode
4.MyLinearLayout的方法和使用逻辑简单,易用并且功能强大。app
5.支持国产原创的库发展。框架
若是上述理由您没有一条赞成的话那么能够关闭掉这篇文章了。ide
对于视图的布局来讲,android系统的解决方案相对来讲仍是比较好的,对于android系统来讲咱们基本上不须要设置视图摆放的具体位置,所以通常也不须要设置视图的绝对位置值和绝对大小值,而是经过wrap_content,match_parent来指定视图自己的相对高度和宽度,同时经过LinearLayout, RelativeLayout,FrameLayout等布局来决定里面子视图的排列和摆放的位置,经过weight,padding,margin,gravity等属性来设置视图尺寸比例和排列对齐以及间距方面的东西, 而对于IOS来讲咱们之前编码时对于视图的布局老是经过setFrame来实现,用这种方法进行编程的最大的问题是咱们必须在代码中写死不少位置的常量,并且还要本身进行计算高度和宽度以及边距等等,通常屏幕尺寸不一样还须要对不一样的尺寸进行处理,一样对于AutoLayout来讲咱们须要在代码里面编写大量的约束,形成的结果就是代码复杂和难以理解,对于维护来讲更加是个灾难,而对于布局的微调更加是一个灾难。
基于上面的各类因素以及参考,MyLinearLayout横空出世了:https://github.com/youngsoft/MyLinearLayout
MyLinearLayout的实现充分参考了Android中的LinearLayout布局,可是却比LinearLayout更为强大,他几乎能够实现AutoLayout的全部功能甚至其不具有的功能。MyLinearLayout是一个基于流式布局的容器视图,咱们只须要把子视图添加到MyLinearLayout中,并设置一些简单的约束参数那么就能够完成各类布局的要求了,并且后续中只要子视图的位置和大小进行变化都会触发容器视图里面的子视图进行从新布局。
我先简单的对MyLinearLayout里面的属性和函数进行介绍,而后咱们再实现一些布局的场景的代码的实现。MyLinearLayout既能够用于编码实现有能够用在XIB中使用,可是在XIB中使用时请把AutoLayout的支持去掉,由于MyLinearLayout不是基于自动布局的。
在介绍MyLinearLayout以前,咱们先要对视图扩展出一些属性,这些属性只用于MyLinearLayout中。我会在后面一一介绍这些属性以及用法。
@interface UIView(LayoutExt)
//下面4个属性用于指定视图跟他相关视图之间的间距,若是为0则是没有间距,若是>0 <1则是相对间距,是按父视图的比例算的,好比父视图是100,而左间距是0.1则值是10。若是大于等于1则是绝对间距
//通常当使用相对间距时主要用图是子视图的宽度和高度是固定的,只是边距随父视图的大小而调整。
@property(nonatomic,assign)CGFloat topMargin;
@property(nonatomic,assign)CGFloat leftMargin;
@property(nonatomic,assign)CGFloat bottomMargin;
@property(nonatomic,assign)CGFloat rightMargin;
@property(nonatomic,assign)CGFloat centerXOffset;
@property(nonatomic,assign)CGFloat centerYOffset;
@property(nonatomic,assign)CGPoint centerOffset;
//设定视图的高度在宽度是固定的状况下根据内容的大小而浮动,若是内容没法容纳的话则自动拉升视图的高度,若是原始高度高于内容则会缩小视图的高度。默认为NO,这个属性主要用UILabel,UITextView的多行的状况。
@property(nonatomic,assign,getter=isFlexedHeight)BOOL flexedHeight;
@end
@interface UIView(LinearLayoutExtra)
//比重,指定自定的高度或者宽度在父视图的比重。取值为>=0 <=1,这个特性用于平均分配高宽度或者按比例分配高宽度
@property(nonatomic,assign)CGFloat weight;
@end
这些属性只用在MyLinearLayout中和MyFrameLayout(框架布局,请浏览个人另一篇文章)中才有意义。若是咱们在XIB中进行布局的话咱们能够在自定义数据设置界面指定这些属性。
接下来咱们在介绍MyLinearLayout的定义,对于流式布局来讲简单点就是从上到下或者从左到右,所以咱们定义了垂直布局和水平布局两种样式。
//布局排列的方向
typedefenum : NSUInteger {
LVORIENTATION_VERT,
LVORIENTATION_HORZ,
} LineViewOrientation;
以及MyLinearLayout的属性:
@property(nonatomic,assign)LineViewOrientation orientation;
orientation = LVORIENTATION_VERT orientation = LVORIENTATION_HORZ
由于垂直布局和水平布局的实现都是同样的,下面的例子我都将以垂直布局进行举例,同时在没有特殊说明的状况下我会把MyLinearLayout的背景设置为灰色。
1、子视图间距设置以及自动调整大小的属性
要实现上面的布局须要键入下面的代码:
上面的代码中实现了垂直布局的代码,在这段代码中咱们发现v1,v2,v3的frame中x,y值部分不须要指定和计算了,都默认设置为0,而改用topMargin和bottomMargin,leftMargin和rightMargin来指定视图之间的间距,这样一个好处是当某个子视图的高度变化时布局会自动从新进行子视图位置的排列,而不要手动进行调整。 同时能够发现虽然MyLinearLayout的高度设置为200,但实际高度确是147,这是怎么回事呢? 这是由于MyLinearLayout中有两个属性:
//高宽是否由子视图决定,这两个属性只对线性和相对布局有效,框架布局无效
@property(nonatomic,assign)BOOL wrapContentWidth;
@property(nonatomic,assign)BOOL wrapContentHeight;
这两个属性表示布局视图的宽度和高度是由子视图决定的,这个就像android的wrapContent同样,对于垂直布局来讲默认wrapContentHeight设置为YES,而对于水平布局来讲默认wrapContentWidth设置为YES。固然你也能够在垂直布局中将wrapContentHeight设置为NO。下面的例子就是:
2、布局里面子视图的隐藏显示以及对UIScrollView的支持。
有时候有一些场景中,当某个或者某几个视图隐藏时,咱们但愿下面的视图可以自动往上移动以便填补空白,而当某个视图再次显示时下面的视图又再次往下移动,很幸运! MyLinearLayout是支持这种状况的(视图必须是MyLinearLayout的直接子视图才能够)。
另外由于咱们有wrapContentHeight属性,所以咱们能够把线性布局放入到一个ScrollView中(这又有点像android中的scrollview的方式)。来实现ScrollView的contentSize值的自动高度的调整。
这段代码以下:
从代码中咱们能够看出当咱们要隐藏和显示某个子视图时直接设置子视图隐藏和取消隐藏而不须要再编码来调整整个视图的高度,不须要编码来移动下面兄弟视图的位置,不须要编码来调整父UIScrollView的contentSize来调整高度,咱们还能够看到UIScrollView下只须要添加一个布局视图,同时咱们还指定了布局视图的宽度和UIScrollView是保持一致的。同时咱们还看到了UILabel中使用了扩展属性flexedHeight,这个属性设置为YES时,系统会在布局时自动根据指定的宽度来调整本身的高度,从而多行显示完全部的内容,这个属性很是的强大。
3、布局内视图位置的停靠以及布局的内部边距设定以及子视图大小的指定
有时候咱们但愿布局里面的全部子视图的位置都是固定的,好比全部子视图左对齐,或者居中对齐,或者居右对齐。这时候咱们就须要用到布局中的以下属性了:
@property(nonatomic,assign)MarignGravity gravity;
这个属性用于指定布局中的全部子视图的停靠位置,一共有以下的定义:
MGRAVITY_NONE //不强制全部子视图的停靠方案而是子视图本身决定本身的停靠位置
MGRAVITY_HORZ_LEFT //水平居左停靠
MGRAVITY_HORZ_CENTER // 水平居中停靠
MGRAVITY_HORZ_RIGHT //水平居右停靠
MGRAVITY_HORZ_FILL //水平填充
MGRAVITY_VERT_TOP //垂直居顶停靠
MGRAVITY_VERT_CENTER //垂直居中停靠
MGRAVITY_VERT_BOTTOM //垂直居低停靠
MGRAVITY_VERT_FILL //垂直填充
MGRAVITY_CENTER //居中
MGRAVITY_FILL //填充
这些定义中当gravity为MGRAVITY_NONE时则代表不使用停靠策略,也就是按子视图自身的停靠策略,好比有一些场景中咱们但愿全部的子视图都左边对齐,那么咱们能够设置布局视图的gravity的值为:MGRAVITY_HORZ_LEFT。而有时候咱们又但愿布局中的部分子视图左边对齐,部分右边对齐则咱们不须要设置布局视图的gravity值而是分别对每一个子视图自行设定。
有时候咱们但愿布局里面的全部子视图都跟布局保持必定的间距,这时候咱们就能够用以下的属性:
@property(nonatomic,assign)UIEdgeInsets padding; //用来描述里面的子视图的离本身的边距,默认上下左右都是0
//这个是上面属性的简化设置版本。
@property(nonatomic,assign)CGFloat topPadding;
@property(nonatomic,assign)CGFloat leftPadding;
@property(nonatomic,assign)CGFloat bottomPadding;
@property(nonatomic,assign)CGFloat rightPadding;
padding属性用来描述里面的全部子视图跟本身保持的边界距离。
下面代码显示了垂直布局中的左中右三种停靠方式,以及四周的边距都设置为5,这样是否是进一步的简化了编码?
上面的视图中咱们能够经过为布局视图设置gravity的值来调整里面全部子视图的左右方向的停靠位置,一样咱们也能够经过设置布局视图的gravity的值来调整全部子视图上下方向的停靠位置,见下面的图片:
代码以下:
从上面两段代码中咱们能够经过gravity的值来设置布局视图内的全部视图的停靠方式,可是有的时候咱们不但愿由布局视图来统一控制停靠的方向(对于垂直布局的话是指左右方向,对于水平布局的话是指上下方向),而是但愿由子视图自生来控制停靠的位置。这时候必需要将线性布局的gravity设置为MGRAVITY_NONE.
代码以下:
上面的四个子视图中v1设置了leftMargin=10表示左边边距为10, v2设置了centerXOffset = 0 表示水平居中, v3设置了rightMargin=10表示右边边距为10, 而v4设置了leftMargin=5,rightMargin = 5则表示两边的边距都是5,那么v4的宽度就会动态的调整。所以有时候咱们想让子视图的宽度等于父视图时则能够设置以下:
v.leftMargin = 0;
v.rightMargin = 0;
在布局时,咱们能够经过frame来设置子视图的高度和宽度的绝对值,咱们能够经过扩展属性:
/*
下面两个属性是上面widthDime,heightDime的equalTo设置NSNumber类型值的简化版本,主要用在线性布局和框架布局里面
表示设置视图的宽度和高度,须要注意的是设置这两个值并非直接设置frame里面的size的宽度和高度,而是设置的布局的高度和宽度值。
也就是说设置和获取值并不必定是最终视图在布局时的真实高度和宽度
v.width = 10 <=> v.widthDime.equalTo(@10)
*/
@property(nonatomic,assign)CGFloat width;
@property(nonatomic,assign)CGFloat height;
来设置视图的高度和宽度值,须要注意的是经过width,height设置的值并非frame设置的高度和宽度值,而是在布局时才会生效的高度值和宽度。
咱们也能够经过同时设置leftMargin 和rightMargin的值来指定视图的宽度值。但有时候咱们但愿能有更增强大的功能,好比但愿某个子视图是布局视图宽度的50%或者80%或者但愿比布局视图的宽度减小50等等。
这时候咱们经过frame或者经过设置width就不能实现了,可是咱们可使用高级的子视图扩展属性。
//尺寸,若是设置了尺寸则以设置尺寸优先,不然以视图自身的frame的高宽为基准
@property(nonatomic,readonly) MyLayoutDime *widthDime;
@property(nonatomic,readonly) MyLayoutDime *heightDime;
上面的width和height设置是这两个属性设置的简化版本,咱们能够看看MyLayoutDime的定义:
@interface MyLayoutDime :NSObject
//乘
-(MyLayoutDime* (^)(CGFloat val))multiply;
//加,用这个和equalTo的数组功能能够实现均分子视图宽度以及间隔的设定。
-(MyLayoutDime* (^)(CGFloat val))add;
//NSNumber, MyLayoutDime以及MyLayoutDime数组,数组的概念就是全部数组里面的子视图的尺寸平分父视图的尺寸。
-(MyLayoutDime* (^)(id val))equalTo;
@end
高级的widthDime和heightDime一般用在相对布局里面,可是现行布局也是支持。好比咱们但愿子视图的宽度和父视图相等则:
v.widthDime.equalTo(v.superView.widthDime)
若是咱们但愿子视图的宽度是父视图的50%则
v.widthDime.equalTo(v.superView.widthDime).multiply(0.5)
而咱们但愿子视图的宽度比父视图减小100则
v.widthDime.equalTo(v.superView.widthDime).add(-100)
咱们但愿子视图的宽度就是100则
v.widthDime.equalTo(@100) <==> v.width = 100
看以下布局:
代码以下:
上面的代码中咱们使用了视图的高级扩展属性widthDime, heightDime,这两个一般用在相对布局MyRelativeLayout的处理中。具体的介绍能够看个人关于相对布局的处理的文章。
4、终极武器1:子视图的相对尺寸,以及布局视图的尺寸,复杂布局的处理。
在某些时候,咱们知道了布局视图的高度的状况下,想平分里面全部子视图的高度,或者里面的子视图的高度咱们只须要指定相对值而不须要指定绝对值,若是咱们想在布局视图里面增长2个视图,其中一个视图占用布局视图的40%,而另一个视图占用60%。由于若是支持子视图的相对高度的话,那么当布局视图进行缩放或者进行旋转时里面的子视图都会按照指定比例进行缩放,这时候咱们须要用到上面视图的一个强大的扩展属性:weight了:
@property(nonatomic,assign)CGFloat weight;
这个视图的扩展属性的设置的范围是0到1表示子视图自己在布局视图中所占用的高度或者宽度的比例,下面咱们来实现一个自动布局里面一个最经典的需求:
每一个视图的间距都为20,分为上下2个部分,各占用50%,上面的2个视图各占用50%。
从上面的代码能够看出,其中没有使用到任何绝对的位置和大小的数字,都是相对值,为了支持复杂的布局咱们使用了MyLinearLayout的嵌套的方式来解决问题。
经过为子视图的weight的指定咱们能够很灵活的对布局里面的子视图的高度进行设置,一个布局中咱们能够设置某些子视图的绝对高度,也能够设置另一些子视图的weight。好比:
1.某个线性布局有3个子视图,而且顶部和底部的视图的高度都是固定的,而中间的视图则占用布局的剩余高度则能够以下设置:
v1.frame = CGRectMake(0,0,x, 30)
v2.frame = CGRectZero
v2.weight = 1.0
v3.frame = CGRectMake(0,0,x,50)
2.某个线性布局有3个子视图,顶部的视图高度固定的,而底部两个视图则按剩下的高度的4:6来进行分配则能够设置以下:
v1.frame = CGRectMake(0,0,x, 30)
v2.frame = CGRectZero
v2.weight = 0.4
v3.frame = CGRectZero
v3.weight = 0.6
5、终极武器2:布局视图的高度和宽度彻底由子视图来控制。
在垂直布局中,咱们知道布局的高度能够由全部子视图动态调整,那么宽度是否也能够由子视图来决定呢?这是能够了!!当布局中某个子视图的宽度是肯定的,我么能够选择由子视图里面最宽的那个视图来决定布局视图的宽度。而布局中的属性wrapContentXXX则相似于android的wrap_content的值:
//高宽是否由子视图决定,这两个属性只对线性和相对布局有效,框架布局无效
@property(nonatomic,assign)BOOL wrapContentWidth;
@property(nonatomic,assign)BOOL wrapContentHeight;
上面的视图中能够看到布局视图的宽度是第一个视图的宽度外加上leftMargin,rightMargin的总值,由于垂直布局中的wrapContentHeight默认是设置为YES的。
有的时候咱们但愿让某个布局视图设置为某个非布局视图的子视图,好比咱们但愿创建一个布局视图所有覆盖到某个view下,这时候咱们同样的能够在将布局视图添加到非布局视图下时经过指定本身的leftMargin,rightMargin, topMargin,bottomMargin来设置布局视图距离父视图的边距值,一旦将某个布局视图放入到非布局视图时而且又但愿这个布局视图随非布局视图的大小而调整时,须要注意的是要将wrapContentHeight,wrapContentWidth 设置为NO,由于若是设置为YES的话就会产生布局的冲突。
6、终极武器3:视图之间的间距也能够是相对值。
上面的全部布局中,咱们可让布局视图随着子视图的尺寸进行大小的调整,也可让子视图随着布局视图的尺寸进行大小的调整,也可使用weight进行子视图的按比例尺寸分配,也可使用gravity进行子视图的位置和尺寸的控制。可是前面的代码中全部的间距指定的都是固定值,正是由于间距部分是固定值,所以咱们仍是没法好好的适配不一样尺寸的屏幕,好比有时候咱们但愿子视图的尺寸是固定的,可是视图之间的间距是随着屏幕尺寸的大小而调整。在好比下面的登陆对话框
咱们要求底部版权部分固定在底部,而且有固定的底部边距,而中间的图标和帐号输入框的间距之间则须要根据布局的大小的调整而进行缩放。这时候由于子视图的高度是固定的,而间距是浮动的,所以解决的方法就是咱们不设置固定的间距,而是设置浮动的间距,将间距按必定比例进行指定。经过采用间距使用比例的方法咱们能够很容易的实现则不一样屏幕尺寸上以及横向和纵向屏幕上进行完美的适配,这样的话咱们是否是再也不须要size class了。 上面的子视图扩展属性中咱们已经看到了四个边距值是能够设置相对边距的,当咱们把边距设置为>0而小于1的话,则代表是按比例来设置间距。咱们能够看看以下代码是如何实现上述功能的:
上面的代码中咱们能够看到底部子视图的bottomMargin使用的是固定的间距也就是保证在底部,而头像和布局,头像和帐号,以及帐号和底部则采用的是相对的margin值,这样就是实现了上述的功能了,这样是否是很简单。
经过相对间距和子视图的weight属性,咱们还能实现不少强大的功能,好比:
1.咱们想让某个子视图跟父视图的边距始终保持在总体宽度的20%左右,那么咱们只须要为子视图的leftMargin = 0.2就能够了。
2.咱们在一个垂直布局中有3个子视图,而且要求这三个子视图的高度保持一致,同时间距也和高度保持一致,也就是平均分布三个子视图。
代码以下:
三个子视图和间距的高度都是平均的。看以下函数:
-(void)averageSubviews:(BOOL)centered;
这个函数用来指定将子视图和间距平均分配,centered表示是否总体居中,也就是是否保留顶部和底部的边距。
-(void)averageMargin:(BOOL)centered;
这个函数要求每一个子视图都具备固定的高度或者宽度,而是把全部剩余的间距所有平分,一样centered也表示视图是否居中。
须要注意的是上面两个函数只对以前添加的视图有效,后续添加的视图是无效的。
7、终极武器4:UITableView的替代品。
实践中咱们常用UITableView来布局一些静态的CELL,这种方式在某些场合确实很方面,既能够重用又能够很方便的使用滚动视图的功能,可是静态CELL一个最致命的问题是,有时候咱们的某个CELL的高度是要求动态变化的,并且有时候咱们的CELL里面有UILabel,里面的内容假如很长的话须要换行显示,从而调整CELL的高度,因而咱们就只能在:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath;
动态计算CELL的高度,而后又在CELL中进行各类麻烦的布局,并且这个问题AutoLayout是没法解决的。我象形不少人都会在这个问题上写不少代码。还有一个场景是咱们的CELL之间是能够设置分割线的,可是有时候有些需求是咱们要求顶部没有线,底部没有线等等一些奇怪的问题,因而乎咱们就须要在CELL中插入背景视图而且把CELL的分割线属性取消来解决这个问题,还有一个场景是由于CELL是能够复用的,一旦CELL不可见系统自动删除子视图的内容(注意这时候子视图上的一些状态是没法保留的),而后出现时你又要从新根据状态来设置CELL中的内容,这样你就须要单独的保存各类视图的状态数据。
其实对于一些静态CELL的状况下由于CELL的数量很少,咱们彻底不须要考虑内存以及复用的问题。咱们只要在UIScrollView上直接线性布局就能够了。这时候咱们就能够祭出MyLayout的两个大杀器了:
边界线绘制以及布局的触摸处理事件,
经过这两个属性就能够彻底把一个MyLinearLayout当作一个CELL来使用了。
边界线绘制为咱们实现四个边界的线条的绘制,他支持线条的颜色,粗细,缩进,立体,点线的绘制:
//指定四个边界线的绘制。
@property(nonatomic,strong)MyBorderLineDraw *leftBorderLine;
@property(nonatomic,strong)MyBorderLineDraw *rightBorderLine;
@property(nonatomic,strong)MyBorderLineDraw *topBorderLine;
@property(nonatomic,strong)MyBorderLineDraw *bottomBorderLine;
//同时设置4个边界线。
@property(nonatomic,strong)MyBorderLineDraw *boundBorderLine;
而后MyBorderLineDraw的定义以下:
@interface MyBorderLineDraw :NSObject
@property(nonatomic)UIColor *color; //颜色
@property(nonatomic)UIColor *insetColor; //嵌入颜色,用于实现立体效果
@property(nonatomic,assign)CGFloat thick; //厚度,默认为1
@property(nonatomic,assign)CGFloat headIndent; //头部缩进
@property(nonatomic,assign)CGFloat tailIndent; //尾部缩进
@property(nonatomic,assign)CGFloat dash; //虚线的点数若是为0则是实线。
-(id)initWithColor:(UIColor*)color;
@end
经过边界线咱们能够实现布局视图四个方向的边界线的绘制。布局视图的另外两个属性以下:
//高亮的背景色,咱们支持在布局中执行单击的事件,用户按下时背景会高亮.只有设置了事件才会高亮
@property(nonatomic,strong)UIColor *highlightedBackgroundColor;
//设置单击触摸的事件,若是target为nil则取消事件。
-(void)setTarget:(id)target action:(SEL)action;
没错!咱们能够为布局视图设置单击处理事件!以及单击时的高亮显示颜色。这两个功能是代替
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath;
的强有力武器,只要咱们把一个垂直MyLinearLayout做为UIScrollView的子视图,而且垂直的MyLinearLayout中加入多个水平的MyLinearLayout子视图(起到CELL的功能),而后为每一个水平的子布局视图添加时间处理函数就行了。 废话这么多,上界面吧。代码由于比较多就不贴出来了,直接看DEMO中的就能够了。
8、总结
MyLinearLayout的功能基本就介绍完成了,最后须要总结的是: MyLinearLayout能够彻底胜任屏幕的旋转,各类尺寸的完美适配。各类版本操做系统的完美适配,开发简单易用并且功能强大,并且他不是基于AutoLayout的也不是基于Size Class,没有版本限制,也不须要学习新的布局知识。
最后欢迎你们到个人github中去下载库和DEMO。