iosswiftswiftuipublishobservableobject

Objects associated with enums in Swift lack ObservableObject and @Published wrappers, preventing state changes from updating the UI


I have a MainViewModel that holds an array of enums with associated objects. However, when I use this in a SwiftUI view, the variable loses its @Published wrapper, and the UI doesn't update or get notified of changes. How can I preserve the @Published status of the variable?

Here's my code look like:

// Enum with associated types
enum ViewType: Hashable, Identifiable {
    case .a(ViewModelA)
    case .b(ViewModelB)
    case .c(ViewModelC)
}

// classes used in associated types
class ViewModelA: ObservableObject {
    @Published var listIndex: Int = 1
    //some other properties
}

class ViewModelB: ObservableObject {
    @Published var name: String = "hello"
     //some other properties
}

class ViewModelC: ObservableObject {
    @Published var isOn: Bool = false
     //some other properties
}

// then I create an array in my mainViewModel
// to use in my SwiftUI view
class MainViewModel: ObservableObject {
    @Published var viewTypeArray: [ViewType] = [] // add items to it based on the backend response (assume I have 4 items)
}


// then I use the viewModel in my view
struct MyView: View {
    @ObservedObject viewModel: MainViewModel

    init(viewModel: MainViewModel) {
        self.viewModel = viewModel
    }

    var body: some View {
        ForEach(viewModel.viewTypeArray) { view in
            case .a(let viewModelA):
               // I display something
               // I loose the ObservableObject wrapper
               // then I lose the connection to @Published wrapper properties ex: `listIndex`
               // and no ui updates will trigger here
            case .b(let viewModelB):
               // I display something
               // I loose the ObservableObject wrapper
               // then I lose the connection to @Published properties ex: `name`
               // and no ui updates will trigger here
            case .c(let viewModelC):
               // I display something
               // I loose the ObservableObject wrapper
               // then I lose the connection to @Published properties ex: `isOn`
               // and no ui updates will trigger here
        }
    }
}

I tried using an ObservableObject wrapper for each child ViewModel (e.g., ViewModelA, ViewModelB) to maintain a direct connection with the view, but this didn't work. I want to use an enum with associated types, along with @ObservableObject and @Published wrappers, to ensure that changes properly update the SwiftUI view.


Solution

  • You can use a wrapper to selectively expose specific @Published properties.

    struct PropertyWrapper<T: ObservableObject, Content: View>: View {
        @ObservedObject private var value: T
        private var content: (T) -> Content
    
        init(value: T, @ViewBuilder content: @escaping (T) -> Content) {
            self.value = value
            self.content = content
        }
    
        var body: some View {
            content(value)
        }
    }
    

    and then use it as below ex: in case .b

     ForEach(viewModel.viewTypeArray) { view in 
                switch view {
                    case .a(let viewModelA):
                       // I display something
                       // I loose the ObservableObject wrapper
                       // then I lose the connection to @Published wrapper properties ex: `listIndex`
                       // and no ui updates will trigger here
                    case .b(let viewModelB):
                       PropertyWrapper(value: viewModelB) { object in 
                         Text(object.name) // this should update the UI properly when `name` updates
                       }
                    case .c(let viewModelC):
                       // I display something
                       // I loose the ObservableObject wrapper
                       // then I lose the connection to @Published properties ex: `isOn`
                       // and no ui updates will trigger here
                }
            }
    

    I hope this work as expected. have fun.