如何做好IOS View的布局
(文章转载自:http://blog.cnbluebox.com/blog/2015/09/18/howtolayoutview/)
如何做好IOS View的布局
这个命题貌似有点大,那就尽量将我理解的分享一下吧,首先说明一点,我是代码党,所以我所讲的都是代码布局。本文会围绕一些我们平常开发中常遇到的布局问题来进行叙述,包括以下几个方面:
- 如何布局UIViewController的view
- childViewController的处理
- Autolayout来布局
- tableView管理
1.如何布局UIViewController的view
首先给出设计原则:
- 屏幕尺寸变化时能自适应,如不同尺寸设备,屏幕旋转,热点,电话等。
- 无论是否有navigationBar或tabBar都能够正常显示,即要考虑是否有这些bar的所有场景
- 尽量避免hard code间距,如20,44,49等
1.1 是否全屏
自从ios7扁平化设计以来,高斯模块是为你的应用增色的很好的工具,而为了更好的让navigationBar和tabBar实现高斯模糊的效果,最好让UIViewController能够全屏布局。我们在设计一个页面时,最好先确定好是否需要全屏布局,确定了这一点,我们就很简单的这样设置来决定是否全屏布局。
不需要全屏布局:
1
|
|
需要全屏布局:
1
2
|
|
1.2 subview的布局
对于subview的frame设置不难,重要的是要做到以下2点:
- 在ViewController的view尺寸变化时能自适应,如屏幕旋转,热点,电话等。
- 无论是否有navigationBar或tabBar都能够正常显示
做到第一点不难,不使用Autolayout也可以做到,那就是设置view的 autoresizingMask, 这个属性在还是frame布局时代是适配的利器。比如这样设置就可以让subview始终和view的尺寸一致:
1
2
|
|
这个的详细使用可以参考Autoresizing这里面的介绍,本文不详细描述。
但是要做到第二点不使用Autolayout就有点捉襟见肘了,因为ios7以上全屏布局到处都是,为了能更好适配不至于navigationBar或者tabBar挡住了内容,当然你可以说不需要全屏,但是有一种情况你还要考虑:
在没有navigationBar的情况下,statusBar的高度不被考虑,于是你又不得不做出判断,当没有navigationBar的情况下,view上面留20像素来避免被statusBar挡住。
那如何来做呢,答案就是使用 LayoutGuide, 例如这个:
1
2
3
4
5
6
|
|
本文autolayout均使用 Masonry 作为示例,如果你不了解Masonry,请参考Masonry
上面的示例代码就保证了view不会被顶部或者底部的“条”遮住。
1.3 scrollView的contentInset
上面一小节的例子说明了如何在view是全屏的时候如何布局subview不被挡住, 但是如果我的subview是 UIScrollView, 也要全屏布局呢(为了炫酷的高斯模糊效果), 一般的做法就是设置 contentInset来避免内容被挡住, 这里我的答案是设置automaticallyAdjustsScrollViewInsets, 你是不是想:不是逗我吧,这东西不是坑么,很容易不起作用的。下面我来解释下。
为什么要使用automaticallyAdjustsScrollViewInsets就是我们要依从上面原则中的b. 不依赖当前是否有navigationBar或者tabBar来hardCode布局subview。 当然你也可以通过判断当前是否有navigationBar或者tabBar来手动计算,但是这样不是感觉有点dirty么。
苹果设计UIViewController的automaticallyAdjustsScrollViewInsets这个属性就是为了应对scrollView的全屏布局的,会依据viewController所处的环境(是否有navigationBar或者tabBar之类的bar), 在UIViewController的view moveToWindow的时候,自动设置scrollView的 contentInset 和scrollIndicatorInsets来保证内容不被挡住, 但完美使用且不出问题要满足以下的条件:
- UIScrollView是 UIViewConroller的view的第一个subview.
- UIScrollView的位置正好是view的bounds
我想说的是,我们完全可以满足上面两点要求,经过我实践的经验,如果你的布局设计稿满足不了以上两点要求的页面,这样的页面也没有什么需要全屏的必要,这种情况下就不要全屏就好了嘛。当然如果你还是会有这样的需求,也有方法,参考2.2
1.4 SCREEN_WIDTH 和 SCREEN_HEIGHT
相信很多同学的工程里面一定可以搜到这两个宏,或者类似的东西,即是在我们使用了Autolayout布局之后,某些场景可能还是会让我们使用到这两个参数。比如设置 preferredMaxLayoutWidth(IOS7,IOS8已经可以自动) 的时候,很多时候直接用SCREEN_WIDTH来计算。
但是这两个东西也算是HARD CODE啊,只是目前用起来还好,其实也应该摒弃,像ipad分屏出来以后,这个东西就成了麻烦的事情了,但是麻烦我们也要解决啊,不然久而久之这个一定会是个坑的。
思考为了移除 SCREEN_WIDTH 和 SCREEN_HEIGHT, 我们需要兼顾哪些事情呢?
- 尽量依赖相对关系来计算size
- 类似
preferredMaxLayoutWidth这样的属性也要去除依赖 - 在view的size变化时,
preferredMaxLayoutWidth可以对应更改
上面做到第一点应该不难,用Autolayout完全满足,第二点和第三点可以给个参考,在view的layoutSubviews里面根据当前的size大小来设置 preferredMaxLayoutWidth,有人实际操作过可行,在IOS7上也实现了 Automatic 的 preferredMaxLayoutWidth。
1.5 IOS6问题
本来也想讲讲IOS6的兼容,但是现在淘宝天猫都开始不兼容IOS6了,还费精力干啥,把精力放到更有意义的事上吧,如果你们老板还在要兼容IOS6,你就打开淘宝天猫最新版,说:看!淘宝都不兼容了。
2. childViewController
在开发过程中,发现还有小部分同学对 childViewController的用法是错误的,你是否见过这样的代码呢?
1
2
3
4
5
|
|
其实这里面第二行是多余的,具体如何使用,苹果的注释里面很清楚了:
These two methods are public for container subclasses to call when transitioning between child controllers. If they are overridden, the overrides should ensure to call the super. The parent argument in both of these methods is nil when a child is being removed from its parent; otherwise it is equal to the new parent view controller.
addChildViewController: will call [child willMoveToParentViewController:self] before adding the child. However, it will not call didMoveToParentViewController:. It is expected that a container view controller subclass will make this call after a transition to the new child has completed or, in the case of no transition, immediately after the call to addChildViewController:. Similarly removeFromParentViewController: does not call [self willMoveToParentViewController:nil] before removing the child. This is also the responsibilty of the container subclass. Container subclasses will typically define a method that transitions to a new child by first calling addChildViewController:, then executing a transition which will add the new child’s view into the view hierarchy of its parent, and finally will call didMoveToParentViewController:. Similarly, subclasses will typically define a method that removes a child in the reverse manner by first calling [child willMoveToParentViewController:nil].
大概意思是: 1. 重载 willMoveToParentViewController: 和 didMoveToParentViewController:时不要忘了super 2. addChildViewController的时候只需要在后面调用 didMoveToParentViewController: 3.removeChildViewController的时候只需要在前面 willMoveToParentViewController: 4. 如果有切换动画应该先 addChildViewController 再将view添加到parentView上并做切换动画,动画结束再didMoveToParentViewController:
好了,今天讲的是 childViewController里面的布局问题,同样会遇到上面的问题:
2.1 childViewController的layoutGuide
在 childViewController 中使用 layoutGuide 不那么好使了, IOS7上完全不对,IOS9上可以正常工作,IOS8还没有测。那如何解决呢,解决方案可以取其 parentViewController的layoutGuide嘛,参考toplayoutguide-in-child-view-controller
当然取出来的 layoutGuide 可不能直接在Autolayout里面使用了,但是可以取其 length 来进行使用。
2.2 childViewController的 contentInset
在 childViewController 中 automaticallyAdjustsScrollViewInsets 也没用了, 解决方案可以同 2.1 取其 parentViewController的 layoutGuide 的length来进行设置, 这里面有个细节要注意,就是设置 contentInset 的方法放在 viewWillLayoutSubviews 函数里面才最佳。
3. Autolayout
上面讲了一些关于 viewController 怎么处理布局的问题,下面就列举一些实用布局的实例来解释如何用Autolayout来布局,已经其好处和必要性。
3.1 组合区块
这一节我们举例一个简单的区块布局,常见一些电商类活动资源模板建立。需求如下:
_______________________________________________________________________ | _______________3________________ ________________3_______________ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | view2 | | | | | | | | | | | | |3| | | |3| | | | | view1 | |________________________________| | |3| | _______3______ _______3_______ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | view3 | | view4 | | | | | | | | | | | | | | | | | | | | | | | | | | | |________________________________| |______________| |_______________| | |_____________3__________________________________3______________________|
其中 view1 和 view2 同宽, view2 和 view3, view4 同高, view3 和 view4 同宽, 所有的margin都是3。要完成这样要求的布局,可以很容易的用Autolayout来完成, 只需要指定好这些间距和宽度的关系就好了。
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
|
|