MLinView github下载地址 https://github.com/chen397254698/MLinView
相对于Android布局来说个人觉得IOS布局方式不够友好。在复杂的布局方面两者都可以用非常灵活的方式实现,但是简单布局再IOS上的实现就要复杂的多。
就拿一个最常见的简单布局举例
布局包含四个元素,图标,标题,简介,时间。当简介为空的时候标题居中,标题的长度不超过图标到时间的距离。
传统约束布局
这个布局如果基于SnapKit来编写
// 创建四个基础元素
//图标
lazy var _icon = UIImageView(image: UIImage(named: "done_icon"))
//时间
lazy var _time = UILabel() => { it in
it.font = UIFont.systemFont(ofSize: 12)
it.textColor = color_gray_99
it.text = "2020-01-14"
}
//辅助布局定位
lazy var _content = UIView()
//标题
lazy var _title = UILabel() => { it in
it.font = UIFont.systemFont(ofSize: 17)
it.textColor = color_gray_22
it.text = "传统约束布局"
it.mWidth = .wrap
it.mHeight = .match
}
//简介
lazy var _brief = UILabel() => { it in
it.font = UIFont.systemFont(ofSize: 14)
it.textColor = color_gray_99
it.text = "简介"
}
//添加约束,将布局封装成一个UIView
init() {
super.init(frame: .zero)
//添加子UIView
addSubview(_icon)
addSubview(_content)
addSubview(_time)
//防止icon拉伸
_icon.setContentHuggingPriority(.defaultHigh, for: .horizontal)
_icon.snp.makeConstraints{
$0.centerY.equalToSuperview()
$0.left.equalToSuperview().offset(15)
$0.right.greaterThanOrEqualTo(_content.snp.left).offset(-15)
}
//允许标题拉伸
_content.setContentHuggingPriority(.defaultLow, for: .horizontal)
_content.snp.makeConstraints{
$0.centerY.equalToSuperview()
$0.left.greaterThanOrEqualTo(_icon.snp.right).offset(15)
$0.right.greaterThanOrEqualTo(_time.snp.left)
}
_time.snp.makeConstraints{
$0.right.equalToSuperview().offset(-15)
$0.top.equalToSuperview().offset(20)
$0.left.greaterThanOrEqualTo(_content.snp.right)
}
_content.addSubview(_title)
_content.addSubview(_brief)
_title.snp.makeConstraints{
$0.top.left.equalToSuperview()
$0.bottom.greaterThanOrEqualTo(_brief.snp.top).offset(-8)
$0.width.lessThanOrEqualToSuperview()
}
_brief.snp.makeConstraints{
$0.top.greaterThanOrEqualTo(_title.snp.bottom).offset(8)
$0.bottom.equalToSuperview()
$0.width.lessThanOrEqualToSuperview()
}
}
完成布局基本上就是这些步骤,由于UIView存在拉伸抗压的问题,布局比复杂度比想象的要高,这还是在没有考虑View显示隐藏的情况,如果左侧Icon可以动态隐藏,简介可以动态隐藏同时标题需要居中
这样布局将会更加复杂。你需要去更新对应的约束,如果还有UIView的添加删除,那将会无比头疼。那有没有什么简单的实现呢?
MLinView 主干布局
MLinView主干布局就是希望可以简化这种常见的布局的实现:
//同样是初始化四个基础控件,不同的是需要添加控件的描述。
lazy var _icon = UIImageView(image: UIImage(named: "done_icon")) => { it in
it.mWidth = .wrap
it.mHeight = .wrap
it.mGravity = .center
it.mLeft = 15
}
//嵌套一层垂直的主干布局,方便_brief 隐藏的时候 _title 可以居中
lazy var _content = MLinView(orientation: .vertical) => { it in
it.mLeft = 15
it.mRight = 15
it.mWidth = .match
it.mHeight = .wrap
it.mGravity = .center
}
lazy var _time = UILabel() => { it in
it.font = UIFont.systemFont(ofSize: 12)
it.textColor = color_gray_99
it.text = "2020-01-14"
it.mRight = 15
it.mHeight = .wrap
it.mTop = 20
}
lazy var _title = UILabel() => { it in
it.font = UIFont.systemFont(ofSize: 17)
it.textColor = color_gray_22
it.text = "MLinView主干布局"
it.mWidth = .wrap
it.mHeight = .match
}
lazy var _brief = UILabel() => { it in
it.font = UIFont.systemFont(ofSize: 14)
it.textColor = color_gray_99
it.text = "简介"
it.textAlignment = .center
it.mTop = 8
it.mHiddenTop = 0
it.mWidth = .wrap
it.mHeight = .wrap
}
每个UIView扩展了几个基础属性。
- mWidth UIVew宽度可以试具体数值入 50、100 也可以是.match撑满父布局(可拉伸)、.wrap包裹内容(防拉伸)
- mHeight UIVew高度可以试具体数值入 50、100 也可以是.match撑满父布局(可拉伸)、.wrap包裹内容(防拉伸)
- mLeft 左边距
- mRight 右边距(正值表示正边距)
- mTop上边距
- mBottom 下边距(正值表示正边距)
- mHiddenLeft UIView.isHidden = true时生效的左边距
- mHiddenRight UIView.isHidden = true时生效的右边距(正值表示正边距)
- mHiddenTop UIView.isHidden = true时生效的上边距
- mHiddenBottom UIView.isHidden = true时生效的下边距(正值表示正边距)
- mGravity 布局的位置关系
以布局中的图标_icon为例。
it.mWidth = .wrap //_icon 的宽度为图片宽度,即包裹内容
it.mHeight = .wrap //_icon 的宽度为图片高度度,即包裹内容
it.mGravity = .center // 容器布局为水平主干布局,.center 代表垂直居中
it.mLeft = 15 // 左边距为15,即和父容器间距为15
添加了这些条件之后,剩下的就很简单了
init() {
//继承MLinView,定义一个水平主干布局,宽度铺满父布局,高度为100
super.init(orientation: .horizontal, mWidth: .match, mHeight: 100)
//标题和简介加入到垂直主干布局 _content
_content.addBatch(_title, _brief)
//把所有View加入到主干布局中
addBatch(_icon, _content, _time)
}
没有约束,没有约束,没有约束。简单的把UIView添加到主干布局就大功告成。这样的好处显而易见,如果布局中需要添加,删除,显示,隐藏某些元素。我们也不需要修改约束。主干布局将自动重新排布。
主干布局添加UIView有几个简单的方法
//往最后添加
func addSubview(_ view: UIView)
//指定index位置插入
func insertSubview(_ view: UIView, _ index: Int)
//往最后批量添加
func addBatch(_ views: UIView...)
布局中需要添加,删除,显示,隐藏某些元素,以_icon为例
//移除_icon
_icon.removeFromSuperview()
//插入_icon
insertSubview(self._icon, 0)
//隐藏_icon
_icon.isHidden = true
//显示_icon
_icon.isHidden = false
还可以通过设置
view.setContentHuggingPriority()
view.setContentCompressionResistancePriority()
来控制UIView在 .match铺满状态下的拉伸和压缩拉大到动态控制布局的目的
以上的MLinView的基础用法。其实是基于它线性布局的能力,在此基础上还可以扩展兼容更多布局。
高级扩展 依附布局
UIView扩展了一个高级属性 mConstraints 依附约束。依附约束实在主干布局上依附的UIView的约束。
举个常见的例子
在一个简单的垂直主干布局的按钮上加一个小红点。
//_showHideBtn 为对应的按钮 "icon显示隐藏"
lazy var _redPoint = UIView() => { it in
it.backgroundColor = .red
it.mWidth = 10
it.mHeight = 10
it.layer.cornerRadius = 5
it.layer.masksToBounds = true
it.mTop = -5
it.mRight = -5
//依附约束的锚点View为_showHideBtn按钮,约束条件为 .rightToRight, .topToTop。一旦设置了mConstraints,View将变成依附View。不再参加主干布局。
it.mConstraints = [MCons(_showHideBtn, .rightToRight, .topToTop)]
}
//把 _redPoint 添加到主干布局,依附View的添加顺序不影响布局
addBatch(_redPoint)
有了依附约束,很多看似有点复杂的布局也可以在主干布局基础上简单扩展来实现。
高级扩展 页面滚动
当布局超出一页的时候就需要页面支持滚动,可以一行代码搞定
//通过_scroller静态方法扩展出滚动,出入父布局view。是否空出安全边距。通过这种方式初始化的主干布局就支持滚动了。
lazy var _linear = MLinView._scroller(view, safeEdge: true)
高级扩展 支持复杂布局。
如果以上的方法都不能满足你。那么MLinView也支持SnapKit添加约束的方式。MLinView也支持用复杂的方式实现复杂的布局(尽量不用)。( Ĭ ^ Ĭ )
//通过attach 添加VIew。将不参数主干布局和依附布局
func attach(_ views: UIView...)
//编写相应的snapkit约束
view.snp.makeConstraints {
$0.left.right.equalToSuperview()
$0.height.equalTo(45)
$0.bottom.equalTo(view.safeAreaLayoutGuide.snp.bottom)
}