Home > Back-end >  How to force view updates from changes in value of published array item
How to force view updates from changes in value of published array item

Time:06-16

Disclaimer: Android developer here

Been trying to make an existing android app work on iOS via KMM and I'd like to know if there's a way to force a view update in SwiftUI when only an update is made to an Item's paramaters in an array. (Something like the array[index].copy() function in Jetpack Compose

Here's the set up:

class MyViewModel: ObservableObject {


...
@Published var itemsArray = [Item]()

...

func increaseCount(itemIndex: Int)  {
        
        let count = itemsArray[itemIndex].quantity   1
        itemsArray[itemIndex].quantity = count
print("item count is \(count)")
           
    }

func decreaseCount(itemIndex: Int)  {
        var count: Int
        if itemsArray[itemIndex].quantity < 2 {
             count = 1
        } else {
            count = Int(itemsArray[itemIndex].quantity - 1)
        }
        
        itemsArray[itemIndex].quantity = Int32(count) //Xcode insisted on Int32
    }

...
}

On the View in question:

struct SecondView: View {
    
    @ObservedObject var viewModel: MyViewModel

...
 var body: some View {

...
 ForEach (viewModel.itemsArray, id: \.self)   {
                        item in
                        let index = viewModel.itemsArray.firstIndex(of: item)
HStack {
                                    Button(action: {viewModel.decreaseCount(itemIndex: index!
                                    )}) {
                                        Image(systemName: "minus.circle")
                                            .foregroundColor(Color.gray)
                                    }
                                                                    
                                   
                                    Text("\(viewModel.itemsArray[index!].quantity)")
                                    
                                    Button(action: {viewModel.increaseCount(itemIndex: index!)}) {
                                        Image(systemName: "plus.circle")
                                            .foregroundColor(Color.black)
                                    }
                                }
}
}
}

I am also setting

extension Item: Identifiable{}

on the file containing the ViewModel

The print statements in the viewmodel get updated according but the UI does not. Any help will be appreciated.

EDIT: Just realised that if I remove the element at the specified index and re-insert it, the UI updates. Surely there has to be a more efficient/ elegant way to this is on Swift, right?

CodePudding user response:

without all relevent the code, I can only guess. Try something like this:

struct SecondView: View {
    @ObservedObject var viewModel: MyViewModel
    ...
    var body: some View {
        ...
        ForEach ($viewModel.itemsArray, id: \.self) { $item in  // <-- here
            HStack {
                Button(action: {
                    if item.quantity < 2 {    // <-- here
                        item.quantity = 1
                    } else {
                        item.quantity -= 1
                    }
                }) {
                    Image(systemName: "minus.circle").foregroundColor(Color.gray)
                }
                Text("\(item.quantity)")
                Button(action: { item.quantity  = 1 }) {  // <-- here
                    Image(systemName: "plus.circle").foregroundColor(Color.black)
                }
            }
        }
    }
}

and no need for your increaseCount and decreaseCount code in your MyViewModel.

EDIT-1: here is my test code that shows the UI is updated when the buttons are tapped/clicked.

struct Item: Identifiable, Hashable {
    let id = UUID()
    var quantity = 0
}

class MyViewModel: ObservableObject {
    @Published var itemsArray = [Item(),Item(),Item()] // for testing some Items in the array
}

struct ContentView: View {
    @StateObject var viewModel = MyViewModel() // <-- here, or let
    
    var body: some View {
        SecondView(viewModel: viewModel)
    }
}

struct SecondView: View {
    @ObservedObject var viewModel: MyViewModel
    
    var body: some View {
        VStack(spacing: 33) {
            ForEach ($viewModel.itemsArray, id: \.self) { $item in  // <-- here
                HStack {
                    Button(action: {
                        if item.quantity < 1 {    // <-- here
                            item.quantity = 0
                        } else {
                            item.quantity -= 1
                        }
                    }) {
                        Image(systemName: "minus.circle").foregroundColor(Color.gray)
                    }
                    
                    Text("\(item.quantity)")
                    
                    Button(action: { item.quantity  = 1 }) {  // <-- here
                        Image(systemName: "plus.circle").foregroundColor(Color.black)
                    }
                }
            }
        }
    }
}
  • Related