can't use @Environment for Binding?

@Environment can't use for Binding?

@Observable
final class View1Model {
  var text: String = ""
}

struct View1: View {
  @State var viewModel = View1Model()
  var body: some View {
    View2()
      .environment(viewModel)
  }
}

struct View2: View {
  @Environment(View1Model.self) var viewModel
  
  var body: some View {
    TextField("Text", text: $viewModel.text) // Cannot find '$viewModel' in scope
  }
}

Accepted Reply

struct TitleEditView: View {
  @Environment(Book.self) private var book


  var body: some View {
    @Bindable var book = book
    TextField("Title", text: $book.title)
  }
}

I found this from Bindable Document https://developer.apple.com/documentation/swiftui/bindable

thanks @macrojd

Replies

Why don't you declare an environmentObject ?

 @EnvironmentObject var viewModel: View1Model
  • I think that combination of @Observable and ObservableObject is complex in iOS 17 or more.

Add a Comment

I also had that problem, this is the solution: add @Bindable var viewModel = viewModel in your view’s body, even though I don’t think it is elegant.

@Observable
final class View1Model {
  var text: String = ""
}

struct View1: View {
  @State var viewModel = View1Model()
  var body: some View {
    View2()
      .environment(viewModel)
  }
}

struct View2: View {
  @Environment(View1Model.self) var viewModel
  
  var body: some View {
   // use @Bindable to get the binding  
    @Bindable var viewModel = viewModel
    TextField("Text", text: $viewModel.text) 
  }
}
  • I saw this solution in Twitter. I think that too... We need more elegant solution or fix (bug?)

  • I think PropertyWrapper is available in Computed Property(body) but @Bindable does not work perfect in Computed Property(body)

  • This code is better at performance. but it is not cleaner than @EnvironmentObject

    below code

This code has good performance. But it is not cleaner than @EnvironmentObject

struct View2: View {
  @Environment(View1Model.self) var viewModel
  
  var body: some View {
    TextField("Text", text: Binding(get: {
      viewModel.text
    }, set: { newValue in
      viewModel.text = newValue
    }))
  }
}
  • Using @Bindable could be better as you would have to create this new Binding for every property you need to access.

    We are still in beta you must remember: things could change.

Add a Comment

The most elegant solution I've found is to extract the view and pass the model to a @Bindable property.

@Observable
final class View1Model {
  var text: String = ""
}

struct View1: View {
  @State var viewModel = View1Model()
  var body: some View {
    View2()
      .environment(viewModel)
  }
}

struct View2: View {
  @Environment(View1Model.self) var viewModel
  
  var body: some View {
    View3(viewModel: viewModel)
  }
}

struct View3: View {
  @Bindable var viewModel: ViewModel
  
  var body: some View {
    TextField("Text", text: $viewModel.text)
  }
}
  • thanks!

Add a Comment

You can also create an inline bindable:

TextField("Text", text: Bindable(viewModel).text)
  • Thanks, this one is the most elegant solution.

  • This was great solution, very clean and elegant.

Add a Comment
struct TitleEditView: View {
  @Environment(Book.self) private var book


  var body: some View {
    @Bindable var book = book
    TextField("Title", text: $book.title)
  }
}

I found this from Bindable Document https://developer.apple.com/documentation/swiftui/bindable

thanks @macrojd