SwiftiSwift
SwiftiSwift

Reputation: 8687

SwiftUI View doesn't update in a seperate View

I'm using a List to display checkmarks of messages. The issue is if i seperate the checkmarks view into a subview that view doesn't get updated. If i put exactly the same code directly into the List it works as expected. I'm using that checkmarks view in multiple places so it need to be in a subview.

Example:

This doesn't work:

List(filteredMessages, id: \.content.uniqueIdentifier) { message in
     DoubleCheckmark(message: message)
}

But this does work:

List(filteredMessages, id: \.content.uniqueIdentifier) { message in
    HStack(spacing: 0) {
        if message.content.readStatus == .loading {
            Text("loading")
        } else if message.content.readStatus == .sent {
            Image(systemName: "checkmark")
                .resizable()
                .scaledToFit()
                .foregroundColor(message.content.readStatus == .read ? .blue : .gray)
        } else if message.content.readStatus == .received {
            Image(systemName: "checkmark")
                .resizable()
                .scaledToFit()
                .foregroundColor( .gray)

            Image(systemName: "checkmark")
                .resizable()
                .scaledToFit()
                .padding(.leading, -7)
                .foregroundColor(.gray)
        } else if message.content.readStatus == .read {
            Image(systemName: "checkmark")
                .resizable()
                .scaledToFit()
                .foregroundColor(.blue)

            Image(systemName: "checkmark")
                .resizable()
                .scaledToFit()
                .padding(.leading, -7)
                .foregroundColor(.blue)
        } else {

            Text("error")
        }
    }
    .height(11)
    .width(15)
    .yOffset(-1)
}

This is my DoubleCheckmark view:

struct DoubleCheckmark: View {
    var message: Message

    var body: some View {
        HStack(spacing: 0) {
            if message.content.readStatus == .loading {
                Text("loading")
            } else if message.content.readStatus == .sent {
                Image(systemName: "checkmark")
                    .resizable()
                    .scaledToFit()
                    .foregroundColor(message.content.readStatus == .read ? .blue : .gray)
            } else if message.content.readStatus == .received {
                Image(systemName: "checkmark")
                    .resizable()
                    .scaledToFit()
                    .foregroundColor( .gray)

                Image(systemName: "checkmark")
                    .resizable()
                    .scaledToFit()
                    .padding(.leading, -7)
                    .foregroundColor(.gray)
            } else if message.content.readStatus == .read {
                Image(systemName: "checkmark")
                    .resizable()
                    .scaledToFit()
                    .foregroundColor(.blue)

                Image(systemName: "checkmark")
                    .resizable()
                    .scaledToFit()
                    .padding(.leading, -7)
                    .foregroundColor(.blue)
            } else {

                Text("error")
            }
        }
        .height(11)
        .width(15)
        .yOffset(-1)
    }
}

This is my Message class:

public class Message: NSObject, Codable, NSCoding {
    public var content: MessageContent
    public var fromUser: User
    public var toUser: User

    public init(content: MessageContent, fromUser: User, toUser: User) {
        self.content = content
        self.fromUser = fromUser
        self.toUser = toUser
    }

    enum Keys: String {
        case content, fromUser, toUser
    }

    public func encode(with aCoder: NSCoder) {

        aCoder.encode(content, forKey: Keys.content.rawValue)
        aCoder.encode(fromUser, forKey: Keys.fromUser.rawValue)
        aCoder.encode(toUser, forKey: Keys.toUser.rawValue)
    }

    public required convenience init?(coder aDecoder: NSCoder) {

        let content = aDecoder.decodeObject(forKey: Keys.content.rawValue) as! MessageContent
        let fromUser = aDecoder.decodeObject(forKey: Keys.fromUser.rawValue) as! User
        let toUser = aDecoder.decodeObject(forKey: Keys.toUser.rawValue) as! User

        self.init(content: content, fromUser: fromUser, toUser: toUser)
    }

    public override func isEqual(_ object: Any?) -> Bool {
        if let object = object as? Message {
            return self.content.uniqueIdentifier == object.content.uniqueIdentifier
        } else {
            return false
        }
    }
}

Upvotes: 1

Views: 291

Answers (3)

Vitaliy P.
Vitaliy P.

Reputation: 1

It doesn’t update because Message is not ObservableObject. If you pass a class instance you need to add @ObservedObject to the view property. Otherwise, SwiftUI doesn’t know that there was a change. Also, the class must either mark the properties that changing with @Published wrapper, or manually call objectWillChange.send()

Upvotes: 0

SwiftiSwift
SwiftiSwift

Reputation: 8687

I changed my Message class to a struct and used binary data for the datatype in CoreData instead of Transformable which needs the model to conform to NSCoding

Upvotes: 0

Asperi
Asperi

Reputation: 257493

If your Message is a class, then List most probably does not update rows for same messages due to equal references of message property. Try to conform your view to Equatable explicitly and override same in Message to make comparison deeply

struct DoubleCheckmark: View, Equatable {
    static func == (lhs: DoubleCheckmark, rhs: DoubleCheckmark) -> Bool {
        lhs.message == rhs.message
    }
    ...

and

class Message: ObservableObject, Equatable {
    static func == (lhs: Message, rhs: Message) -> Bool {
        // compare here all important properties
    }
    ...    

alter this it might be also needed to mentioned explicitly that your custom view is custom equatable

List(filteredMessages, id: \.content.uniqueIdentifier) { message in
     DoubleCheckmark(message: message).equatable() // try with & w/o
}

Upvotes: 1

Related Questions