exchangeSubview 踩坑记

如何正确使用 exchangeSubview

2020-08-06

TOC

前言

今天要讨论的是一个平常不那么常用的方法 func exchangeSubview(at index1: Int, withSubviewAt index2: Int) 的踩坑记。

我们先看一下官方文档中的描述:

Exchanges the subviews at the specified indices.

Each index represents the position of the corresponding view in the array in the subviews property. Subview indices start at 0 and cannot be greater than the number of subviews. This method does not change the superview of either view but simply swaps their positions in the subviews array.

简单来讲就是从一个 view 中交换两个 subview 的位置,先从 subviews 数组里获取两个 subviewindex ,然后从 subviews 数组对象里面交换各自的位置重新渲染。从描述里我们能大概猜想到如何使用此方法,上代码:

let view1 = UIView(frame: CGRect(x: 50, y: 100, width: 200, height: 200))
view.addSubview(view1)

let view2 = UIView(frame: CGRect(x: 70, y: 120, width: 200, height: 200))
view.addSubview(view2)

/// 请忽略这里的强转,真实场景里要避免强转
let view1Index = view.subviews.firstIndex(of: view1)!   // index为0
let view2Index = view.subviews.firstIndex(of: view2)!   // index为1
view.exchangeSubview(at: view1Index, withSubviewAt: view2Index)

在大部分场景下如上代码会按照文档里的描述一样正常工作。

踩坑

但是今天我在实际使用过程当中发现并非如此,继续上代码:

let view1 = UIView(frame: CGRect(x: 50, y: 100, width: 200, height: 200))
view.addSubview(view1)

let view2 = UIView(frame: CGRect(x: 70, y: 120, width: 200, height: 200))
view.addSubview(view2)

/// 这里是新增的代码部分
let layer1 = CALayer()
view.layer.insertSublayer(layer1, at: 0)

let view1Index = view.subviews.firstIndex(of: view1)!   // index还是为0
let view2Index = view.subviews.firstIndex(of: view2)!   // index还是为1
view.exchangeSubview(at: view1Index, withSubviewAt: view2Index)

运行代码就会发现交换的并不是预期的两个 view ,而是交换成一个莫名其妙不知哪里来的 view ,如果打印 subviews 发现 index 也是对的。那么 index 也是系统给的,方法也是调用的正确的,为何结果就不对了呢?

如果重新看一下上述代码就会发现跟之前唯一的差别就是新插入了一个纯layer,问题也是出现在这里。当给一个 viewlayer 插入一个 layer 的时候它会加到 sublayers 里面,而非 subviews 。也就是说如果你的 view 里即使用了 addSubview 又使用了 insertSublayer 的话你从 subviews 里面拿到的 index 完全是错误的。

最关键的一点是 exchangeSubview 方法中交换所使用的 index 并非是 view 层级的,而是 layer 层级的。这一点在官方文档里面压根没提,网上资料也搜不到关于这个问题的相关解答,让我着实踩了几个小时的坑。

正解

综上,exchangeSubview 方法正确的打开方式应该是这样子的:

let view1 = UIView(frame: CGRect(x: 50, y: 100, width: 200, height: 200))
view.addSubview(view1)

let view2 = UIView(frame: CGRect(x: 70, y: 120, width: 200, height: 200))
view.addSubview(view2)

let layer1 = CALayer()
view.layer.insertSublayer(layer1, at: 0)

/// 请忽略这里的强转,真实场景里要避免强转
let view1Index = view.layer.sublayers!.firstIndex(of: view1.layer)!   // index为1
let view2Index = view.layer.sublayers!.firstIndex(of: view2.layer)!   // index为2
view.exchangeSubview(at: view1Index, withSubviewAt: view2Index)

DONE!