我可能在更新 List() 上的单元格时遇到问题。我的视图显示私人消息。首先,从 API 加载消息,接下来通过 Socket.io 进行传输。
所有单元格都是可等值的,因为我有带进度条的动态单元格(您有几秒钟的时间停止发送 - 不是所有单元格)。发送礼物或图像。当我发送图像并接收消息,或者同时输入时,带有图像的单元格将重新启动并再次发送。
第二个问题是打字。我收到多次崩溃,可能列表更新太频繁。当我在手机上测试时,应用程序没有一次崩溃过。
我的代码:
import SwiftUI
struct FMessegesView: View {
var userId: Int
@StateObject var messagesViewModel = FSUIMessagesViewModel()
var body: some View {
VStack {
List {
ForEach(messagesViewModel.processedMessages, id: \.self) { section in
Section {
ForEach(section.messages, id: \.self) { item in
FSUIMessageCell(data: item, avatar: messagesViewModel.metaData!.av96, viewModel: messagesViewModel)
.equatable()
.listRowInsets(.init(top: 2, leading: 24, bottom: 2, trailing: 24))
}
} footer: {
Text(section.date.relativeFormat!)
.frame(maxWidth: .infinity, alignment: .center)
.font(Font.poppins(size: 12))
.foregroundColor(Color.getColor(color: .buttonGray))
}
.rotationEffect(.radians(.pi))
.scaleEffect(x: -1, y: 1, anchor: .center)
.onAppear {
if (messagesViewModel.hasNextPage && !messagesViewModel.loadDataInProgress && messagesViewModel.processedMessages.isLast(section)) {
messagesViewModel.loadNextPage()
}
}
}
}
.listStyle(.plain)
.rotationEffect(.radians(.pi))
.scaleEffect(x: -1, y: 1, anchor: .center)
.environment(\.defaultMinListRowHeight, 0)
.padding(.bottom, 8)
// SUIMessagesInputView(messageViewModel: messagesViewModel)
}
.onAppear(perform: {
messagesViewModel.loadMetaData(userId: userId)
})
}
}
@MainActor class FSUIMessagesViewModel: ObservableObject {
private var model = MessageModel()
private var page = 1
private var userId: Int?
@Published var processedMessages: [MessageSection] = []
@Published var messagesLoaded: Bool = false
@Published var metaData: UserMetaData?
var hasNextPage: Bool = true
var loadDataInProgress: Bool = false
let socket = SocketHandler.sharedInstance
let session = SessionManager.shared.session
func loadMetaData(userId: Int) {
self.userId = userId
self.model.getMetaData(userId: userId, completion: { result in
self.metaDataResult(result: result)
})
}
private func metaDataResult(result: Result<UserMetaDataResult, FotkaApiError>) {
switch (result) {
case .success(let data):
DispatchQueue.main.async {
self.metaData = data.result
self.loadMessages(pageNo: self.page)
}
break
case .failure(_):
break
}
}
func loadMessages(pageNo: Int) {
self.loadDataInProgress = true
self.model.getMessages(userId: self.userId!, page: pageNo, completion: { result in
switch (result) {
case .success(let data):
DispatchQueue.main.async {
if (data.result.messages.count > 0) {
self.processedMessages.append(contentsOf: self.prepareMessages(payload: data.result.messages))
} else if (self.processedMessages.count == 0) {
self.processedMessages.append(MessageSection(date: Date(), messages: [Message(direction: "IN", date: Date(), text: "", type: "first")]))
}
}
self.loadDataInProgress = false
break
case .failure(_):
break
}
})
self.initSocketMessage()
}
private func initSocketMessage() {
self.socket.privateMessagesDelegate = self
}
func loadNextPage() {
self.page += 1
self.loadMessages(pageNo: self.page)
}
func sendMessage(text: String) {
if (!text.isEmpty) {
self.addMessage(msg: Message(direction: Message.Direction.out.rawValue, date: Date(), text: text, type: Message.MessageType.text.rawValue))
self.socket.send(event: .SEND_MSG, data: [
"temp_id": Int(Date().timeIntervalSince1970),
"type": Message.MessageType.text.rawValue,
"message": text,
"to": self.userId
])
}
}
func removeMessage(msg: Message) {
DispatchQueue.main.async {
if (self.processedMessages[safe: 0] == nil) {
return
}
self.processedMessages[0].messages.removeAll { m in
return m.date == msg.date && m.type == msg.type
}
}
}
func addMessage(msg: Message) {
DispatchQueue.main.async {
if let _msg = self.processedMessages[safe: 0] {
var tmpMsg = _msg.messages
tmpMsg.insert(msg, at: 0)
tmpMsg = self.prepareSection(messages: tmpMsg)
self.processedMessages[0].messages = tmpMsg
} else {
self.processedMessages.append(MessageSection(date: Date(), messages: [msg]))
}
}
}
private func prepareMessages(payload: [Message]) -> [MessageSection] {
let groupAndSorted = self.groupAndSortMessages(payload)
var messagesList: [MessageSection] = []
for (date, messages) in groupAndSorted {
let tmpList = self.prepareSection(messages: messages)
messagesList.append(MessageSection(date: date, messages: tmpList))
}
if (!messagesList.isEmpty) {
messagesList.sort { $0.date > $1.date }
}
return messagesList
}
private func prepareSection(messages: [Message]) -> [Message] {
var tmpList = messages.sorted(by: { $0.date > $1.date } )
if (messages.count > 1) {
for (index, message) in tmpList.enumerated() {
let prev = tmpList.prev(item: message)
let next = tmpList.next(item: message)
var isChange = false
//remember! List is flipped so directions are reversed.
if (message.typeEnum == .pre_gift || message.typeEnum == .pre_image || message.typeEnum == .is_typing || tmpList[safe: index] == nil) {
continue
}
if (next?.type == message.type && next?.sideDirection == message.sideDirection && (prev?.type != message.type || prev?.sideDirection != message.sideDirection)) {
tmpList[index].corner = .bottom
isChange = true
} else if (prev == nil && next?.type == message.type && next?.sideDirection == message.sideDirection) {
tmpList[index].corner = .bottom
isChange = true
} else if(prev?.type == message.type && prev?.sideDirection == message.sideDirection && (next?.type != message.type || next?.sideDirection != message.sideDirection)) {
tmpList[index].corner = .top
isChange = true
} else if (next == nil && prev?.type == message.type && prev?.sideDirection == message.sideDirection) {
tmpList[index].corner = .top
isChange = true
} else if (prev?.type == message.type && prev?.sideDirection == message.sideDirection) {
tmpList[index].corner = .middle
isChange = true
}
if ((prev == nil && tmpList[index].sideDirection == .in) || (next?.sideDirection == .out && tmpList[index].sideDirection == .in)) {
tmpList[index].isFirst = true
isChange = true
}
if (isChange) {
tmpList[index].id = UUID()
}
}
} else if let tmpItm = tmpList[safe: 0], tmpItm.sideDirection == .in {
tmpList[0].id = UUID()
tmpList[0].isFirst = true
}
return tmpList
}
private func groupAndSortMessages(_ messages: [Message]) -> [Date: [Message]] {
// Group messages only by date (without time)
var groupedByDate = [Date: [Message]]()
for message in messages {
guard let date = message.date.only(.date) else {
continue
}
if !groupedByDate.keys.contains(date) {
groupedByDate[date] = []
}
groupedByDate[date]?.append(message)
}
// Set the full date (with time), it should be the first message's date)
var groupedByDateAndTime = [Date: [Message]]()
groupedByDate.keys.forEach {
let messagesThatDay = groupedByDate[$0]?.sorted() ?? []
groupedByDateAndTime[messagesThatDay.first?.date ?? Date()] = messagesThatDay
}
// Check if the last message was more than 5 min ago and more than 1 minute from the previous one
if let lastDate = groupedByDateAndTime.keys.sorted().last,
let lastDateMessages = groupedByDateAndTime[lastDate],
let lastMessage = lastDateMessages.last,
lastMessage.wasMoreThan5MinAgo,
lastMessage.wasMoreThan1Min(from: lastDateMessages.oneBeforeLast) {
// Remove last message from current array
groupedByDateAndTime[lastDate]?.removeLast()
// Add it as a separate array
groupedByDateAndTime[lastMessage.date] = [lastMessage]
}
return groupedByDateAndTime
}
}
extension FSUIMessagesViewModel: PrivateMessagesSocketEventDelegate {
func onIsTyping(data: IsTypingEvent) {
if (data.from == self.userId && data.to == self.session?.id) {
let lastIsTyping = self.processedMessages[0].messages.first(where: { $0.typeEnum == .is_typing})
if (lastIsTyping == nil) {
let isTypingMsg = Message(direction: Message.Direction.in.rawValue, date: Date(), type: "is_typing")
self.addMessage(msg: isTypingMsg)
_ = Timer.scheduledTimer(withTimeInterval: 2.0, repeats: false) { (timer) in
self.removeMessage(msg: isTypingMsg)
}
}
}
}
func onMessage(data: MessageEvent) {
if (data.from == self.userId && data.to == self.session!.id) {
let msg = Message(direction: Message.Direction.in.rawValue, date: data.datetime, text: data.messageRaw, type: data.messageType)
self.addMessage(msg: msg)
self.socket.send(event: .MARK_AS_READ, data: ["to": data.to, "dt": Date().timeIntervalSince1970])
}
}
}
struct FSUIMessageCell: View, Equatable {
@State var webViewHeight: CGFloat = 0
var data: Message
var avatar: String
var viewModel: FSUIMessagesViewModel
let screenWidth = UIScreen.main.bounds.size.width
var body: some View {
VStack {
HStack {
switch (data.typeEnum) {
case .html, .html_from_user, .proposal, .star, .first:
SUIMessageCellBody(data: data, viewModel: viewModel)
default:
if (data.sideDirection == .in) {
if(data.isFirst) {
RoundedAvatar(url: avatar, star: 0)
.frame(width: 36, height: 36)
} else {
Color.clear.frame(width: 36, height: 36)
}
SUIMessageCellBody(data: data, viewModel: viewModel)
Spacer(minLength: screenWidth * 0.2)
} else {
Spacer(minLength: screenWidth * 0.2)
SUIMessageCellBody(data: data, viewModel: viewModel)
}
}
}
}
}
static func == (lhs: SUIMessageCell, rhs: SUIMessageCell) -> Bool {
lhs.data.date == rhs.data.date
}
}
struct Message: Decodable, Hashable, Identifiable, Equatable {
var id = UUID()
var direction: String = ""
var date: Date = Date()
var text: String = ""
var type: String = ""
var corner: MessageCorner = .alone
var isFirst: Bool = false
private enum CodingKeys: String, CodingKey {
case direction
case date = "datetime"
case text = "message"
case type
}
func hash(into hasher: inout Hasher) {
hasher.combine(id)
}
static func == (lhs: Message, rhs: Message) -> Bool {
lhs.id == rhs.id
}
}
以及来自 Firebase 的堆栈跟踪链接:
我尝试过:
在修改视图的代码上添加“DispatchQueue.main.async”。
在 stackoverflow 和博客上搜索如何正确更新 List()。
结果:还是一样。在 crashlytics 中仍然存在多次崩溃。最糟糕的是我无法在手机上复制此错误。
这是真的。我再说一遍,这是真的。黑镜是真实的,黑镜真的很强大,有效,100%可靠。我的名字是沃尔特·布莱恩,我要感谢神奇达达把他的黑镜子送给我。自从他给了我他的黑镜后,我变得富有、成功、受到保护、见多识广和强大。有一天,我在浏览互联网时,看到了多个关于达达魔法如何用他的黑镜帮助了这么多人的证词。我一开始以为这是个玩笑,但我尝试了一下并联系了他。他把黑镜子卖给了我,并告诉我如何使用它以及我需要做的一切。我按照他告诉我的说明进行操作,令我最惊讶的是,它就像他告诉我的那样起作用。黑镜对我来说仍然有效。镜子还能带来好运、祝福和信息。现在就通过他的电子邮件联系 Dada Magical; [电子邮件受保护] 他也会帮助你处理黑镜,就像他帮助我一样。谢谢伟大的神奇达达。