9.5 C
New York
Tuesday, March 11, 2025

ios – Binding will get damaged when presenting .shee – However the place and why?


A SwiftUI view ought to present totally different .sheets. For instance some EditorView with a picker view in a .sheet for a variety of totally different values.

As an alternative of utilizing totally different @State vars and .sheet blocks for any picker, I created the next Router and RouterView to deal with the sheet presentation:

class Router: ObservableObject {
    @Revealed var sheetContent: AnyView? = nil
    
    func current(content material: Content material) {
        self.sheetContent = AnyView(content material)
    }
}


struct RouterView: View {
    @ObservedObject var router: Router
    
    non-public var content material: () -> Content material
    
    init (_ router: Router, @ViewBuilder content material: @escaping () -> Content material) {
        self.router = router
        self.content material = content material
    }
    
    var physique: some View {
        content material()
            .sheet(isPresented: Binding(
                get: { router.sheetContent != nil },
                set: { if !$0 { router.sheetContent = nil } }
            )) {
                router.sheetContent
            }
    }
}

Whereas this works high-quality normally, it one way or the other breaks @Bindings used within the introduced content material, like within the demo code under.

The demo contains:

  • RouterTestView holding the viewModel with its Router occasion. It reveals the present worth of viewModel.showTitle and Buttons to indicate the sheet in three other ways:
    • A “basic” sheet straight setup and managed inside the view
    • A Router managed sheet the place the content material is created inside the viewModel
    • A router managed sheet the place the content material is created inside the view
  • Anticipated behaviour:
    • Regardless of which sheet is used, the ValueView is displayed.
    • Wether title is proven or hidden is dependent upon the present worth of $viewModel.showTitle
    • $viewModel.showTitle could be toggled. The change is proven on the RouterTestView and inside the ValueView
  • Precise behaviour:
    • Solely the basic sheet works as anticipated.
    • When utilizing any of the 2 Router sheets, toggling $viewModel.showTitle is accurately utilized to the viewModel. When closing the sheet RouterTestViewaccurately reveals true or false. When presenting one other sheet the preliminary state (hidden/seen) of the title is right.
    • Nonetheless, the toggling is NOT instantly utilized to the ValueView. The title textual content doesn’t change its visibility.

Conclusion: Utilizing the Router one way or the other breaks the Binding it solely works from ValueView again to the viewModel whereas the opposite route (viewModel to ValueView) doesn’t work.

How can this be solved?


enter image description here

Demo Code:

// View Mannequin
extension RouterTestView {
    class ViewModelModel: ObservableObject {
        @Revealed var router = Router()
        
        @Revealed var title: String = "The Worth"
        @Revealed var showTitle: Bool = true
        
        func present() {
            router.current(content material: ValueView(worth: title, showValue: Binding(get: { self.showTitle }, set: { self.showTitle = $0 })))
        }
        
        func present(_ inView: Content material) {
            router.current(content material: inView)
        }
    }
}


struct RouterTestView: View {
    @StateObject var viewModel: ViewModelModel = .init()
    
    @State non-public var isPresentingSheet: Bool = false
    
    var physique: some View {
        Textual content("showTitle == (viewModel.showTitle ? "true" : "false")")
        
        
        // Don't use Router however as an alternative use the basic option to current
        // the sheet. The Binding works with none drawback.
        Button("Current Sheet manually") {
            isPresentingSheet = true
        }
        .padding()
        .sheet(isPresented: $isPresentingSheet) {
            ValueView(worth: viewModel.title, showValue: $viewModel.showTitle)
        }
        
        
        // Use Router to current the sheet. It makes no distinction if the
        // Content material is created within the ViewModel or right here. The Binding is damaged.
        // Adjustments to $viewModel.showTitle in ValueView are accurately reported
        // to the viewModel, however the adjustments aren't acknowledged in ValueView
        // itself
        RouterView(viewModel.router) {
            // Sheet with Content material created in viewModel
            Button("Current Sheet with Router") {
                viewModel.present()
            }
            .padding()
            
            // Sheet with Content material created right here.
            Button("Current Sheet with Router") {
                viewModel.present(
                    ValueView(worth: viewModel.title, showValue: $viewModel.showTitle)
                )
            }
            .padding()
        }
    }
}

// ValueView as dummy "editor". Adjustments within the showValue Binding must be reported
// Again to the viewModel in RouterTestView and toggle the visibility of the worth right here.
struct ValueView: View {
    let worth: String
    @Binding var showValue: Bool
    
    var physique: some View {
        Textual content("Right here is the worth:")
        
        if showValue {
            Textual content(worth)
        }
        
        Button("Toggle") {
            showValue.toggle()
        }
    }
}

#Preview {
    RouterTestView()
} 

Related Articles

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Latest Articles