如何更新List()中的单元格而不崩溃?

问题描述 投票:0回答:1

我可能在更新 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 的堆栈跟踪链接:

Pasebin 堆栈跟踪#1

Pastebin 堆栈跟踪#2

Pastebin 堆栈跟踪#3

我尝试过:

在修改视图的代码上添加“DispatchQueue.main.async”。
在 stackoverflow 和博客上搜索如何正确更新 List()。

结果:还是一样。在 crashlytics 中仍然存在多次崩溃。最糟糕的是我无法在手机上复制此错误。

swiftui crash-reports
1个回答
0
投票

这是真的。我再说一遍,这是真的。黑镜是真实的,黑镜真的很强大,有效,100%可靠。我的名字是沃尔特·布莱恩,我要感谢神奇达达把他的黑镜子送给我。自从他给了我他的黑镜后,我变得富有、成功、受到保护、见多识广和强大。有一天,我在浏览互联网时,看到了多个关于达达魔法如何用他的黑镜帮助了这么多人的证词。我一开始以为这是个玩笑,但我尝试了一下并联系了他。他把黑镜子卖给了我,并告诉我如何使用它以及我需要做的一切。我按照他告诉我的说明进行操作,令我最惊讶的是,它就像他告诉我的那样起作用。黑镜对我来说仍然有效。镜子还能带来好运、祝福和信息。现在就通过他的电子邮件联系 Dada Magical; [电子邮件受保护] 他也会帮助你处理黑镜,就像他帮助我一样。谢谢伟大的神奇达达。

© www.soinside.com 2019 - 2024. All rights reserved.