概述

DCFrame是一个swift组合界面框架,在线上迭代了2年,目前曾经比较稳定,应用该框架能够实现:

  1. 轻松组合治理简单UI模块;
  2. 零老本迁徙和重用UI模块;
  3. 模块间无耦合通信。

这篇文档会应用3个例子由浅如深介绍怎么应用DCFrame构建iOS界面,并且轻松实现模块间的通信。

简略列表

第一个例子咱们来学习怎么应用DCFrame来创立一个繁多cell的简略列表,如下图所示:

要创立这样一个简略列表,须要如下三步:

  1. 定义一个 CellModel 和 Cell 类型;
  2. 创立一个 ContainerModel 来包装 CellModel;
  3. 应用 ContainerTableView 去加载 ContainerModel。

创立 CellModel 和 Cell

定义一个 CellModel 类型须要满足如下几个条件:

  1. 须要继承自 DCCellModel
  2. 定义Cell所须要的数据类型;
  3. 设置对应Cell的类型和高度。

在这个例子外面,Cell只须要一个字符串数据,所以我能够用如下形式定义 CellModel:

class SimpleLabelModel: DCCellModel {    var text: String = ""        required init() {        super.init()        cellClass = SimpleLabelCell.self        cellHeight = 50    }}

定义一个 Cell 类型也须要3步:

  1. 须要继承自 DCCell;
  2. 定义UI元素,在setupUI()办法中进行布局;
  3. cellModelDidUpdate() 办法中更新界面数据。

在这个简略列表中,只须要一个 Label 界面元素和一个分割线,所以咱们能够这样来定义 Cell:

class SimpleLabelCell: DCCell<SimpleLabelModel> {    let label: UILabel = {        let label = UILabel()        label.font = UIFont.systemFont(ofSize: 17)        return label    }()    let separateLine: CALayer = {        let layer = CALayer()        layer.backgroundColor = UIColor.lightGray.cgColor        return layer    }()        override func setupUI() {        super.setupUI()                contentView.addSubview(label)        contentView.layer.addSublayer(separateLine)    }        override func layoutSubviews() {        super.layoutSubviews()        let bounds = contentView.bounds        let left: CGFloat = 15        let height: CGFloat = 1.0 / UIScreen.main.scale                label.frame = bounds.inset(by: UIEdgeInsets(top: 8, left: left, bottom: 8, right: 15))        separateLine.frame = CGRect(x: left, y: bounds.height - height, width: bounds.width - left, height: height)    }    override func cellModelDidUpdate() {        super.cellModelDidUpdate()                label.text = cellModel.text    }}

留神: Cell UI 界面的赋值操作倡议放在 cellModelDidUpdate() 办法中, 因为在列表中 Cell 通常会被重用,cellModelDidUpdate() 办法会在 Cell 重用前被回调。

创立一个 ContainerModel

对于列表中繁多的元素须要定义 Cell 和对应的 CellModel,如果要将这些繁多元素组装成列表就须要另外一个角色:ContainerModel。定义一个ContainerModel须要满足如下条件:

  1. 须要继承自 DCContainerModel
  2. 一些初始化逻辑能够放在 cmDidLoad() 中,比方:这个例子中创立列表操作;
  3. 应用 addSubmodel() 办法,来组合 CellModel。

在下面例子中,初始化逻辑是组装一个列表,所以咱们能够这样来定义 ContainerModel:

class SimpleListContainerModel: DCContainerModel {    override func cmDidLoad() {        super.cmDidLoad()        for num in 0...100 {            let model = SimpleLabelModel()            model.text = "\(num)"            addSubmodel(model)        }    }}

提醒:对于一个简略列表,能够间接创立一个 DCContainerModel 去组装列表,不须要再额定定义。

加载 ContainerModel

实现 ContainerModel 的定义后,对于列表 UI 数据和逻辑局部就实现了。当初能够应用 DCFrame 提供的 DCContainerTableView 来加载 ContainerModel,如下所示:

class SimpleListViewController: UIViewController {    let dcTableView = DCContainerTableView()        override func viewDidLoad() {        super.viewDidLoad()        view.addSubview(dcTableView)                let simpleListCM = SimpleListContainerModel()        dcTableView.loadCM(simpleListCM)    }        override func viewDidLayoutSubviews() {        super.viewDidLayoutSubviews()        dcTableView.frame = view.bounds    }}

简单列表

下面学习了怎么应用 DCFrame 创立一个简略列表,当初咱们来创立一个帖子的列表,如下所示:

应用 DCFrame 也能够很容易的创立一个简单列表,依然须要三个步骤:

  1. 定义界面列表呈现的所有 CellModel 和 Cell 类型;
  2. 创立 ContainerModel 去组合这些 CellModel;
  3. 应用 DCContainerTableView 去加载一个根的 ContainerModel。

创立界面中的 Cell 和 CellModel

这个列表中咱们有 4 个不同的 Cell 类型,如下图所示:

UserCell and UserModel:

在这个例子中,UserCell 蕴含一个 UILabel 来显示名字,对应的 UserModel 也只须要蕴含一个 name 字符串类型数据。所以咱们能够定义 UserInfoCellUserInfoCellModel 类来示意:

class UserInfoCellModel: DCCellModel {    var name: String!        required init() {        super.init()        cellHeight = 41        cellClass = UserInfoCell.self    }}class UserInfoCell: DCCell<UserInfoCellModel> {    // The detailed code can be found in the project example      override func cellModelDidUpdate() {        super.cellModelDidUpdate()              nameLabel.text = cellModel.name    }}

PhotCell and PhotoModel:

为了能简略展现例子,这里咱们定义 PhotoCell 只波及背景色彩的变更,所以在 PhotoModel 中咱们只须要定义一个色彩数据属性,如下所示:

class PhotoCellModel: DCCellModel {    var color: UIColor = UIColor(red: 4/255.0, green: 170/255.0, blue: 166/255.0, alpha: 1.0)    required init() {        super.init()                cellClass = PhotoCell.self        cellHeight = 375    }}class PhotoCell: DCCell<PhotoCellModel> {      // The detailed code can be found in the project example      override func cellModelDidUpdate() {        super.cellModelDidUpdate()              contentView.backgroundColor = cellModel.color    }}

InteractiveCell and InteractiveModel:

在这个例子中,InteractiveCell 会固定显示三个按钮(为简略起见代码中定义按钮和布局代码省略,能够在DCFrame例子中查看),所以无需定义界面数据,如下代码所示:

class InteractiveCellModel: DCCellModel {    required init() {        super.init()        cellClass = InteractiveCell.self        cellHeight = 41    }}class InteractiveCell: DCCell<InteractiveCellModel> {    // The detailed code can be found in the project example}

CommentCell and CommentModel:

对于 CommentCell 来说只有一个显示评论的 Label,所以 CommentModel 中须要定一个评论数据的字符串,如下代码所示:

class CommentCellModel: DCCellModel {    var comment: String!        required init() {        super.init()        cellClass = CommentCell.self        cellHeight = 25    }}class CommentCell: DCCell<CommentCellModel> {      // The detailed code can be found in the project example      override func cellModelDidUpdate() {        super.cellModelDidUpdate()                commentLabel.text = cellModel.comment    }}

组合 CellModel

实现界面中根本元素 CellModel 和 Cell 的定义后,下一步就是通过 ContainerModel 去组装界面,组装 CellModel 有两个小技巧:

  1. 找到界面中间断反复呈现的 Cell,应用 ContainerModel 来组装,比方下面例子中的 CommentCell;
  2. 如果有多个 Cell 循环呈现,就能够用一个 ContainerModel 来组装,比方下面例子中4个不同的Cell组成了每个帖子。

组装 CommentModel

这里定义 PostCommentsContainerModel 类来组装每个帖子中的 CommentModel。

class PostCommentsContainerModel: DCContainerModel {    init(with comments: [String]) {        super.init()        for comment in comments {            let model = CommentCellModel()            model.comment = comment            addSubmodel(model)        }    }}

组装一个帖子的 ContainerModel

另外一个循环呈现的 UI 元素就是每个帖子,每个帖子中蕴含4种类型的 Cell,咱们通过定义一个 PostItemContainerModel 类来组装这些 Cell。

class PostItemContainerModel: DCContainerModel {      // The detailed code can be found in the project example          init(with post: PostData) {        super.init()                let userModel = UserInfoCellModel()        userModel.name = post.username        userModel.isHoverTop = true                let photoModel = PhotoCellModel()        let interactiveModel = InteractiveCellModel()                let commentsCM = PostCommentsContainerModel(with: post.comments)        addSubmodels([userModel, photoModel, interactiveModel, commentsCM])    }}

提醒:ContainerModel 有个十分弱小的性能,它不仅能够组装 CellModel,也能够组装 ContainerModel,比方下面的 commentsCM。

组装根的 ContainerModel

当初每个帖子能够用 PostItemContainerModel 来示意,最初咱们定一个 PostListContainerModel 类型来组装所有的帖子,这个 ContainerModel 被称为根 ContainerModel。

class PostListContainerModel: DCContainerModel {        // The detailed code can be found in the project example          override func cmDidLoad() {        super.cmDidLoad()                for data in mockData {            let infoCM = PostInfoContainerModel(with: data)            infoCM.bottomSeparator = DCSeparatorModel(color: .clear, height: 10)            addSubmodel(infoCM)        }    }}

加载 ContainerModel

加载 containerModel 和下面的简略列表一样,只须要创立一个 DCContainerTableView 而后调用 loadCM() 办法就能够将 UI 列表显示进去。

let dcContainerTableView = DCContainerTableView()// Omit layout code, the detailed code can be found in the project examplelet postListContainerModel = PostListContainerModel()dcContainerTableView.loadCM(postListContainerModel)

简单列表的构造如下图所示:

当初咱们学习了怎么应用 DCFrame 创立一个简单列表,能够发现和创立一个简略列表一样简略,都须要如下三步:

  1. 首先,创立 UI 中的根底元素 Cell 和 CellModel;
  2. 其次,应用 ContainerModel 来组装 CellModel;
  3. 最初,应用 ContainerTableView 去加载一个 ContainerModel。

模块间通信

当初咱们来扩大一些下面的简单列表例子,增加两个模块间通信的性能,点击 InteractiveCell,扭转 PhotoCell 的背景色如下所示:

DCFrame 提供了弱小的事件传递和数据共享能力,能够很容易实现页面中两个不同模块间的通信问题。要实现下面性能,咱们须要如下三步:

  1. 扩大简单列表中的 InteractiveCellPhotoCell
  2. 定义事件和共享数据;
  3. 在 ContainerModel 中响应事件和进行数据共享。

扩大 Cell 性能

为了反对下面的点击事件,咱们须要为 InteractiveCell 中的 Button 增加点击事件,如下所示:

class InteractiveCell: DCCell<InteractiveCellModel> {      // The detailed code can be found in the project example    private lazy var likeButton: UIButton = {        return createButton(with: "Like")    }()    private lazy var commentButton: UIButton = {        return createButton(with: "Comment")    }()    private lazy var shareButton: UIButton = {        return createButton(with: "Share")    }()    private func createButton(with title: String) -> UIButton {        let button = UIButton()             // The detailed code can be found in the project example        button.addTarget(self, action: #selector(touch(sender:)), for: .touchUpInside)        return button    }        @objc func touch(sender: UIButton) {        switch sender {        case likeButton:            // do something        case commentButton:            // do something        case shareButton:            // do something        default: break        }}

PhotoCell 须要增加一个新的 UILabel,去显示按钮点击后的文案,也须要为 PhotoCellModel 增加一个 text 字符串属性,如下所示:

class PhotoCellModel: DCCellModel {    var text = ""    // The detailed code can be found in the project example}class PhotoCell: DCCell<PhotoCellModel> {        // The detailed code can be found in the project example    lazy var infoLabel: UILabel = {        let label = UILabel()        label.font = UIFont.boldSystemFont(ofSize: 15)        label.textColor = UIColor.darkText        label.textAlignment = .center        contentView.addSubview(label)        return label    }()    override func cellModelDidUpdate() {        super.cellModelDidUpdate()                infoLabel.text = cellModel.text        contentView.backgroundColor = cellModel.color    }        override func layoutSubviews() {        super.layoutSubviews()                infoLabel.frame = CGRect(x: 0, y: 0, width: bounds.width, height: infoLabel.font.lineHeight)        infoLabel.center = CGPoint(x: contentView.bounds.width / 2, y: contentView.bounds.height / 2)    }}

定义事件和共享数据

当初 InteractiveCell 具备了响应点击事件的能力,咱们能够通过定义一个 DCEventID 来进行事件的传递,如下所示:

class InteractiveCell: DCCell<InteractiveCellModel> {    static let likeTouch = DCEventID()    static let commentTouch = DCEventID()    static let shareTouch = DCEventID()          // The detailed code can be found in the project example        @objc func touch(sender: UIButton) {        switch sender {        case likeButton:            sendEvent(Self.likeTouch, data: sender.titleLabel?.text)        case commentButton:            sendEvent(Self.commentTouch, data: sender.titleLabel?.text)        case shareButton:            sendEvent(Self.shareTouch, data: sender.titleLabel?.text)        default: break        }    }}

对于 PhotoCell 来说,只须要扭转背景色和文案,所以它能够定义一个 DCSharedDataID 来订阅数据的变动,如下所示:

class PhotoCell: DCCell<PhotoCellModel> {    static let data = DCSharedDataID()    // The detailed code can be found in the project example        override func cellModelDidLoad() {        super.cellModelDidLoad()                subscribeData(Self.data) { [weak self] (text: String, color: UIColor) in            guard let `self` = self else { return }                        self.cellModel.text = text            self.cellModel.color = color                        self.infoLabel.text = text            self.contentView.backgroundColor = color        }    }}

留神:在Cell中订阅数据变动,必须放在 cellModelDidLoad() 办法中。

解决模块间的通信

在 DCFrame 中 sendEvent() 办法能够将事件传递到 ContainerModel 中,而后事件会持续延着 ContainerModel 向根的 ContainerModel 传递,在传递链上的每个 ContainerModel 都能够响应这个事件。

对于共享数据,个别是在 ContainerModel 中进行共享数据,在 ContainerModel 所组装的 CellModel 和 Cell 中订阅数据的变动。所以两个不同模块间的通信,须要依赖 ContainerModel 去进行解决,如下图所示:

上图中 Model_A 和 Model_B 进行通信,须要在根的 CM(ContainerModel) 中进行解决,因为他们最近的公共 CM 就是根CM。而在这个例子中, InteractiveCellPhotoCell 间的通信能够放在 PostItemContainerModel 中进行解决,因为 PostItemContainerModel 是这两个 Cell 最近的公共 CM,如下所示:

class PostItemContainerModel: DCContainerModel {    // The detailed code can be found in the project example        override func cmDidLoad() {        super.cmDidLoad()                subscribeEvent(InteractiveCell.likeTouch) { [weak self] (text: String) in            self?.shareData((text, UIColor.red), to: PhotoCell.data)        }.and(InteractiveCell.commentTouch) { [weak self] (text: String) in            self?.shareData((text, UIColor.yellow), to: PhotoCell.data)        }.and(InteractiveCell.shareTouch) { [weak self] (text: String) in            self?.shareData((text, UIColor.blue), to: PhotoCell.data)        }    }}

留神:在 ContainerModel 中订阅事件和共享数据,必须放在 cmDidLoad() 中进行解决。

总结

下面咱们首先通过一个简略例子解说了怎么应用DCFrame来组装一个列表;而后通过一个简单列表理解到 ContaienrModel 有着弱小的组合性能,正因为这个性能使得治理简单界面变得非常容易;最初介绍了怎么轻松实现无耦合的模块间通信。界面组合和模块间通信是DCFrame的外围性能,当然还有很多乏味的例子(参考了IGListKit的例子)和性能能够在这里找到:DCFrame例子。