TokamakUI / Tokamak

SwiftUI-compatible framework for building browser apps with WebAssembly and native apps for other platforms
Apache License 2.0
2.62k stars 111 forks source link

Preference Key subviews flipped #540

Open shial4 opened 1 year ago

shial4 commented 1 year ago

Describe the bug While working on the gestures, and testing difference approaches, including Preference Keys to report changes up the view tree. I've noticed that the actual view order (in HStack) is being flipped. Result can be see in the attached video

HStack {
                Rectangle()
                    .fill(Color.purple)
                    .frame(width: 100, height: 100)
                    .onTapGesture() {
                        print("🟢 1st gesture")
                    }
                    .onTapGesture() {
                        print("🟢 2nd gesture, should not be called")
                    }
                    .onTapGesture() {
                        print("🟢 3rd gesture, should not be called")
                    }
                    .simultaneousGesture(
                        TapGesture().onEnded({ _ in
                            print("🟢 simultaneousGesture gesture")
                        })
                    )
                    .onLongPressGesture {
                        print("🟢 onLongPressGesture")
                    }
                Text("Tap Parent")
            }
            .background(
                Color.gray
                    .onTapGesture() {
                        print("🩶 Background gesture")
                    }
            )
            .onTapGesture() {
                print("🔵 Parent gesture")
            }

Used code to trigger the issue


private struct GesturePreference: Equatable {
    let priority: UInt8
    let mask: GestureMask
}

private struct GesturePreferenceKey: PreferenceKey {
    static var defaultValue: [GesturePreference] = []

    static func reduce(value: inout [GesturePreference], nextValue: () -> [GesturePreference]) {
        value.append(contentsOf: nextValue())
    }
}

private struct GesturePreferenceModifier<G: Gesture>: ViewModifier {
    @Environment(\.isEnabled) var isEnabled
    @State var shouldEnableGestures: Bool = false

    let gesture: G
    let priority: UInt8
    let mask: GestureMask

    init(
        gesture: G,
        priority: UInt8,
        mask: GestureMask
    ) {
        self.gesture = gesture
        self.priority = priority
        self.mask = mask
    }

    func body(content: Content) -> some View {
        GestureView(
            gesture: gesture,
            isEnabled: isGestureEnabled,
            content: content
        )
        // ISSUE HAPPENS WITH EITHER PREFERENCE ATTACHED 
        .preference(key: GesturePreferenceKey.self, value: [
            GesturePreference(priority: priority, mask: mask)
        ])
        .onPreferenceChange(GesturePreferenceKey.self) { preferences in
            // Find the highest priority preference that matches the mask
            let highestMatchingPreference = preferences
                .filter { $0.mask.contains(mask) }
                .max { $0.priority < $1.priority }

            // Disable gestures if no matching preference is found or it's not the highest priority
            let newValue = highestMatchingPreference?.priority == priority
            if self.shouldEnableGestures != newValue {
                self.shouldEnableGestures = newValue
                print("🚀", preferences.count, self.shouldEnableGestures, String(describing: gesture.body.self))
            }
        }
    }
}

with the modifier applied like this:

modifier(GesturePreferenceModifier(gesture: gesture.body, priority: 0, mask: mask))

To Reproduce Steps to reproduce the behavior:

  1. Create a preference key
  2. Create HStack View with modifier applied to it and its subviews
  3. change state (i.e. on tap)
  4. See error

Expected behavior Views remain, in expected order

Screenshots

Desktop (please complete the following information):

https://github.com/TokamakUI/Tokamak/assets/8544773/0e5878ef-dfec-4848-9b55-d77222d366a3