pointfreeco / swift-perception

Observable tools, backported.
MIT License
574 stars 43 forks source link

Perception Bindable bindings cause body re-evaluations #55

Closed watt closed 7 months ago

watt commented 8 months ago

Description

It seems that SwiftUI does not consider bindings created from @Perception.Bindable to be equivalent across updates in the same way as bindings created from @SwiftUI.Bindable. Consider this example:

@Perceptible
class DemoState {
    var count = 0
    var isOn = false
}

struct DemoView: View {
    @Perception.Bindable var state = DemoState()

    var body: some View {
        WithPerceptionTracking {
            VStack {
                ToggleView(isOn: $state.isOn)

                Button("Increment") {
                    state.count += 1
                }

                Text("Count: \(state.count)")
            }
        }
    }

    struct ToggleView: View {
        @Binding var isOn: Bool
        var body: some View {
            let _ = Self._printChanges()
            Toggle("isOn", isOn: $isOn)
        }
    }
}

This view has a counter that can be incremented, and a toggle bound to a bool. Ideally, updates to count and isOn can be tracked independently. But it seems that when count is incremented by tapping the button, the binding to isOn changes as well, and we see it reported in the log:

DemoView.ToggleView: @self, _isOn changed.

When replacing @Perception.Bindable with @SwiftUI.Bindable the ToggleView body is no longer evaluated on each button press.

Checklist

Expected behavior

When tapping "Increment", the ToggleView is not re-evaluated.

Actual behavior

When tapping "Increment", the ToggleView is re-evaluated.

Steps to reproduce

Run the example in the description in an app or preview, and tap the "Increment" button.

Perception version information

main

Destination operating system

iOS 17

Xcode version information

Version 15.1 (15C65)

Swift Compiler version information

swift-driver version: 1.87.3 Apple Swift version 5.9.2 (swiftlang-5.9.2.2.56 clang-1500.1.0.2.5)
Target: arm64-apple-macosx14.0
mbrandonw commented 8 months ago

Hi @watt, I'm not sure there is going to be much we can do to fix this. Who knows why SwiftUI's bindings are special. We'll try to find some time to investigate more, but in the meantime please do share if you uncover anything.

watt commented 8 months ago

Sure thing. I've done some experiments while adding SwiftUI support to Workflow, and had success avoiding this by caching bindings. I'll follow up when I can provide a more concrete example of that.

mbrandonw commented 7 months ago

@watt We have a fix for this and will be PR'ing it soon.