View body is called although its @Binding has not changed

SwiftUI promise is to call View’s body only when needed to avoid invalidating views whose State has not changed. However, there are some cases when this promise is not kept and the View is updated even though its state has not changed.

Example:

struct InsideView: View {
   @Binding var value: Int
   // …
}

Looking at that view, we’d expect that its body is called when the value changes. However, this is not always true and it depends on how that binding is passed to the view.

When the view is created this way, everything works as expected and InsideView is not updated when value hasn’t changed.

@State private var value: Int = 0
InsideView(value: $value)

In the example below, InsideView will be incorrectly updated even when value has not changed. It will be updated whenever its container is updated too.

var customBinding: Binding<Int> {
   Binding<Int> { 100 } set: { _ in }
}
InsideView(value: customBinding)

Can anyone explain this and say whether it's expected? Is there any way to avoid this behaviour that can ultimately lead to performance issues?

Here's a sample project if anyone wants to play with it:

import SwiftUI

struct ContentView: View {
    
    @State private var tab = 0
    
    @State private var count = 0
    
    @State private var someValue: Int = 100
    var customBinding: Binding<Int> {
        Binding<Int> { 100 } set: { _ in }
    }
    
    var body: some View {
        VStack {
            Picker("Tab", selection: $tab) {
                Text("@Binding from @State").tag(0)
                Text("Custom @Binding").tag(1)
            }
            .pickerStyle(SegmentedPickerStyle())
            
            VStack(spacing: 10) {
                if tab == 0 {
                    Text("When you tap a button, a view below should not be updated. That's a desired behaviour.")
                    InsideView(value: $someValue)
                } else if tab == 1 {
                    Text("When you tap a button, a view below will be updated (its background color will be set to random value to indicate this). This is unexpected because the view State has not changed.")
                    InsideView(value: customBinding)
                }
            }
            .frame(width: 250, height: 150)
            
            Button("Tap! Count: \(count)") {
                count += 1
            }
        }
        .frame(width: 300, height: 350)
        .padding()
    }
}




struct InsideView: View {
    
    @Binding var value: Int
    
    var body: some View {
        print("[⚠️] InsideView body.")
        
        return VStack {
            Text("I'm a child view. My body should be called only once.")
                .multilineTextAlignment(.center)
            Text("Value: \(value)")
        }
        .background(Color.random)
    }
    
}



extension ShapeStyle where Self == Color {
    static var random: Color {
        Color(
            red: .random(in: 0...1),
            green: .random(in: 0...1),
            blue: .random(in: 0...1)
        )
    }
}

we’d expect that its body is called when the value changes.

Unfortunately, that is a wrong expectation. SwiftUI may call body at any time needed, for example any of the @State variables of the outer view change.

Is there any way to avoid this behaviour that can ultimately lead to performance issues?

You may need to construct your views as will not lead performance issues, or you can go back to UIKit world.

To avoid this do:

struct InsideView: View {
   let value: Int
   // …
}

@Binding is for when we want write access to the state.

View body is called although its @Binding has not changed
 
 
Q