ViewController中的页面布局
目前的页面布局的常规方式
通常的布局的代码会写在viewDidLoad
方法中, 那么在整个viewcontroller
的生命周期中只会被调用一次, 那么如果页面中需要一些简单的动画, 就需要去更新页面的autoLayout
代码.
页面布局代码的优化方案
在viewController
的生命周期有一些方法是可以被重复调用的, 比如说viewDidLayoutSubviews
, 而且该方法可以通过self.view.setNeedsLayout
调用到, 所以我们可以在这个方法里面去实现页面的布局代码, 以减少viewController
中viewDidLoad
方法中过多的页面布局代码.
Swift的具体的实现
因为swift的protocol
可以定义需要实现的var
, 可以通过将该页面需要的subviews
先在protocol
中定义好, 然后可以通过protocol
中的associatedtype
和类似codable
这种组合协议的方式将viewcontroller
和页面元素的protocol
组合, 并且实现doLayoutWithController
方法. 以下为protocol
的方法和默认实现
ControllerViewElements
基本协议, 使用时需要其他协议继承该协议
protocol ControllerViewElements where Self: UIViewController {
var isNeedLayout: Bool {get set}
func controllerViewShouldLayout() -> Bool
func controllerViewDidLayout(_ isLayout: inout Bool)
func controllerViewNeedRelayout(_ isNeed: inout Bool)
}
extension ControllerViewElements where Self: UIViewController{
func controllerViewShouldLayout() -> Bool {
if self.isNeedLayout { return false }
return true
}
func controllerViewDidLayout(_ isNeedLayout: inout Bool) {
var isNeedLayout = isNeedLayout
DispatchQueue.main.async {
if isNeedLayout {
fatalError("the controller alread layouted")
} else {
isNeedLayout = true
}
}
}
func controllerViewNeedRelayout(_ isNeedRelayout: inout Bool) {
var isNeedRelayout = isNeedRelayout
DispatchQueue.main.async {
if isNeedRelayout {
isNeedRelayout = false
} else {
fatalError("no need call this method")
}
}
}
}
ControllerLayoutProtocol
在ViewController
的viewDidLayoutSubviews
中调用doLayoutWithController
protocol ControllerLayoutProtocol {
associatedtype LayoutTarget
func doLayoutWithController(_ parent: LayoutTarget)
}
ViewControllerLayouParameter
设置该页面元素autoLayout
时的具体参数
struct ControllerLayouParameter {
let top_view_top: CGFloat = 20
let top_view_left: CGFloat = 20
let top_view_right: CGFloat = 20
let top_view_height: CGFloat = 45
let mid_view_top: CGFloat = 20
let mid_view_left: CGFloat = 20
let mid_view_right: CGFloat = 20
let mid_view_default_height: CGFloat = 45
let mid_view_changed_height: CGFloat = 0
let mid_view_animated_duartion = 0.5
}
ViewControllerLayoutElements
定义该页面需要的页面元素
protocl ViewControllerLayoutElements: ControllerViewElements {
var topView: UIView! {get set}
var midView: UIView! {get set}
}
ViewControllerLayout
执行页面元素的autoLayout
代码
struct ViewControllerLayout: ControllerLayoutProtocol {
typealias LayoutTarget = ViewControllerLayoutElements
func doLayoutWithController(_ parent: LayoutTarget) {
let params = ControllerLayouParameter()
parent.topView.snp.makeConstraints { (make) in
make.top.equalTo(params.top_view_top)
make.left.equalTo(params.top_view.left)
make.right.equalTo(params.top_view.right)
make.height.equalTo(params.top_view_height)
}
parent.midView.snp.updateConstraints { (make) in
make.height.equalTo(params.mid_view_default_height)
}
parent.midView.snp.makeConstraints { (make) in
make.top.equalTo(params.mid_view_top)
make.left.equalTo(params.mid_view.left)
make.right.equalTo(params.mid_view.right)
}
}
func updateMidViewHeight(_ parent: LayoutTarget, isUpdate: Bool) {
let params = ControllerLayoutParamater()
let changedHeight = params.mid_view_changed_height
let defaultHeight = params.mid_view_default_height
let height = isUpdate ? changedHeight : defaultHeight
parent.midView.snp.updateConstraints { (make) in
make.height.equalTo(height)
}
let duration = params.mid_view_animated_duartion
UIView.animate(withDuration: duration) {
parent.view.layoutIfNeeded()
}
}
}
ViewController
中使用该方式布局的代码
final class ViewController: UIViewController, ViewControllerLayoutElements {
var isNeedLayout: Bool = false
var isUpdateMid: Bool = false {
didSet {
if isUpdateMid != oldValue {
self.view.setNeedsLayout()
}
}
}
lazy var topView: UIView! = {
let internalTopView = UIView()
//TODO: set params for topView
return internalTopView
}()
lazy var topView: UIView! = {
let internalTopView = UIView()
//TODO: set params for midView
return internalTopView
}()
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let layout = ViewControllerLayout()
if self.controllerViewShouldLayout() {
layout.doLayoutWithController(self)
self.controllerViewDidLayout(self.isNeedLayout)
}
layout.updateMidViewHeight(self, isUpdate: self.isUpdateMid)
}
//该方法需要button中调用, 需要去实现changedButton的布局和Button的属性设置
@IBAction private func toChangedMid(_ sender: UIButton) {
self.isNeedLayout = true
}
//该方法需要button中调用, 需要去实现defaultButton的布局和Button的属性设置
@IBAction private func toDefaultMid(_ sender: UIButton) {
self.isNeedLayout = false
}
}