1

How to pass Published as Binding?

I have rating views for an array of products and a clear button, when the clear button is pressed I want to clear the ratings of all.

I am converting the Published object to Binding in order to reflects the updates but it doesn't work.

If I use direct $viewModel.products in ForEach the ratings update & clear works perfectly.

struct ProductModel: Identifiable, Equatable {
    var id: Int
    var title: String
    var rating: Int
        
    mutating func clearRating() {
        rating = 0
    }
}

protocol ViewModelType: ObservableObject {
    associatedtype Input
    associatedtype State
    
    var state: State { get }
    func trigger(_ input: Input)
    
    var subscriptions: Set<AnyCancellable> { get }
}

struct TestView: View {
    
    @StateObject var viewModel = TestViewModel()
    
    var body: some View {
        switch viewModel.state {
        case .initial:
            Text("Hello world")
                .onAppear {
                    viewModel.trigger(.initial)
                }
            
        case .data(let products):
            VStack(spacing: 20) {
                ForEach(products) { product in
                    Text("\(product.rating.wrappedValue)")
                    RatingStarView(rating: product.rating)
                }
                Button {
                    viewModel.clear()
                } label: {
                    Text("Clear Rating")
                }
            }
        }
    }
}

class TestViewModel: ViewModelType {
    
    enum State {
        case initial
        case data(Binding<[ProductModel]>)
    }
    
    enum Input {
        case initial
        case clear
    }
    
    @Published var state: State = .initial
    @Published var products: [ProductModel] = [.init(id: 101, title: "Product 1", rating: 3),
                                                             .init(id: 102, title: "Product 2", rating: 2)]
    
    func trigger(_ input: Input) {
        switch input {
        case .initial:
            convertPublishedToBinding()
            
        case .clear:
            clear()
        }
    }
    
    func clear() {
        products.indices.forEach { products[$0].clearRating() }
    }
    
    private func convertPublishedToBinding() {
        state = .data(Binding(get: { [weak self] in
            self?.products ?? []
        }, set: { [weak self] newValue in
            print(newValue)
            self?.products = newValue
        }))
    }
}

NOTE: I was referring to this article https://quickbirdstudios.com/blog/swiftui-architecture-redux-mvvm/

Anirudha Mahale
  • 2,526
  • 3
  • 37
  • 57

1 Answers1

-2

You can find the answer in this post.

The short summary is: The view will only change if the array itself is modified. When changing an attribute of an object inside of the array, the view will not update.

Cameron
  • 542
  • 5
  • 13