Understanding SwiftUI View lifecycle
iOS SwiftUI View Estimated reading time: 5 minutesSwiftUI
brings for us, developers, the whole new ecosystem for creating complex and responsible UI
.
Thus the entry point for this approach is quite low and u can start producing acceptable UI
after the first 5 min, it’s always better to dive a bit and understand how everything works under the hood. Such knowledge will improve your future work and our developer’s skills. Even more - without understanding how it works, u can’t develop something really interesting and stunning.
Talking about SwiftUI
, the good start point may be understanding View’s lifecycle - how everything is combined and how every part and components of this ecosystem are related one to each other.
Lifecycle
We may draw a small scheme that visualize the full Lifecycle of a View
as follow:
As u can see - it’s not very complex. But under this simplicity, there is much more. Every action has its mechanism(s) that improve and optimize it.
Let’s review a bit what’s going on when we create and use a SwiftUI
’s View
.
Initialization
View
is a protocol that requires from us only body definition, we also have one more requirement - Type
that conform to View
protocol should be a value type, thus struct
.
This requirement is not something that u can observe during a compiling time, instead of in runtime u will receive fatalError:
So View may be only a value type - struct. This means that initialization is quite simple and yet powerful. In most cases compiler create an initializer for us.
During initialization, we also often can create inner @State
variables (should be private
) or pass @State
through @Binding
. Other properties may be observed using various ways such as @StateObject
, @ObservedValue
or @SceneStorage
etc.
So during initialization, we simply create and configure View
’s states and “store” variables in SwiftUI
ViewGraph
.
ViewGraph
constructed by private frameworkAttributeGraph.framework
(/System/Library/PrivateFrameworks/AttributeGraph.framework)
Result of this initialization - very complex type with Generics, or simply some View
. some
specially designed for hiding actual type. Why? That’s because of a few main reasons:
- type-level abstraction
- protection by hiding implementation details (that’s almost the same as the point above)
- simplification
some
- is opaque type that was introduces here. This type simply hide return value’s type information and only refere to conformed protocol.
some
is opaque type, so all limitation and possibilities are also in place:
PAT
’s (protocol associated types) can’t be used for opaque type- these types are identifiable
- can be composed with generic placeholders
To check the actual type of View, we may create a very simple example:
And if we use Mirror
(aka print(Mirror(reflecting: self).subjectType)
) we can get:
Quite complex generic type, that can be hidden above some View
.
check out any complex
View
that u use in a real project - u will observe a huge name ofType
.
Change
As we already know, View
’s can be redrawn whenever something is changed. This performed very efficiently because of the used mechanism - SwiftUI checks what exactly was changed and redraw only this part (this is also known as diff
).
AnyView
removes this efficiency because this is type-eraser, soSwiftUI
can’t compare an unknown type with an unknown type, instead, the whole view will be redrawn. So useAnyView
wisely.
Another essential component of any View - various @propertyWrappers
.
We can’t (actually can but with a lot of efforts) interact within View
and show to user any update without special variables that can change and hold their state independently from View
(thus view is a struct
and any change will recreate/mutate it). Thanks to @propertyWrappers
, we have a template with boiler part code for various purposes needed during the life of View
.
I wrote an overview about available
@propertyWrappers
inSwiftUI
. You can check it here.
Anyway, these values are initialized within the view and changed outside of view. The only thing that should be done by View
- is properly reacting to them. And it does. This is done by design. So we should think only about logic now, not about sync the data and view.
The update/redraw View
flow may be as follow:
- Find State of View using
Field Descriptor
- Inject
ViewGraph
intoState
- Render
View.body
State
is changedState
notify theViewGraph
to update view- Re-render
View.body
if u interested into how
@State
might work in details - check this post
Events
Configuration of View
’s states is also simplified - we have few callbacks. In additional, if we need some event handling configuration we may use another dataFlow mechanisms such as onChange(of:perform:)
or some other View Modifiers.
check this official doc for more about data flow
If be clear, there are 2 callback that can be used
That’s it. The name tells us their purpose by itself.
other input events
In total, we may sum-up all events and update a bit the scheme from the very beginning of the post:
Summary
Such an elegant design of View
’s lifecycle reduces required effort, amount of code and so bugs.
With a declarative
Swift
syntax that’s easy to read and natural to write,SwiftUI
works seamlessly with newXcode
design tools to keep your code and design perfectly in sync (Apple). source
Share on: