前几天写了一篇 API 从 rx_tap
到 rx.tap
的讨论 写更优雅的 Swift 框架 — rx_tap -> rx.tap。
写完该文章的第二天上午就顺手把代码都改成了这样的形式。
当然我只是改了很多 UI 配置相关的代码。先来论述一下我改进的步骤,要补充的一点是 Enjoy 这边基本都是代码布局,换句话说视图的样式和布局都需要用代码来写。
我个人是喜欢用 lazy
的方法,所以基本上各种属性都是 lazy
。所以最开始代码大概写成这个样子。
lazy var avatarImageView: UIImageView = {
let imageView = UIImageView()
imageView.image = UIImage(named: "DianQK")
imageView.contentMode = .ScaleAspectFit
return imageView
}()
...
view.addSubview(avatarImageView)
avatarImageView.snp_makeConstraints { (make) in
make.centerX.equalTo(self.view)
make.centerY.equalTo(self.view)
}
就目前而言,有几点麻烦。
()
的场景,即使是加了,也没什么卵用。imageView
都要写 let imageView = UIImageView()
return imageView。addSubview
。当然,我们也可以先全部 add 。比如,
view.addSubviews(avatarImageView, detailButton)
。
终于有一天我忍不了,我再也不想写重复的 let imageView = UIImageView()
return imageView 。寻求新的写法。
Then (opens new window) 是一个很有意思的 Swift 框架,直接看 README 就可以知道为什么 Then 很有意思。
let titleLabel = UILabel().then {
$0.textColor = .blackColor()
$0.textAlignment = .Center
}
于是我们可以将之前的代码进行一点的改进。
private lazy var avatarImageView = UIImageView()
.then { (imageView) in
imageView.image = UIImage(named: "DianQK")
imageView.contentMode = .ScaleAspectFit
}
一定程度上可以不声明类型,以及自动补全相对完善了。
我将在下一小节解释为什么只是一定程度上可以不声明类型。
此时的代码还有几点比较麻烦,代码复用问题,每次都需要写闭包。
我试着改了一下,大概可以写成下面的样子。
lazy var avatarImageView = UIImageView()
.then(Config.scaleAspectFit)
.then(Config.image(UIImage(named: "DianQK")))
这样写就很有意思了,不需要每次都去写闭包了。同时如果这个 config
是个常用的 config
,那就可以很好的提高代码复用率。比如有一个 Config.h1
,设置为 h1
字体。
当然这样的代码我个人感觉,这很优雅了。直到 rx.tap
的出现。
这种命名空间形式的代码很有意思,这表明 tap
是 rx
的一些属性/方法。那么上面对于 UI 配置是否也能写成类似的样子?可以。
根据之前的 rx.tap
实现方式,我写了个 Config
。
lazy var avatarImageView = UIImageView()
.config
.fill
.image(UIImage(named: "DianQK"))
.view
虽然行数稍微多了一些,但是这段代码相比 then
更有意思些。then
只是表达了然后,然后,然后。
而这里的代码含义为,创建 UIImageView
,进入 Config
阶段,设置为 fill
样式,设置图片为 DianQK
,进入/回到 View
阶段。
在合理的作用域调用合理的方法/属性。
为此我们还可以写更多类似的代码, Layout
。
avatarImageView
.layout
.adhereTo(self.view)
.make { (make) in
make.centerX.equalTo(self.view)
make.centerY.equalTo(self.view)
}
当然,为了更好的约束代码(先添加视图,后设置布局),我移除了 Layout
中 make
的方法。
比较疯狂的写法可以这样。
let avatarImageView = UIImageView()
.config
.fill
.image(UIImage(named: "DianQK"))
.layout
.adhereTo(view)
.make { (make) in
make.centerX.equalTo(self.view)
make.centerY.equalTo(self.view)
}
.view
具体实现见 Demo (opens new window)。
这样写稍微错在一点小问题,不是很容易理解代码逻辑,除非很清楚 config
和 layout
表示进入了一个新的层级配置。我整理一下代码如下。
let avatarImageView = UIImageView()
.config
.fill
.image(UIImage(named: "DianQK"))
.layout
.adhereTo(view)
.make { (make) in
make.centerX.equalTo(self.view)
make.centerY.equalTo(self.view)
}
.view
这样可能看起来好一些<s>然而 Xcode 并不知道我想写成这样</s>
。具体怎么整理这个问题,看个人喜好了,也可以 .Config
.Layout
,当然如果是 .CONFIG
和 .LAYOUT
就算了。
比如项目中完整的代码。
lazy var kTableView = UITableView(frame: CGRect.zero, style: .Grouped)
.config
.register([PayProductInfoTableViewCell.self, PayPriceInfoTableViewCell.self, PayAddressTableViewCell.self])
.centerSeparatorInset
.custom { tableView in
// ...
}
.view
当然,如果你好奇
register([PayProductInfoTableViewCell.self, PayPriceInfoTableViewCell.self, PayAddressTableViewCell.self])
这段代码,我想留给你来思考会比较有意思。
前面提到一定程度上可以不声明类型,那么什么情况可以?什么情况不可以?用一句话总结,使用 self
时,需要声明类型。
写完这段代码,我的脸和这个 self
是一个颜色。这居然是一个 NSObject -> () -> NamespaceViewController
。这里就不讨论为什么是这个类型了。
解决方案也很简单,指明类型即可。
当我提出上面这种写法时,我收到了梁杰的一条回复。
可以,这很前端。
$(document).ready(function() {
$('html').find('label').html('测试你大爷').next().find('img')
.attr('src', 'http://static.zybuluo.com/numbbbbb/letp7i9nq115kbfiswhhfplg/2.png')
.width('100px').height('100px')
.next().css({color: 'red'}).html('这里是正文,点我试试看').on('click', function(){alert('别摸我!')})
.closest('div').css({border: '1px solid red'})
});
代码可以见https://jsfiddle.net/Lsg7brb9/ (opens new window)。
当然,稍微有些不同的是,本文的代码有加入 config
layout
等表明层级代码进行约束。
需要注意的是这样的写法虽然爽,但不要到处都用。毕竟这样的代码写多了容易让人把所有的代码都连起来(这是可以的),这时候 100 多个 .
连起来怕也是一种灾难。
此外,Enjoy 欢迎喜欢 Swift 的同学加入 iOS 组,我们在招人哦,如果你对尝试各种因缺斯坦的代码有兴趣,我很欢迎。差点忘了,请发简历到 songxutao@ricebook.com 。