我尝试在 SwiftUI 中构建聊天,目前每次收到消息时我都会打印它以证明套接字实际上正在工作,但是尽管打印结果是正确的,但视图不会更新并且我看不到消息出现就在视图上。
我认为他们在定义我的状态时可能是一个错误?
这是我的代码
import SwiftUI
import SocketIO
struct ChatMessage: Hashable {
var message: String
var MatchImg: String
var isMe: Bool
}
class ChatMessageList: ObservableObject {
@Published var list: [ChatMessage]
init(list: [ChatMessage]) {
self.list = list
}
func addItem(item: ChatMessage) {
list.append(item)
objectWillChange.send()
}
}
struct ChatRow : View {
var chatMessage: ChatMessage
var body: some View {
if !chatMessage.isMe {
HStack {
Group {
Image(chatMessage.MatchImg)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 32, height: 32)
.clipShape(Circle())
Text(chatMessage.message)
.bold()
.padding(10)
.foregroundColor(Color.white)
.background(.purple)
.cornerRadius(10)
}
}
}
else {
HStack {
Group {
Spacer()
Text(chatMessage.message)
.bold()
.foregroundColor(Color.white)
.padding(10)
.background(.red)
.cornerRadius(10)
}
}
}
}
}
struct MessageView: View {
@StateObject var messages: ChatMessageList = ChatMessageList(list: [
ChatMessage(message: "Hello world", MatchImg:"ye",isMe: true),
ChatMessage(message: "Hello world", MatchImg:"ye",isMe: false)
])
@State private var composedMessage: String = ""
let roomID: String
let userID: String
let otherUser: String
private var socket: SocketIOClient
private var manager: SocketManager
init(roomID: String, userID: String, otherUser: String) {
self.roomID = roomID
self.userID = userID
self.otherUser = otherUser
self.manager = SocketManager(socketURL: URL(string: "http://localhost:8080")!, config: [.log(false), .compress])
self.socket = self.manager.defaultSocket
self.socket.connect()
self.addHandlers()
}
func addHandlers() {
self.socket.on("message") { data, ack in
print(data[0])
}
self.socket.on("chat message") { data, ack in
let messageText = data[0]
print("got message", messageText)
let newMessage = ChatMessage(message: messageText as! String, MatchImg: "ye", isMe: false)
addMessage(message: newMessage)
}
}
func addMessage(message: ChatMessage) {
DispatchQueue.main.async {
messages.addItem(item: message)
}
}
var body: some View {
VStack(alignment: .center){
HStack {
Image("ye")
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 32, height: 32)
.clipShape(Circle())
Text("Kim Kardashiana")
.bold()
.padding(10)
.foregroundColor(Color.black)
.cornerRadius(10)
}
List{
// we have several messages so we use the For Loop
ForEach(messages.list, id: \.self) { msg in
ChatRow(chatMessage: msg)
.listRowSeparator(.hidden)
.listRowBackground(Color.clear)
}
}
.padding(0)
.listStyle(PlainListStyle())
.environment(\.defaultMinListRowHeight, 0)
HStack {
// this textField generates the value for the composedMessage @State var
TextField("Message...", text: $composedMessage).frame(minHeight: CGFloat(36))
.padding(.horizontal,12)
.overlay(
RoundedRectangle(cornerRadius: 15)
.stroke(Color.blue, lineWidth: 2)
)
// the button triggers the sendMessage() function written in the end of current View
Button("Send"){
Task {
self.socket.emit("message", roomID, composedMessage)
print(self.messages)
self.messages.addItem(item: ChatMessage(message: composedMessage, MatchImg: "ye", isMe: true))
print(self.messages)
self.composedMessage = ""
}
}
Button(action: {}){
Image("person.fill")
.foregroundColor(Color.black)
}
}.padding(20)
.task{
print(self.socket.status)
self.socket.emit("identify", userID)
self.socket.emit("subscribe", roomID, otherUser)
}
// that's the height of the HStack
}
}
}
struct MessageView_Previews: PreviewProvider {
static var previews: some View {
MessageView(roomID: "588b4e7a9bed4ece96ec6769fe6ade8a", userID: "74eae22fe84149af98f62e4376e7867a", otherUser: "ceb5e306b8ba4977bd5af5dfd77ca710")
}
}
尝试这种方法,使用
Identifiable
代替 ChatMessage
,这样 ForEach
元素根据需要是唯一的,并使用 ForEach(messages.list) { msg in ...}
。
测试代码示例:
struct ContentView: View {
var body: some View {
MessageView(roomID: "room-1", userID: "user-1", otherUser: "user-2")
}
}
struct ChatMessage: Identifiable, Hashable { // <-- here
let id = UUID() // <-- here
var message: String
var MatchImg: String
var isMe: Bool
}
class ChatMessageList: ObservableObject {
@Published var list: [ChatMessage]
init(list: [ChatMessage]) {
self.list = list
}
func addItem(item: ChatMessage) {
list.append(item)
//objectWillChange.send() // <-- here no need
}
}
struct ChatRow : View {
var chatMessage: ChatMessage
var body: some View {
if !chatMessage.isMe {
HStack {
Group {
Image(systemName: "globe") // <-- for testing
// Image(chatMessage.MatchImg)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 32, height: 32)
.clipShape(Circle())
Text(chatMessage.message)
.bold()
.padding(10)
.foregroundColor(Color.white)
.background(.purple)
.cornerRadius(10)
}
}
}
else {
HStack {
Group {
Spacer()
Text(chatMessage.message)
.bold()
.foregroundColor(Color.white)
.padding(10)
.background(.red)
.cornerRadius(10)
}
}
}
}
}
struct MessageView: View {
@StateObject var messages: ChatMessageList = ChatMessageList(list: [
ChatMessage(message: "Hello world1", MatchImg:"ye", isMe: true),
ChatMessage(message: "Hello world2", MatchImg:"ye", isMe: false)
])
@State private var composedMessage: String = ""
let roomID: String
let userID: String
let otherUser: String
// private var socket: SocketIOClient
// private var manager: SocketManager
init(roomID: String, userID: String, otherUser: String) {
self.roomID = roomID
self.userID = userID
self.otherUser = otherUser
// self.manager = SocketManager(socketURL: URL(string: "http://localhost:8080")!, config: [.log(false), .compress])
// self.socket = self.manager.defaultSocket
//
// self.socket.connect()
self.addHandlers() // <-- todo need to fix
}
func addHandlers() {
// self.socket.on("message") { data, ack in
// print(data[0])
// }
// self.socket.on("chat message") { data, ack in
// let messageText = data[0]
// print("got message", messageText)
// let newMessage = ChatMessage(message: messageText as! String, MatchImg: "ye", isMe: false)
// addMessage(message: newMessage)
// }
// for testing
let newMessage = ChatMessage(message: UUID().uuidString, MatchImg: "ye", isMe: false)
addMessage(message: newMessage)
}
func addMessage(message: ChatMessage) {
DispatchQueue.main.async {
messages.addItem(item: message)
}
}
var body: some View {
VStack(alignment: .center){
HStack {
Image(systemName: "house") // <--- for testing
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 32, height: 32)
.clipShape(Circle())
Text("Kim Kardashiana")
.bold()
.padding(10)
.foregroundColor(Color.black)
.cornerRadius(10)
}
List{
// we have several messages so we use the For Loop
ForEach(messages.list) { msg in // <--- here
ChatRow(chatMessage: msg)
.listRowSeparator(.hidden)
.listRowBackground(Color.clear)
}
}
.padding(0)
.listStyle(PlainListStyle())
.environment(\.defaultMinListRowHeight, 0)
HStack {
// this textField generates the value for the composedMessage @State var
TextField("Message...", text: $composedMessage).frame(minHeight: CGFloat(36))
.padding(.horizontal,12)
.overlay(
RoundedRectangle(cornerRadius: 15)
.stroke(Color.blue, lineWidth: 2)
)
// the button triggers the sendMessage() function written in the end of current View
Button("Send"){
Task {
// self.socket.emit("message", roomID, composedMessage)
// print(self.messages)
self.messages.addItem(item: ChatMessage(message: composedMessage, MatchImg: "yaya", isMe: true))
print(self.messages)
self.composedMessage = ""
}
}
Button(action: {}){
Image(systemName: "person.fill")
.foregroundColor(Color.black)
}
}.padding(20)
// for testing
// .task{
// print(self.socket.status)
// self.socket.emit("identify", userID)
//
// self.socket.emit("subscribe", roomID, otherUser)
// }
// that's the height of the HStack
}
}
}