My View doesn't update when I change a property in an Array in the ObservableObject Class. I even declared a objectWillChange property and called it manually but the View just updated randomly or not how i want to. I don't understand this.
import Foundation
import SocketIO
import Combine
class SocketService: ObservableObject {
static let instance = SocketService()
let manager = SocketManager(socketURL: URL(RequestURL.base_url)!)
let socket: SocketIOClient
// let objectWillChange = ObservableObjectPublisher()
@Published var allMessages: [Message] = []
// {
// willSet {
// self.objectWillChange.send()
// }
// }
@Published var writtenUsers: [PreviewMessage] = []
// {
// willSet {
// self.objectWillChange.send()
// }
// }
init() {
socket = manager.defaultSocket
setSocketEvents()
}
// This method is called
func recieve_read_all_messages() {
socket.on("recieve-read-all-messages") { (data, ack) in
guard let arr = data as? [[String: Any]] else { return }
guard let userID = arr[0]["userID"] as? Int else {
print("no userID, \(#function), line: \(#line)"); return
}
// self.objectWillChange.send()
for msg in self.allMessages {
// Here im trying to change the property in the array
msg.content.readStatus = .read
}
}
}
Even when i directly change this property the view doesn't update
@EnvironmentObject private var socketService: SocketService
var body: some View {
VStack {
List(filteredMessages, id: \.content.uuid, rowContent: chatSpeechBubbleView)
sendView
}
}
private func chatSpeechBubbleView(forMessage message: Message) -> some View {
ChatSpeechBubble(message: message)
}
private var sendView: some View {
Button(action: sendMessage) {
SFSymbol(.paperplane_fill)
.fontSize(20)
.foregroundColor(.white)
.rotate(.degrees(45))
.padding(10)
.padding(.trailing, 5)
.backgroundColor(.blue)
.clipCircle()
}
.padding(.bottom, 2)
}
func sendMessage() {
for msg in socketService.allMessages {
msg.content.readStatus = .read
}
}
My other view where it should be updated:
struct DoubleCheckmark: View {
var messageContent: MessageContent
var body: some View {
HStack(spacing: 0) {
SFSymbol(.checkmark)
.resizable()
.scaledToFit()
SFSymbol(.checkmark)
.resizable()
.scaledToFit()
.padding(.leading, -9)
}
.height(13)
.foregroundColor(self.messageContent.readStatus == .read ? .blue : .gray)
}
}
struct ChatSpeechBubble: View {
// MARK: - init variables
var message: Message
// MARK: - normal variables
var ownSendMessage: Bool {
message.fromUser.id == UDService.shared.user.id
}
// MARK: - Body
var body: some View {
messageContent
}
private var messageContent: some View {
HStack(alignment: .bottom) {
if message.content.text != nil {
Text(message.content.text!)
.foregroundColor(.black)
}
if message.content.imageURL != nil {
Spacer(minLength: 0)
}
Text(message.content.timeHourMinute)
.font(.caption)
.foregroundColor(.gray)
if ownSendMessage {
DoubleCheckmark(messageContent: self.message.content)
}
}
}
}
struct, not classThe problem is that Message and its subtypes should be declared as a struct, rather than a class. Here's why.
Here is a basic example I made to demonstrate the difference:
class SomeObject: ObservableObject {
@Published var users = [User(username: "George", password: "password")]
}
struct ContentView: View {
@StateObject var object = SomeObject()
var body: some View {
Text("Hello world!")
let _ = print("Body")
}
}
let content = ContentView()
print(content.object)
content.object.users[0].password = "password123"
User as a structstruct User {
var username: String
var password: String
}
User as a classclass User {
var username: String
var password: String
init(username: String, password: String) {
self.username = username
self.password = password
}
}
struct |
class |
|---|---|
![]() |
![]() |
Have a look at the print logs at the bottom. With a struct, "Body" gets printed again, but why?
This is because classes are passed by reference, and structs are passed by value. This means that you can mutate the properties of the instance of a class, like so:
import SpriteKit
let sprite = SKSpriteNode()
sprite.position = CGPoint(x: 50, y: 50)
sprite.color = .red
If SKSpriteNode would have been a struct, with let properties, you would get an error for trying to change a let constant:
Cannot assign to property: '*' is a 'let' constant
So because Message is a class the instance isn't changing. But with a struct, the whole thing is changing. You need to change the whole thing (by using a struct) so @Published & StateObject/ObservableObject can observe the change.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With