1

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
        }
    }
}
5
  • Is content.uniqueIdentifier changed when message.content.readStatus changed? If not, then you isEqual does not work. Commented Mar 16, 2020 at 13:24
  • @Asperi of course its not changed as its a uniqueIdentifier Commented Mar 16, 2020 at 13:29
  • @Asperi do you have another idea? Commented Mar 16, 2020 at 14:45
  • I already wrote - your isEqual does not work, because for readStatus .loading and .read it returns true, but must false. Commented Mar 16, 2020 at 14:49
  • even if i explicitly return false it doesn't work @Asperi Commented Mar 16, 2020 at 18:41

3 Answers 3

1

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
}
Sign up to request clarification or add additional context in comments.

3 Comments

Message need to be a class as it needs to conform to NSCoding so i can use it with CoreData's Transformable
@jsbeginnerNodeJS, it is not a problem, then you need to override isEqual, because it is a NSObject.
it still doesn't work. I attached the Message class in the question above. Please have a look @Asperi
0

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

Comments

0

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()

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.