A SwiftUI
view ought to present totally different .sheet
s. 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 var
s 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 @Binding
s used within the introduced content material, like within the demo code under.
The demo contains:
RouterTestView
holding theviewModel
with itsRouter
occasion. It reveals the present worth ofviewModel.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 theValueView
- Regardless of which sheet is used, the
- 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 sheetRouterTestView
accurately 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?
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()
}