本文为 WWDC 2016 Session 419 的部分内容笔记。强烈推荐观看。




1、设计师来需求了

在我们的 App 中,通常需要自定义一些视图。例如下图:

2016年WWDC-UIKit 中协议与值类型编程实战 1

2016年WWDC-UIKit 中协议与值类型编程实战,Swift定义新类型 - 敏捷大拇指 - 2016年WWDC-UIKit 中协议与值类型编程实战 1


我们可能会在很多地方用到右边为内容,左边有个装饰视图的样式,为了代码的通用性,我们在 UITableViewCell 的基础上,封装了一层 DecoratingLayout,然后再让子类继承它,从而实现这一类视图。

[Swift] 纯文本查看 复制代码
class DecoratingLayout : UITableViewCell {
    var content: UIView
    var decoration: UIView

    // Perform layout...
}





2、重构

但是代码这样组织的话,因为继承自 UITableViewCell,所以对于其他类型的 view 就不能使用了。我们开始重构。

2016年WWDC-UIKit 中协议与值类型编程实战 2

2016年WWDC-UIKit 中协议与值类型编程实战,Swift定义新类型 - 敏捷大拇指 - 2016年WWDC-UIKit 中协议与值类型编程实战 2


我们需要让视图布局的功能独立与具体的 view 类型,无论是 UITableViewCell、UIView、还是 SKNode(Sprite Kit 中的类型)

[Swift] 纯文本查看 复制代码
struct DecoratingLayout {
    var content: UIView
    var decoration: UIView

    mutating func layout(in rect: CGRect) {
        // Perform layout...
    }
}


这里,我们使用结构体 DecoratingLayout 来表示这种 layout。相比于之前的方式,现在只要在具体的实现中,创建一个 DecoratingLayout 就可以实现布局的功能。代码如下:

[Swift] 纯文本查看 复制代码
class DreamCell : UITableViewCell {
   ...

    override func layoutSubviews() {
        var decoratingLayout = DecoratingLayout(content: content, decoration: decoration)
        decoratingLayout.layout(in: bounds)
    } 
}

class DreamDetailView : UIView {
   ...

    override func layoutSubviews() {
        var decoratingLayout = DecoratingLayout(content: content, decoration: decoration)
        decoratingLayout.layout(in: bounds)
    } 
}


注意观察上面的代码,在 UITableViewCell 和 UIView 类型的 view 中,布局功能和具体的视图已经解耦,我们都可以使用 struct 的代码来完成布局功能。

通过这种方式实现的布局,对于测试来说也更加的方便:

[Swift] 纯文本查看 复制代码
func testLayout() {
    let child1 = UIView()
    let child2 = UIView()
    var layout = DecoratingLayout(content: child1, decoration: child2)
    layout.layout(in: CGRect(x: 0, y: 0, width: 120, height: 40))

    XCTAssertEqual(child1.frame, CGRect(x: 0, y: 5, width: 35, height: 30))
    XCTAssertEqual(child2.frame, CGRect(x: 35, y: 5, width: 70, height: 30))
}


我们的野心远不止于此。这里我们也想要在 SKNode 上使用上面的布局方式。看如下的代码:

[Swift] 纯文本查看 复制代码
struct ViewDecoratingLayout {
    var content: UIView
    var decoration: UIView

    mutating func layout(in rect: CGRect) {
        content.frame = ...
        decoration.frame = ...
    }
}

struct NodeDecoratingLayout {
    var content: SKNode
    var decoration: SKNode

    mutating func layout(in rect: CGRect) {
        content.frame = ...
        decoration.frame = ...
    }
}


注意观察上面的代码,除了 content 和 decoration 的类型不一样之外,其他的都是重复的代码,重复就是罪恶!

那么我们如何才能消除这些重复代码呢?在 DecoratingLayout 中,唯一用到 content 和 decoration 的地方,是获取它的 frame 属性,所以,如果这两个 property 的类型信息中,能够提供 frame 就可以了,于是我们想到了使用 protocol 作为类型(type)来使用。

[Swift] 纯文本查看