Saturday, October 24, 2026
HomeiOS Developmentios - SwiftUI: @GestureState with @State not working as supposed (MRE included)

ios – SwiftUI: @GestureState with @State not working as supposed (MRE included)

[ad_1]

There is a minimal working instance (with an answer!) on the finish of this put up. After toggling the menu sheet I carried out, attempt dismissing it with a swipe left gesture. You can see an animation glitch. I would prefer to know what’s inflicting this problem, and why the answer fixes it. Please hold studying if you need to try my observations.


I am making an attempt to implement a MenuSheet, which could be dismissed with a single swipe left gesture. I created @Binding var isOpen: Bool and @GestureState var translation: CGFloat, so if translation turns into lower than zero on the time the gesture completes (which suggests swipe left), isOpen is modified to false.

I additionally know that when the gesture completes, the @GestureState resets its property again to its preliminary state, which is zero.

Placing all these collectively, there are two state modifications that ought to occur when the swipe left gesture completes:

  • translation := 0
  • isOpen := false

I assumed the 2 state modifications happen on the similar time earlier than the view rerenders. Nonetheless, my remark was that 1) the translation is first set to 0 and the view rerenders, 2) which is adopted by isOpen being set to false with a second view rerender.

This causes an inconsistency as a result of proper earlier than 2), the 2 states instantaneously change into translation == 0 && isOpen == true and it renders the view as if the menu sheet is open, though I simply completed swipe left gesture to dismiss the sheet. This results in a bizarre animation glitch.

Unusually, I may clear up this problem by merely wrapping isOpen in an @ObservableObject. The answer is connected under, too.

I would prefer to know what’s inflicting this problem and why the answer fixes it. Particularly, the next are some questions that may be associated:

  • How does SwiftUI deal with view rerender when two or extra states are modified nearly concurrently?
  • Do @State and @ObservedObject behave in another way in the case of figuring out when to refresh the view? How?

Thanks!


This instance code reproduces the problem solely in Preview mode. To run it within the simulator, please consult with Replace part under.

Examined in Swift Playground 4, iPadOS 15.

import SwiftUI

struct MenuSheet<Content material>: View the place Content material: View {
    let maxWidth: CGFloat = 290
    
    @Binding var isOpen: Bool
    @ViewBuilder var content material: () -> Content material
    
    @GestureState personal var translation: CGFloat = 0
    
    personal var dragGesture: some Gesture {
        DragGesture().updating(self.$translation) { worth, state, _ in
            state = worth.translation.width
        }
        .onEnded { worth in
            self.isOpen = worth.translation.width >= 0
        }
    }
    
    personal var offset: CGFloat {
        isOpen ? 0 : maxWidth
    }
    
    var physique: some View {
        let _ = print("redrawing physique with isOpen:(self.isOpen), translation: (self.translation)")
        GeometryReader { reader in
            ZStack {
                Colour(UIColor.systemBackground)
                    .edgesIgnoringSafeArea(.all)
                self.content material()
            }
            .body(width: maxWidth, top: reader.dimension.top)
            .offset(x: min(-self.offset + self.translation, 0))
            .animation(.default, worth: isOpen)
        }
        .highPriorityGesture(
            dragGesture
        )
    }
}

/// A easy wrapper that's used to preview `MenuSheet`.
struct MenuSheetWrapper: View {
    @State var isOpen = false
    var physique: some View {
        ZStack {
            Button {
                isOpen.toggle()
            } label: {
                Textual content("Faucet this button to toggle!")
            }
            
            MenuSheet(isOpen: $isOpen) {
                Textual content("Swipe left to dismiss.")
            }
        }
    }
}

struct MenuSheetWrapper_Previews: PreviewProvider {
    static var previews: some View {
        MenuSheetWrapper()
    }
}

Change MenuSheetWrapper with the next code.

/// A easy wrapper that's used to preview `MenuSheet`.
struct MenuSheetWrapper: View {
    class AppState: ObservableObject {
        @Revealed var isOpen = false
    }
    
    @ObservedObject var appState = AppState()
//    @State var isOpen = false
    var physique: some View {
        ZStack {
            Button {
                appState.isOpen.toggle()
//                isOpen = !isOpen
            } label: {
                Textual content("Faucet this button to toggle!")
            }
            
            MenuSheet(isOpen: $appState.isOpen) {
                Textual content("Swipe left to dismiss.")
            }
        }
    }
}

To breed the problem within the simulator, change your @most important App with the next code snippet.

import SwiftUI

@most important
struct YourApp: App {
    @State personal var isOpen = false

    var physique: some Scene {
        WindowGroup {
            ZStack {
                Button {
                    isOpen.toggle()
                } label: {
                    Textual content("Faucet this button to toggle!")
                }
                MenuSheet(isOpen: $isOpen) {
                    Textual content("Swipe left to dismiss.")
                }
            }
        }
    }
}

[ad_2]

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments