Home > Back-end >  How do I share/bind @StateObject between SwiftUI views?
How do I share/bind @StateObject between SwiftUI views?

Time:06-05

I have the following SwiftUI View "PaletteBar":

struct PaletteBar: View {

    final class State: ObservableObject {
        @Published var selectedColor: Color? = .black
        @Published var isEraserSelected = false
    }

    private enum Constants {
        static let colors: [Color] = [.black, .white, .blue, .green, .yellow, .red]
        static let eraserColor: Color = .black.opacity(0.7)
    }

    // MARK: - Properties

    @ObservedObject var state = State()

    // MARK: - Body

    var body: some View {
        HStack {
            PaletteItem(color: Constants.eraserColor, isSelected: Binding(
                get: { state.isEraserSelected },
                set: {
                    state.isEraserSelected = $0
                    state.selectedColor = $0 ? nil : state.selectedColor
                }
            ))
            .overlay { Image(systemName: "pencil.slash") }
            .foregroundColor(.white)
            .frame(maxWidth: .infinity)

            ForEach(Constants.colors, id: \.self) { color in
                PaletteItem(color: color, isSelected: Binding(
                    get: { state.selectedColor == color },
                    set: {
                        state.selectedColor = $0 ? color : nil
                        state.isEraserSelected = $0 ? false : state.isEraserSelected
                    }
                ))
                .frame(maxWidth: .infinity)
            }
        }
    }

}

This stores the state in an @ObservedObject. I would like to pass this state up the hierarchy e.g PaletteBar is a sub view of AnnotateImageToolbar:

struct AnnotateImageToolbar: View {

    final class State: ObservableObject {
        @Published var selectedColor: Color? = .black
        @Published var isEraserSelected = false
    }

    private enum Constants {
        static let spacing: CGFloat = 12.0
        static let backgroundColor: Color = .black.opacity(0.7)
        static let colorsInsets = EdgeInsets(top: 12.0, leading: 16.0, bottom: 0.0, trailing: 16.0)
        static let sliderInsets = EdgeInsets(top: 0.0, leading: 16.0, bottom: 12.0, trailing: 16.0)
        static let radius: CGFloat = 8.0
    }

    // MARK: - State

    @StateObject var state = State()

    var body: some View {
        VStack(spacing: Constants.spacing) {
            PaletteBar()
                .padding(Constants.colorsInsets)

            ZStack {
                SizeBar()
                    .foregroundColor(.gray)
                    .frame(maxHeight: 12.0)
                BrushSizeSlider()
            }
            .padding(Constants.sliderInsets)
        }
        .background(Constants.backgroundColor)
        .clipShape(RoundedCorners(radius: Constants.radius, corners: [.topLeft, .topRight]))
    }

}

Here I create an identical state object @StateObject var state = State(). Is there a way to bind the values from the state in PaletteBar to the state in AnnotateImageToolbar? Or Should I be creating a single state object and injecting it into each view?

CodePudding user response:

Inject it as environment object, like

@StateObject var state = State()

var body: some View {
    VStack(spacing: Constants.spacing) {
      // ... other code
    }
    .background(...)
    .clipShape(...)
    .environmentObject(state)  // << here !!

and use it inside children:

struct PaletteBar: View {

    // MARK: - Properties

    @EnvironmentObject var state: State  // << injected by parent !!

// ...
}
  • Related