共计 10102 个字符,预计需要花费 26 分钟才能阅读完成。
概述
DCFrame 是一个 swift 组合界面框架,在线上迭代了 2 年,目前曾经比较稳定,应用该框架能够实现:
- 轻松组合治理简单 UI 模块;
- 零老本迁徙和重用 UI 模块;
- 模块间无耦合通信。
这篇文档会应用 3 个例子由浅如深介绍怎么应用 DCFrame 构建 iOS 界面,并且轻松实现模块间的通信。
简略列表
第一个例子咱们来学习怎么应用 DCFrame 来创立一个繁多 cell 的简略列表,如下图所示:
要创立这样一个简略列表,须要如下三步:
- 定义一个 CellModel 和 Cell 类型;
- 创立一个 ContainerModel 来包装 CellModel;
- 应用 ContainerTableView 去加载 ContainerModel。
创立 CellModel 和 Cell
定义一个 CellModel 类型须要满足如下几个条件:
- 须要继承自
DCCellModel
; - 定义 Cell 所须要的数据类型;
- 设置对应 Cell 的类型和高度。
在这个例子外面,Cell 只须要一个字符串数据,所以我能够用如下形式定义 CellModel:
class SimpleLabelModel: DCCellModel {
var text: String = ""
required init() {super.init()
cellClass = SimpleLabelCell.self
cellHeight = 50
}
}
定义一个 Cell 类型也须要 3 步:
- 须要继承自
DCCell
; - 定义 UI 元素,在
setupUI()
办法中进行布局; - 在
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 须要满足如下条件:
- 须要继承自
DCContainerModel
; - 一些初始化逻辑能够放在
cmDidLoad()
中,比方:这个例子中创立列表操作; - 应用
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 也能够很容易的创立一个简单列表,依然须要三个步骤:
- 定义界面列表呈现的所有 CellModel 和 Cell 类型;
- 创立 ContainerModel 去组合这些 CellModel;
- 应用 DCContainerTableView 去加载一个根的 ContainerModel。
创立界面中的 Cell 和 CellModel
这个列表中咱们有 4 个不同的 Cell 类型,如下图所示:
UserCell and UserModel:
在这个例子中,UserCell 蕴含一个 UILabel
来显示名字,对应的 UserModel 也只须要蕴含一个 name 字符串类型数据。所以咱们能够定义 UserInfoCell
和 UserInfoCellModel
类来示意:
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 有两个小技巧:
- 找到界面中间断反复呈现的 Cell,应用 ContainerModel 来组装,比方下面例子中的 CommentCell;
- 如果有多个 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 example
let postListContainerModel = PostListContainerModel()
dcContainerTableView.loadCM(postListContainerModel)
简单列表的构造如下图所示:
当初咱们学习了怎么应用 DCFrame 创立一个简单列表,能够发现和创立一个简略列表一样简略,都须要如下三步:
- 首先,创立 UI 中的根底元素 Cell 和 CellModel;
- 其次,应用 ContainerModel 来组装 CellModel;
- 最初,应用 ContainerTableView 去加载一个 ContainerModel。
模块间通信
当初咱们来扩大一些下面的简单列表例子,增加两个模块间通信的性能,点击 InteractiveCell,扭转 PhotoCell 的背景色如下所示:
DCFrame 提供了弱小的事件传递和数据共享能力,能够很容易实现页面中两个不同模块间的通信问题。要实现下面性能,咱们须要如下三步:
- 扩大简单列表中的
InteractiveCell
和PhotoCell
; - 定义事件和共享数据;
- 在 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。而在这个例子中,InteractiveCell
和 PhotoCell
间的通信能够放在 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 例子。