SwiftUI —Why should you write as many custom views for better performance
--
SwiftUI
is everywhere in the Apple world. Developers are playing with it all the time, and here is my observation on how the view renders and what you should do for better performance. Critiques are welcome.
The SwiftUI
’s declarative style offers us many options on how we write the UI code. Even though I’ve always preferred writing UIKit
over Interface Builder / StoryBoard, writing SwiftUI
takes some time to get used to. Once you get hold of it, it's so intuitive. It’s a paradigm shift we’ve to prepare ourselves for.
Here’s a sample view with a TextField
, a Toggle
Switch, a Picker
which is segmented which changes the value in the below Text
and a Button
having a simple Image
as label.
I hope the code is self explanatory. Here is how it looks.
Now, place breakpoints on the four top-level elements in the VStack
— TextField
, Toggle
, Picker
and HStack
. Run the app and try interacting with all the elements in the View. What do you notice?
When we type something in the TextField
, or switch the Toggle
or press the small Button
no breakpoint is reached. But when we change the Picker
, all breakpoints are hit, meaning that the entire view is redrawn. It is reasonable that the HStack
below the picker is redrawn. But why the entire view? Maybe SwiftUI
is not mature enough to find out where we have use the selected value from picker in our view.
Now, go ahead and change the line #29 from Text(texts[selected])
to Text("Sample text")
.
Run the app and interact with the elements again, of course with the breakpoints. Can you notice that the view is not redrawn when we change the Picker
?
SwiftUI
knows when a child view bound with variable from its parent view affects any of the sibling views, but sadly doesn’t know which sibling view so it redraws the entire parent view.
Let me give you another example to show case this. At the end of the Button
, add .disabled(isSwitchOn)
. Run the app again and test. This time, the entire view is redrawn when we flip the Toggle
switch. You get the point, right?
So, to reduce the number of times a view is redrawn, it is better to create custom child views, which are bounded to @State
variables from super view but doesn’t affect any of its other sibling views.
First lets look at the TextField
. It is a standalone view with a few function calls chained to it. When the Toggle
switch or Picker
is changed, even though we can assume that TextField
is not redrawn, but the function calls are done again. So lets move the TextField
and the chained calls into a custom view.
struct MyTextField: View {
var placholder: String
@Binding var value: String
var body: some View {
TextField(placholder, text: $value)
.padding(EdgeInsets(top: 8, leading: 16,
bottom: 8, trailing: 16))
.background(Color(UIColor.secondarySystemFill))
.cornerRadius(4)
}
}
Now, place a break point inside the body of the MyTextField
and replace the TextField
in line #13, and its chained function calls with a declaration of MyTextField
like below.
MyTextField(placholder: "Enter your name", value: $textFieldValue)
Now when we flip the switch, the parent view is redrawn. But MyTextField
is not redrawn. It just goes ahead with the previous instance of MyTextField
. SwiftUI
compares views, which all conform to Equatable
and decides whether to call the body
again. In this case, as MyTextField
has not changed, so not redrawn.
We can provide custom implementations for Equatable
in any view, but we’ll discuss that another day.
Also, if the Picker
and the HStack
below are related to each other but not to any other sibling view, we can create a Custom View with the two. In such case, just like when the TextField
changes and the parent view remains the same, changes to the Picker
will affect only the new custom child view and not the parent view.
I leave it to you to try that out.
TL:DR; We have now arrived at two cases, where it is better to create Custom Views. First, when we don’t want the multiple chaining function calls to happen over and over again when other parts of the super view changes. Second, when two siblings are related to each other but not to the other siblings in the parent view.
Happy Swifting!