'SwiftUI clean up ContentView

I'm trying to simplify the ContentView within a project and I'm struggling to understand how to move @State based logic into its own file and have ContentView adapt to any changes. Currently I have dynamic views that display themselves based on @Binding actions which I'm passing the $binding down the view hierarchy to have buttons toggle the bool values.

Here's my current attempt. I'm not sure how in SwiftUI to change the view state of SheetPresenter from a nested view without passing the $binding all the way down the view stack. Ideally I'd like it to look like ContentView.overlay(sheetPresenter($isOpen, $present).

Also, I'm learning SwiftUI so if this isn't the best approach please provide guidance.

class SheetPresenter: ObservableObject {

@Published var present: Present = .none
@State var isOpen: Bool = false

enum Present {
    case none, login, register
}

@ViewBuilder
func makeView(with presenter: Present) -> some View {
    switch presenter {
    case .none:
        EmptyView()
    case .login:
        BottomSheetView(isOpen: $isOpen, maxHeight: UIConfig.Utils.screenHeight * 0.75) {
            LoginScreen()
        }
    case .register:
        BottomSheetView(isOpen: $isOpen, maxHeight: UIConfig.Utils.screenHeight * 0.75) {
            RegisterScreen()
        }
    }
}

}



Solution 1:[1]

You are correct this is not the best approach, however it is a common mistake. In SwiftUI we actually use @State for transient data owned by the view. This means using a value type like a struct, not classes. This is explained at 4:18 in Data Essentials in SwiftUI from WWDC 2020.

EditorConfig can maintain invariants on its properties and be tested independently. And because EditorConfig is a value type, any change to a property of EditorConfig, like its progress, is visible as a change to EditorConfig itself.

struct EditorConfig {
    var isEditorPresented = false
    var note = ""
    var progress: Double = 0
    mutating func present(initialProgress: Double) {
        progress = initialProgress
        note = ""
        isEditorPresented = true
    }
}
struct BookView: View {
    @State private var editorConfig = EditorConfig()
    func presentEditor() { editorConfig.present(…) }
    var body: some View {
        …
        Button(action: presentEditor) { … }
        …
    }
}

Then you just use $editorConfig.isEditorPresented as the boolean binding in .sheet or .overlay.

Worth also taking a look at sheet(item:onDismiss:content:) which makes it much simpler to show an item because no boolean is required it uses an optional @State which you can set to nil to dismiss.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1