@Published var item: any? cast to concrete type and preserve binding

365 views Asked by At

Note to reader: This was a dumb mistake on my part... I was using @Binding in my subview when I should have used @ObservedObject. This lead me to think I needed to cast to Binding<ConcreteModel>..

I have a published var of type Any?, I would like to cast it and pass it to a View expecting Binding<ConcreteModel>... Is this supported?

Problem with my casting?

// This cast doesn't seem to be working...
if let concreteModel = viewModel.item as? Binding<ConcreteModel> {}

I'm not having success with this:

class ConcreteModel {
    public var count = 0
}

class MyViewModel: ObservableObject {
    @Published var item: Any?

    init() { item = ConcreteModel() }
}

// SwiftUI
struct MyView: View {
    @ObservedObject var viewModel = MyViewModel()

    var body: some View {
        // This cast doesn't seem to be working...
        if let concreteModel = viewModel.item as? Binding<ConcreteModel> {
            MySecondView(concreteModel: concreteModel)
        }   
    }
}

struct MySecondView: View {
    @Binding var concreteModel: ConcreteModel

    var body: some View {
        Text("\(concreteModel.count)")
    }
}
2

There are 2 answers

2
cohen72 On

What about using generics? For example ...

protocol Initializable {
  init()
}

class ConcreteModel: Initializable {
  public var count = 0
  required init() {}
}

class MyViewModel<T>: ObservableObject where T: Initializable {
  @Published var item: T?

  init() { item = T() }
}

struct MyView: View {
   @ObservedObject public var viewModel = MyViewModel<ConcreteModel>()

    var body: some View {        
       MySecondView(concreteModel: $viewModel.item)  
    }
}

struct MySecondView: View {
    @Binding var concreteModel: ConcreteModel?

    var body: some View {
        if let concreteModel = concreteModel {
           Text("\(concreteModel.count)")
        }          
    }
}
2
visc On

Ok so here is a solution:

// SwiftUI
struct MyView: View {
    @ObservedObject var viewModel = MyViewModel()

    var body: some View {
        
        // get concrete type
        if var concreteModel = viewModel.item as? ConcreteModel {
            // create new binding
            let binding = Binding<ConcreteModel>(get: {
                concreteModel
            }, set: {
                concreteModel = $0
            })
            // pass through new binding
            MySecondView(concreteModel: binding)
        }   
    }
}