2

I get a compiler error trying to specify Binding on property of @ObservedObject using KeyPath for a generic type T.

I am trying to create a generic SwiftUI View that will work for a class that has several properties of the same type. The SwiftUI View needs to be generic; however I cannot determine how to create a Binding to a property of the main object that I access through a KeyPath. The code below returns an error of "Value of type 'T' has no subscripts"

UPDATE: I identified the error in my code instead of $model[keyPath: kp] I should have used $model[dynamicMember: kp].

import SwiftUI

class TestClass: ObservableObject {
    @Published public var str1: String = "one"
    @Published public var str2: String = "two"
}

struct ContentView: View { 

    @ObservedObject var test = TestClass()
    var body: some View {
        VStack {
            HStack{
                Text("String One").padding(20)
                StringView(model: test, kp: \TestClass.str1).padding(20)
            }
            HStack{
                Text("String Two").padding(20)
                StringView(model: test, kp: \TestClass.str2).padding(20)
            }
        }
    }
}

struct StringView<T>: View where T: ObservableObject {

    @ObservedObject var model: T

    var kp: ReferenceWritableKeyPath<T,String>

    init(model: T, kp: ReferenceWritableKeyPath<T,String>) {
        self.model = model
        self.kp = kp
    }

    var body: some View {
        HStack{
        TextField("String", text: $model[keyPath: kp])   <--- COMPILER ERROR HERE
        Text(model[keyPath: kp])
        }
    }
}

Is it possible to do this?

1 Answer 1

1

I think you can do it with followings. But my idea is a little bit verbosely.

At least, TextField requires Binder. So KeyPath's target Type should be this.

class TestClass: ObservableObject {
  @Published public var str1: String = "one"
  @Published public var str2: String = "two"
}

struct _ContentView: View {

  @ObservedObject var test = TestClass()

  init() {

  }
  var body: some View {
    VStack {
      HStack{
        Text("String One").padding(20)
        StringView(model: test, kp: \ObservedObject<TestClass>.Wrapper.str1).padding(20)
      }
      HStack{
        Text("String Two").padding(20)
        StringView(model: test, kp: \ObservedObject<TestClass>.Wrapper.str2).padding(20)
      }
    }
  }
}

struct StringView<T>: View where T: ObservableObject {

  @ObservedObject var model: T

  var kp: ReferenceWritableKeyPath<ObservedObject<T>.Wrapper, Binding<String>>

  init(model: T, kp: ReferenceWritableKeyPath<ObservedObject<T>.Wrapper, Binding<String>>) {
    self.model = model
    self.kp = kp
  }

  var body: some View {
    HStack{
      TextField("String", text: $model[keyPath: kp])
      Text($model[keyPath: kp].wrappedValue)
    }
  }
}
Sign up to request clarification or add additional context in comments.

1 Comment

I updated the original post. I was misread the subscript definition for the ObserveObject. Instead of $model[keyPath: kp] it should be $model[dynamicMember: kp]

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.