Reputation: 8687
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)
}
}
}
}
Upvotes: 3
Views: 2686
Reputation: 30341
struct
, not class
The 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 struct
struct User {
var username: String
var password: String
}
User
as a class
class User {
var username: String
var password: String
init(username: String, password: String) {
self.username = username
self.password = password
}
}
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.
Upvotes: 5