Home > Enterprise >  SwiftUI view doesn't react to array property changing
SwiftUI view doesn't react to array property changing

Time:02-03

Showing a list of MessageView:

struct ContentView: View {
    @ObservedObject var viewModel: ConversationViewModel = ConversationViewModel()

    var body: some View {
        ForEach(viewModel.messages, id: \.id) { m in
            MessageView(message: m)
                .id(m.id)
        }
    }

Provided by the viewModel:

class ConversationViewModel: ObservableObject {
    @Published var messages: [Message] = []
}

The Message struct:

struct Message: Identifiable, Equatable {
    var id = UUID()
    var type: MessageType = .text
    var author: MessageAuthor
    var body: String = ""
    var timestamp: Date = Date()
    var failed: Bool = false
    var media: URL?
    var isLast: Bool = false
    
    static func == (lhs: Message, rhs: Message) -> Bool {
        return lhs.id == rhs.id
    }
}

And then the MessageView being shown for each message:

struct MessageView: View {
    private let message: Message
    
    init(message: Message) {
        self.message = message
    }

    var body: some View {
    if message.type == .image {
                if (message.media != nil) {
                    AsyncImage(url: message.media)
                } else {
                    Text("No media.")
                }
    }

Even once the viewModel makes the message.media take a value instead of being null, the above MessageView always shows "No media.".

The viewModel publishes the messages array, and Message is a struct... I think I'm doing everything right, but I'm obviously missing something here.

Any ideas?

CodePudding user response:

Remove

static func == (lhs: Message, rhs: Message) -> Bool {
    return lhs.id == rhs.id
}

SwiftUI will only use the id to determine if it needs to be redrawn with this implementation.

Just add Hashable and Equatable to all the structs involved.

Use the default implementation.

CodePudding user response:

try this approach, changing the logic of your MessageView and using @StateObject var viewModel = ConversationViewModel() in ContentView. And as mentioned, remove static func == .... Works well for me.

 struct MessageView: View {
     let message: Message  // <-- here
     
     var body: some View {
         if message.type == .image {
             if (message.media != nil) {
                 AsyncImage(url: message.media)
             } else {
                 Text("No image")
             }
         } else {
             Text("case text presumably")  // <-- here
         }
     }
 }

You could also use a switch instead of if/else.

  • Related