preface

One of the most talked about viewcontrollers is that they are fat and bloated, and viewcontrollers can be written elegantly even using traditional MVC patterns. It’s not about design patterns, it’s more about how well you understand the patterns, and how clear you are about the division of responsibilities. ViewController also reflects the true level of a programmer to a large extent. The ViewController of a junior programmer is always bloated and fat, and any function can be crammed into it. There is no clear boundary between different functions. A good programmer’s ViewController is so elegant that it makes you feel like you can’t change a stroke.

ViewController duties

  • UI property configuration and layout
  • User interaction events
  • User interaction event handling and callbacks

User interaction event handling: Callbacks are usually handled by other objects: ViewController or other objects, depending on the specific design pattern and application scenario

And usually when we read someone else’s ViewController code, what do we look at?

  1. Where is the control property configuration?
  2. Where is the entry point for user interaction?
  3. What are the consequences of user interaction? (Where is the callback?)

So from this point of view, the three functions should be separated from each other from the very beginning, with clear boundaries. Because who do not want to find their own interactive entry, to read a pile of control lengthy control configuration code, but do not want to slowly clarify the whole user interaction process in a pile of code. We usually focus on what I’m most concerned about right now, and when I see a bunch of irrelevant code, my first reaction is that I want to comment it out.

Protocol based configuration for separating UI properties


protocol MFViewConfigurer {
    var rootView: UIView { get }
    var contentViews: [UIView] { get }
    var contentViewsSettings: [() -> Void] { get }

    func addSubViews(a)
    func configureSubViewsProperty(a)
    func configureSubViewsLayouts(a)

    func initUI(a)
}


Copy the code

You can rely on this protocol to configure all control properties, and then use the Extension Protocol to greatly reduce repetitive code while improving readability


extension MFViewConfigurer {
    func addSubViews(a) {
        for element in contentViews {
            if let rootView = rootView as? UIStackView {
                rootView.addArrangedSubview(element)
            } else {
                rootView.addSubview(element)
            }
        }
    }

    func configureSubViewsProperty(a) {
        for element in contentViewsSettings {
            element()
        }
    }

    func configureSubViewsLayouts(a){}func initUI(a) {
        addSubViews()
        configureSubViewsProperty()
        configureSubViewsLayouts()
    }
}


Copy the code

Here I will control the adding and control configuration is divided into two function addSubViews and configureSubViewsProperty, because in my eyes function should follow the single responsibility this concept: addSubViews: Tell the reader clearly, I this controller which controls configureSubViewsProperty contains: tell the reader clearly, all attributes of the control configuration are all here, want to modify attributes, please read this function

Take a look at an example:


    override func viewDidLoad(a) {
        super.viewDidLoad()
        // Do any additional setup after loading the view.

		 // Initialize the UI
        initUI()
	
		 // Bind user interaction events
        bindEvent()

		 // Bind viewModel.value to the control
        bindValueToUI()
       
    }
    
    // MARK: - UI configure

// MARK: - UI

extension MFWeatherViewController: MFViewConfigurer {
    var contentViews: [UIView] { return [scrollView, cancelButton] }

    var contentViewsSettings: [() -> Void] {
        return [{
            self.view.backgroundColor = UIColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 0.7)
            self.scrollView.hiddenSubViews(isHidden: false)}}]func configureSubViewsLayouts(a) {
        cancelButton.snp.makeConstraints { make in
            if #available(iOS 11, *) {
                make.top.equalTo(self.view.safeAreaLayoutGuide.snp.top)
            } else {
                make.top.equalTo(self.view.snp.top).offset(20)
            }

            make.left.equalTo(self.view).offset(20)
            make.height.width.equalTo(30)
        }

        scrollView.snp.makeConstraints { make in
            make.top.bottom.left.right.equalTo(self.view)}}} forUIViewThis agreement also appliesSwift
// MFWeatherSummaryView
    private override init(frame: CGRect) {
        super.init(frame: frame)

        initUI()
    }
    
    
// MARK: - UI

extension MFWeatherSummaryView: MFViewConfigurer {
    var rootView: UIView { return self }

    var contentViews: [UIView] {
        return [
            cityLabel,
            weatherSummaryLabel,
            temperatureLabel,
            weatherSummaryImageView,
        ]
    }

    var contentViewsSettings: [() -> Void] {
        return [UIConfigure]}private func UIConfigure(a) {
        backgroundColor = UIColor.clear
    }

    public func configureSubViewsLayouts(a) {
        cityLabel.snp.makeConstraints { make in
            make.top.centerX.equalTo(self)
            make.bottom.equalTo(temperatureLabel.snp.top).offset(-10)
        }

        temperatureLabel.snp.makeConstraints { make in
            make.top.equalTo(cityLabel.snp.bottom).offset(10)
            make.right.equalTo(self.snp.centerX).offset(0)
            make.bottom.equalTo(self)
        }

        weatherSummaryImageView.snp.makeConstraints { make in
            make.left.equalTo(self.snp.centerX).offset(20)
            make.bottom.equalTo(temperatureLabel.snp.lastBaseline)
            make.top.equalTo(weatherSummaryLabel.snp.bottom).offset(5)
            make.height.equalTo(weatherSummaryImageView.snp.width).multipliedBy(61.0 / 69.0)
        }

        weatherSummaryLabel.snp.makeConstraints { make in
            make.top.equalTo(temperatureLabel).offset(20)
            make.centerX.equalTo(weatherSummaryImageView)
            make.bottom.equalTo(weatherSummaryImageView.snp.top).offset(-5)}}}Copy the code

Since I’m using MVVM mode, viewDidLoad is still a little bit different from MVC mode, which might be the case if IT’s MVC


    override func viewDidLoad(a) {
        super.viewDidLoad()
        // Do any additional setup after loading the view.

		// Initialize the UI
        initUI()
	
		 // User interaction event entry
        addEvents()

       
    }
    
 // MARK: callBack.Copy the code

Since MVC callback patterns are difficult to unify (Delegate, Closure, Notification, KVC, etc.), callbacks are often scattered all over the controller. It is best to add a MARK flag, collected in the same area as possible, with the necessary comments for each callback:

  • Triggered by what operation
  • What are the consequences
  • Where does it end up

So in this sense UITableViewDataSource and UITableViewDelegate are two completely different behaviors, One is configure UI, and the other is control Behavior, so don’t write these two things together, it’s really ugly.

conclusion

Breaking up your code based on responsibility will make your code more elegant and concise, and will greatly reduce the number of catch-all codes. Reducing the cost of reading code is also a direction of optimization, after all, no one wants to mess with the code to affect their mood