View
public protocol View : AnyObject
Basic protocol representing the 4 main phases of the lifecycle of a UIView in Tempura.
Ideally all the reusable simple Views of the app should conform to this protocol. Please note that this protocol is just used in order to enforce the same lifecycle for all the views.
For more complex Views please refer to the ModellableView
protocol.
Overview
A view is a piece of UI that is visible on screen. It contains no business logic, it can contain UI logic.
class Switch: UIView, View {
// subviews
private var thumb = UIView()
// properties
var isOn: Bool = false {
didSet {
guard self.isOn != oldValue else { return }
self.update()
}
}
override init(frame: CGRect = .zero) {
super.init(frame: frame)
self.setup()
self.style()
}
// interactions
var valueDidChange: ((Bool) -> ())?
func setup() {
// define the subviews that will make up the UI
}
func style() {
// define the default look and feel of the UI elements
}
func update() {
// update the UI based on the value of the properties
}
override func layoutSubviews() {
// layout the subviews, optionally considering the properties
}
}
The interface that the View is exposing is composed by properties and interactions.
The properties
are the internal state of the element that can be manipulated from the outside,
interactions
are callbacks used to listen from outside of the view to changes occurred inside the element itself (like user
interacting with the element changing its value).
The lifecycle of a View contains four different phases:
public protocol View: class {
func setup()
func style()
func update()
func layoutSubviews()
}
This protocol is not doing anything for us, it’s just a way to enforce the SSUL phases.
Setup
The setup phase should execute only once when the View
is created, here you tipically want to create and add all the
children views as subviews
func setup() {
self.addSubview(self.headerView)
self.addSubview(self.contentView)
self.addSubview(self.footerView)
}
Style
The style phase should execute only once when the View
is created,
right after the setup phase.
Here you configure all the style related properties that will not change over time.
For all the style attributes that change depending on the “state” of the view,
look at the View.update()
phase.
func style() {
self.headerView.backgroundColor = .white
self.contentView.layer.cornerRadius = 20
}
Update
The update phase should execute every time the “state” of the View is changed. Here you update the View in order to reflect its new state.
var headerImage: UIImage? {
didSet {
self.update()
}
}
func update() {
self.headerView.image = self.headerImage
}
Layout
The layout phase is where you define the layout of your view.
It’s using the same layoutSubviews()
method of UIView
, meaning that can be triggered
using the usual setNeedsLayout()
and layoutIfNeeded()
methods of UIKit.
override func layoutSubviews() {
self.headerView.frame = CGRect(x: 0, y:0, width: self.bounds.width, height: 100)
self.contentView.frame = CGRect(x: 0, y: 100, width: self.bounds.width, height: 300)
}
Note on layout updates
When the layout of your view changes over time, you are responsible to call
setNeedsLayout()
inside the update()
phase in order to trigger a layout update.
func update() {
self.headerView.image = self.headerImage
self.setNeedsLayout()
}
override func layoutSubviews() {
let containsImage: Bool = self.headerImage != nil
let headerHeight: CGFloat = containsImage ? 100 : 0
self.headerView.frame = CGRect(x: 0, y:0, width: self.bounds.width, height: headerHeight)
}
Note on calling setup and style
When your UIView subclass conforms to the View protocol, you are responsible to call
the setup()
and style()
methods inside the init.
class TestView: UIView, View {
override init(frame: CGRect = .zero) {
super.init(frame: frame)
self.setup()
self.style()
}
func setup() {}
func style() {}
func update() {}
override func layoutSubviews() {}
}
-
The setup phase should execute only once when the
View
is created, here you tipically want to create and add all the children views as subviews.Declaration
Swift
func setup()
-
The style phase should execute only once when the
View
is created, right after the setup phase. Here you configure all the style related properties that will not change over time. For all the style attributes that change depending on the “state” of the view, look at theView.update()
phase.Declaration
Swift
func style()
-
The update phase should execute every time the “state” of the View is changed. Here you update the View in order to reflect its new state.
Declaration
Swift
func update()
-
The layout phase is where you define the layout of your view. It’s using the same
layoutSubviews()
method ofUIView
, meaning that can be triggered using the usualsetNeedsLayout()
andlayoutIfNeeded()
methods of UIKit.Declaration
Swift
func layoutSubviews()