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?
- Where is the control property configuration?
- Where is the entry point for user interaction?
- 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