iOS Tips

ViewController中的页面布局

目前的页面布局的常规方式

通常的布局的代码会写在viewDidLoad方法中, 那么在整个viewcontroller的生命周期中只会被调用一次, 那么如果页面中需要一些简单的动画, 就需要去更新页面的autoLayout代码.

页面布局代码的优化方案

viewController的生命周期有一些方法是可以被重复调用的, 比如说viewDidLayoutSubviews, 而且该方法可以通过self.view.setNeedsLayout调用到, 所以我们可以在这个方法里面去实现页面的布局代码, 以减少viewControllerviewDidLoad方法中过多的页面布局代码.

Swift的具体的实现

因为swiftprotocol可以定义需要实现的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")
            }
        }
    }
}

ControllerLayoutProtocolViewControllerviewDidLayoutSubviews中调用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
    }
}  

你可能感兴趣的:(iOS Tips)