The preceding example made it clear that we should not load views in the constructor. Now, consider the following generic code that helps us to write controllers with an asynchronous state:
enum State<T> {
case unknown, loading
case loaded(T)
case error(Error)
}
class StateController<T>: UIViewController {
var state: State<T> = .unknown {
didSet { updateState() }
}
private func updateState() {
switch state {
case .loading:
view.alpha = 0.0 // make it transparent while loading
case .loaded(_):
view.alpha = 1.0 // show it when loaded
default: break;
}
}
}
// In another view controller
let controller = StateController<String>()
controller.state = .loading
present(controller, animated: true, completion: nil)
This example is a bit trickier, and you may not identify what is wrong at first glance. Here, we have a state property that, when set, will call updateState(), which, in turn, will access the view property in order to toggle its alpha property.
As we know, view is lazily loaded, and the first call to it will be for it to be loaded; therefore, viewDidLoad() is to be called. The problem here is that at the time we set state to .loading, the view controller isn't responsible for any view on the screen. We have again accessed the view outside of UIKit's own life cycle.
Luckily for us, we can solve this problem easily as follows:
- We need to prevent the view from loading as long as the view isn't on screen
- As soon as the view is loaded, we should display the current state:
class StateController<T>: UIViewController {
var state: State<T> = .unknown {
didSet {
// avoid accessing the view unnecessarily
guard isViewLoaded else { return }
updateState()
}
}
override func viewDidLoad() {
super.viewDidLoad()
// ensure we show the right state on screen
updateState()
}
private func updateState() { /* same implementation */ }
}
As your project grows, keeping each view controller properly encapsulated and loading within UIKit's or AppKit's life cycles is critical to guaranteeing smooth transitions and efficient memory management.
The issues that we discussed here are common, so check for them in your implementations! In the next section, we'll detail a powerful tool that will help avoid Massive View Controller syndrome, child view controllers, and view controller composition.