Home > OS >  swiftUI: updating view
swiftUI: updating view

Time:06-15

I can't figure it out why view is not updating, please help. In real project I get data via websocket (and set variable with DispatchQueue.main.async {}). Here's the code as an example. After clicking on button nothing happens with the view. I use ObservableObject, Published attributes. What's the problem?

ps. It requires to add some more text to the post, because it's mostly the code, but I don't know what to add, everything is below :)

import SwiftUI

class DataBase: ObservableObject {
    
    @Published var data: [MyData]
    @Published var users: [User]
    
    init(data: [MyData], users: [User]) {
        self.data = data
        self.users = users
    }
}

class MyData: ObservableObject, Identifiable {
    
    @Published var type: String
    @Published var array: [Double]
    
    init(type: String, array: [Double]) {
        self.type = type
        self.array = array
    }
}

class User: ObservableObject, Identifiable {
    
    @Published var id: UUID = UUID()
    @Published var name: String
    @Published var data: MyData
    
    init(name: String, data: MyData) {
        self.name = name
        self.data = data
    }
}

let data: [MyData] = [
    MyData(type: "type1", array: [1, 2, 3]),
    MyData(type: "type2", array: [4, 5, 6, 7]),
]

let users: [User] = [
    User(name: "Tim", data: data[0]),
    User(name: "Steve", data: data[1]),
]

struct ContentView: View {
    
    let db = DataBase(data: data, users: users)
    
    var body: some View {
        ShowView(db: db)
    }
}

struct ShowView: View {
    
    @ObservedObject var db: DataBase
    
    var body: some View {
        
        HStack {
            
            List(db.users) { user in
                Text("\(user.name) \(user.data.type)")
                Text("\(user.data.array.count)")
                Divider()
            }
            
            List(db.data) { data in
                Text("\(data.type)")
                Text("\(data.array.count)")
                Divider()
            }
        }
        
        HStack {
            Button("add data to data[0]") {
                db.data[0].array.append(db.data[0].array.last!   10)
                print(db.data[0].array)
            }
            
            Button("add data to data[1]") {
                db.data[1].array.append(db.data[1].array.last!   20)
                print(db.data[1].array)
            }
        }
        
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

CodePudding user response:

try this using objectWillChange, works for me:

struct ShowView: View {
    
    @ObservedObject var db: DataBase
    
    var body: some View {
        
        HStack {
            
            List(db.users) { user in
                Text("\(user.name) \(user.data.type)")
                Text("\(user.data.array.count)")
                Divider()
            }
            
            List(db.data) { data in
                Text("\(data.type)")
                Text("\(data.array.count)")
                Divider()
            }
        }
        
        HStack {
            Button("add data to data[0]") {
                db.objectWillChange.send()  // <-- here
                db.data[0].array.append(db.data[0].array.last!   10)
                print(db.data[0].array)
            }
            
            Button("add data to data[1]") {
                db.objectWillChange.send()  // <-- here
                db.data[1].array.append(db.data[1].array.last!   20)
                print(db.data[1].array)
            }
        }
        
    }
}

CodePudding user response:

Just make model as value type (i.e. struct instead of class) - no more changes needed:

struct MyData: Identifiable {
    var id = UUID()
    var type: String
    var array: [Double]

    init(type: String, array: [Double]) {
        self.type = type
        self.array = array
    }
}

struct User: Identifiable {

    var id: UUID = UUID()
    var name: String
    var data: MyData

    init(name: String, data: MyData) {
        self.name = name
        self.data = data
    }
}

Tested with Xcode 13.4 / iOS 15.5

Update

Then it is needed to create separated views with ObservedObject for every observable model object, like

List(db.users) {
   UserRowView(user: $0)
}

struct UserRowView: View {
  @ObservedObject var user: User  // a class, so needed to be observed

  var body: some View {
    Text("\(user.name) \(user.data.type)")
    Text("\(user.data.array.count)")
    Divider()
  }
}

the same for MyData, or make a dependency update, like

class User: ObservableObject {
    @Published var data: MyData

    // ...

    private var cancellable: AnyCancellable?
    init(...) {
        // ....
        cancellable = data.objectWillChange.sink { [weak self] _ in
            guard let self = self else { return }
            self.objectWillChange.send()
        }
    }
}
  • Related