Reputation: 1
I have a CustomType called AppData, and it look like this:
struct AppData {
var stringOfText: String
var colorOfText: Color
}
I am using this AppData in my Views as State or Binding, I have 2 Views in my project called: ContentView and another one called BindingView. In my BindingView I am just using Color information of AppData. And I am expecting that my BindingView render or response for Color information changes! How ever in the fact BindingView render itself even for stringOfText
Which is totally unnecessary, because that data is not used in View. I thought that maybe BindingView not just considering for colorOfText
but also for all package that cary this data and that is appData So I decided help BindingView to understand when it should render itself, and I made that View Equatable, But that does not helped even. Still BindingView refresh and render itself on changes of stringOfText
which it is wasting of rendering. How can I solve this issue of unnecessary rendering while using CustomType as type for my State or Binding.
struct ContentView: View {
@State private var appData: AppData = AppData(stringOfText: "Hello, world!", colorOfText: Color.purple)
var body: some View {
print("rendering ContentView")
return VStack(spacing: 20) {
Spacer()
EquatableView(content: BindingView(appData: $appData))
//BindingView(appData: $appData).equatable()
Spacer()
Button("update stringOfText from ContentView") { appData.stringOfText += " updated"}
Button("update colorOfText from ContentView") { appData.colorOfText = Color.red }
Spacer()
}
}
}
struct BindingView: View, Equatable {
@Binding var appData: AppData
var body: some View {
print("rendering BindingView")
return Text("123")
.bold()
.foregroundColor(appData.colorOfText)
}
static func == (lhs: BindingView, rhs: BindingView) -> Bool {
print("Equatable function used!")
return lhs.appData.colorOfText == rhs.appData.colorOfText
}
}
Upvotes: 1
Views: 1547
Reputation: 52565
When using Equatable
(and .equatable()
, EquatableView()
) on Views
, SwiftUI makes some decisions about when to apply our own ==
functions and when it is going to compare the parameters on its own. See another one of my answers with more details about this here: https://stackoverflow.com/a/66617961/560942
In this case, it appears that even if Equatable
is declared, SwiftUI skips it because it must be deciding that the POD
(plain old data) in the Binding
is determined to be non-equal and therefore it's going to refresh the view (again, even though one would think that the ==
would be enough to force it not to).
In the example you gave, obviously it's trivial for the system to re-render the Text
element, so it doesn't really matter if this re-render happened. But, in the even that there actually are consequences to re-rendering, you could encapsulate the non-changing parts into a separate child view:
struct BindingView: View {
@Binding var appData: AppData
var body: some View {
print("rendering BindingView")
return BindingChildView(color: appData.colorOfText)
}
//no point in declaring == since it won't get called (at least with the current parameters
}
struct BindingChildView : View {
var color: Color
var body: some View {
print("rendering BindingChildView")
return Text("123")
.bold()
.foregroundColor(color)
}
}
In the above code, although the BindingView
is re-rendered each time (although at basically zero cost, because nothing will change), the new child view is skipped because its parameters are equatable (even without declaring Equatable). So, in a non-contrived example, if the child view were expensive to render, this would solve the issue.
Upvotes: 2