Subview and Superview
作者:PMST
文章:Views - Subview and Superview
系列:The Swift Beginner
写于:2015.04.27
正文
在很久以前,苹果公司的视图绘制机制和现在是大相径庭。那时任何一个视图拥有自己的“私人领地”——精确来说就是一个矩形。假如某个非该视图的subview想要呈现其中,这是不被允许的!因为当视图重绘(redraw)矩形区域时,会擦除所有不属于自己的内容;相应地,属于该视图的subview内容只允许在指定的矩形区域内绘制,想要“越狱”(将内容绘制到矩形框外)出去,没门!
而从OS X 10.5开始,这些条条框框都已经被废除了。目前苹果公司给出的视图绘制机制,完全摒弃了先前的那些限制。而值得庆幸的是iOS绘图就是基于新的架构!在iOS中,任何一个subview都允许从superview“越狱出去”,这意味着,打破了矩形区域的限制,子视图绘画面积不仅仅只是那么一小片区域,而是整个!此外其他视图(不属于superview的views)能够覆盖别的视图,这在以前是不被允许的。
下面给出一个小demo,下图显示了三个相互重叠的视图。为了更直观地观看,给三个视图的背景上了色。现在问题来了,你无从得知三个视图之间的关系,比如:三个视图是相互独立?谁是谁的子视图?谁和谁是同级的?
事实上,三者的关系是这样的:
- 粉色视图和红色视图是同级关系
- 绿色视图是粉色视图的子视图
在storyboard中,拖拉两个view到main view中,分别设置背景颜色为粉色和红色;然后再次拖拽一个view到粉色视图中,设其背景颜色为绿色;现在打开nib editor查看视图层级设置。
小技巧:当你的应用程序在运行时想要查看视图层级关系,选择 Debug->View Debugging-> Capture View Hierarchy。
关于视图在视图层级中的摆放位置是非常有讲究了,这也决定了视图的绘制顺序!
- 相同层级的视图(属于同一个superView)绘制顺序是固定的:自上而下,只有当前者绘制完毕,才进行下一个视图绘制。特殊情况:假如后者绘制区域和前者有重叠,那么就会造成覆盖现象,前者视图呈现在后者视图之后。
- 先绘制superview,在绘制属于父视图的subview。
结合本文第一幅图进行绘制顺序讲解,粉色视图和红色视图为同一级,同属于父视图main view;粉色视图在前,红色视图为后,因此首先绘制粉色视图;粉色视图包含绿色子视图,当粉色视图绘制完毕,接下来是绘制绿色视图(注意:同级视图之间,前者所有视图,包括子视图全部绘制完毕,才进行下一个视图
);最后是绘制红色视图,显然它会覆盖粉色以及绿色视图。
storyboard中设置层级关系
选中main.storyboard,左侧会罗列出所有视图场景,各视图层级关系一览无余。现在选中一个粉色视图,Editor-> Arrange -> Send Forward(其他还有 Send to Front,Send to Back,Send Backward)
,操作结果会把粉色视图下移到红色视图之下,意味着粉色视图在最前面,红色视图放置到最底下被遮盖。
补充一点视图层级的知识。
- 当子视图从父视图中移除,子视图下的所有视图也将被移除;当子视图在父视图中移动位置,其下所有视图也跟着移动。
- 子视图会继承父视图的透明程度(degree of transparency),这个很有意思。
- 子视图绘画区域可以超出父视图限定的矩形区域,但是!父视图有权利选择显示还是隐藏那些超出范围的内容!!即clipping,我们可以通过设置视图的
clipsToBounds
属性来决定显示还是隐藏。 - 父视图可以拥有多个子视图,从内存管理意义上来说,更像是一组数组;采用引用方式关联每一个子视图;视图数组负责一些添加或移除的工作,当有新的子视图加入进来,相应地数组新增一个元素,相反有子视图从父视图移除,数组要删除对应的元素。
- 父视图的尺寸大小改变时,在它其中的子视图将会自动调整大小。
继续说说视图(UIView),前文谈及它一个特性,只有一个父视图(superview)和多个子视图(an array of UIView,注:数组是有序的!),允许你从中追踪视图层级关系;从方法上来说,isDescendantOfView:
方法能够确定一个视图是否是另外一个视图的子视图;当然你也可以选择使用引用方式来获得某个视图,比如通过oulet
;最后,每一个视图都能够设置一个独一无二的标签——tag属性,通过数值大小来决定层级关系,这都取决你!
代码设置层级关系
使用代码手工设置视图层级关系非常简单。最为熟悉的便是addSubview:
,为一个视图添加子视图;与之对应的removeFromSuperview:
,则是从父视图中移除子视图,记住一旦移除意味着释放(released),假如你打算好之后还要重用它,那么就别移除掉,继续保持住!
iOS还提供了各种事件(Events)来通知视图动态变化。当然想要使用还是有条件限制的:一.视图必须有子视图;二.使用override
来重写方法,具体有:
didAddSubview:
,willRemoveSubview:
didMoveToSuperview
,willMoveToSuperview:
didMoveToWindow
,willMoveToWindow:
一旦addSubview:
被调用,视图(将要添加为子视图的view
)会被放置到superview的子视图中的最新一个,从层级关系上来说,自上而下,它是最下面一个!因此在绘制时,它作为压轴最后绘图,所有其他视图都是在它之下。
前面说到一个视图的子视图可以看做一个数组(array of UIView
),它的索引号自然就是从0开始。当然iOS也提供了一系列方法允许你从一个指定的索引号插入视图,至于放在前面还是后面也是可以选择的!另外还提供了方法能够交换两个同级关系的视图。
insertSubview:atIndex:
insertSubview:belowSubview:
,insterSubview:aboveSubview
exchangeSubviewAtIndex:withSubviewAtIndex:
bringSubviewToFront:
,sendSubviewToBack:
不过iOS貌似没有提供移除所有子视图的方法,这个倒是有点让人诧异的,因此我们只能自己遍历视图数组,逐个删除。
for v in myView.subviews as UIView{
v.removeFromSuperview()
}
恩…貌似这么写好low。那么换种方式:
(myView.subviews as [UIView]).map{$0.removeFromSuperview()}