diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b04eb095e..e76559748b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### 🐞 Fixed - Unread messages divider did not appear in the message list when marking messages as unread [#3444](https://github.com/GetStream/stream-chat-swift/pull/3444) - Fix UI glitch in thread parent message when sending a message and scrolling [#3446](https://github.com/GetStream/stream-chat-swift/pull/3446) +### ⚡ Performance +- Improve performance of presenting `ChatChannelVC` [#3448](https://github.com/GetStream/stream-chat-swift/pull/3448) # [4.64.0](https://github.com/GetStream/stream-chat-swift/releases/tag/4.64.0) _October 02, 2024_ diff --git a/Scripts/updateDependency.sh b/Scripts/updateDependency.sh index e659c696d6..647c891793 100755 --- a/Scripts/updateDependency.sh +++ b/Scripts/updateDependency.sh @@ -73,6 +73,7 @@ fi if [[ $dependency_directory == *"SwiftyMarkdown"* ]]; then # We currently use customized version of SwiftyMarkdown + git restore $output_directory/SwiftyMarkdown/PerformanceLog.swift || true git restore $output_directory/SwiftyMarkdown/SwiftyLineProcessor.swift || true git restore $output_directory/SwiftyMarkdown/SwiftyTokeniser.swift || true fi diff --git a/Sources/StreamChatUI/ChatMessageList/ChatMessage/ChatMessageContentView.swift b/Sources/StreamChatUI/ChatMessageList/ChatMessage/ChatMessageContentView.swift index 106b6cc8da..aa37cdb0e9 100644 --- a/Sources/StreamChatUI/ChatMessageList/ChatMessage/ChatMessageContentView.swift +++ b/Sources/StreamChatUI/ChatMessageList/ChatMessage/ChatMessageContentView.swift @@ -157,6 +157,10 @@ open class ChatMessageContentView: _View, ThemeProvider, UITextViewDelegate { open var editedLabelSeparator: String { " • " } + + /// Skip content update when tintColorDidChange is called but content was already updated for that color + /// Unnecessary updates can get expensive due to text updates. + private var previousContentUpdateTintColor: UIColor? // MARK: - Content views @@ -590,6 +594,7 @@ open class ChatMessageContentView: _View, ThemeProvider, UITextViewDelegate { super.updateContent() defer { attachmentViewInjector?.contentViewDidUpdateContent() + previousContentUpdateTintColor = tintColor setNeedsLayout() } @@ -747,7 +752,7 @@ open class ChatMessageContentView: _View, ThemeProvider, UITextViewDelegate { override open func tintColorDidChange() { super.tintColorDidChange() - + guard previousContentUpdateTintColor != tintColor else { return } guard UIApplication.shared.applicationState == .active else { return } // We need to update the content and manually apply the updated `tintColor` // to the subviews which don't listen for `tintColor` updates. diff --git a/Sources/StreamChatUI/ChatMessageList/ChatMessageListVC.swift b/Sources/StreamChatUI/ChatMessageList/ChatMessageListVC.swift index d40bb06562..6196f6f874 100644 --- a/Sources/StreamChatUI/ChatMessageList/ChatMessageListVC.swift +++ b/Sources/StreamChatUI/ChatMessageList/ChatMessageListVC.swift @@ -271,7 +271,6 @@ open class ChatMessageListVC: _ViewController, override open func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { super.traitCollectionDidChange(previousTraitCollection) - view.layoutIfNeeded() listView.adjustContentInsetToPositionMessagesAtTheTop() } @@ -1304,10 +1303,14 @@ private extension ChatMessageListVC { } UIView.performWithoutAnimation { - self?.scrollToBottomIfNeeded(with: changes, newestChange: newestChange) - self?.reloadMovedMessage(newestChange: newestChange) - self?.reloadPreviousMessagesForVisibleRemoves(with: changes) - self?.reloadPreviousMessageWhenInsertingNewMessage() + guard let self else { return } + self.scrollToBottomIfNeeded(with: changes, newestChange: newestChange) + var additionalChanges = Set() + additionalChanges.formUnion(self.reloadMovedMessage(newestChange: newestChange)) + additionalChanges.formUnion(self.reloadPreviousMessagesForVisibleRemoves(with: changes)) + additionalChanges.formUnion(self.reloadPreviousMessageWhenInsertingNewMessage(with: changes)) + guard !additionalChanges.isEmpty else { return } + self.listView.reloadRows(at: additionalChanges.sorted(), with: .none) } self?.scrollPendingMessageIfNeeded() @@ -1356,24 +1359,21 @@ private extension ChatMessageListVC { // If we are inserting messages at the bottom, update the previous cell // to hide the timestamp of the previous message if needed. - func reloadPreviousMessageWhenInsertingNewMessage() { - guard isFirstPageLoaded else { return } - if listView.isLastCellFullyVisible && listView.newMessagesSnapshot.count > 1 { - let previousMessageIndexPath = IndexPath(item: 1, section: 0) - listView.reloadRows(at: [previousMessageIndexPath], with: .none) - } + private func reloadPreviousMessageWhenInsertingNewMessage(with changes: [ListChange]) -> [IndexPath] { + guard isFirstPageLoaded else { return [] } + guard listView.isLastCellFullyVisible && listView.newMessagesSnapshot.count > 1 else { return [] } + guard !changes.contains(where: { $0.indexPath.item == 1 && $0.isInsertion }) else { return [] } + return [IndexPath(item: 1, section: 0)] } // When there are deletions, we should update the previous message, so that we add the // avatar image is rendered back and the timestamp too. Since we have an inverted list, the previous // message has the same index of the deleted message after the deletion has been executed. - func reloadPreviousMessagesForVisibleRemoves(with changes: [ListChange]) { + private func reloadPreviousMessagesForVisibleRemoves(with changes: [ListChange]) -> [IndexPath] { let visibleRemoves = changes.filter { $0.isRemove && isMessageVisible(at: $0.indexPath) } - visibleRemoves.forEach { - listView.reloadRows(at: [$0.indexPath], with: .none) - } + return visibleRemoves.map(\.indexPath) } // Scroll to the bottom if the new message was sent by @@ -1391,10 +1391,7 @@ private extension ChatMessageListVC { // When a Giphy moves to the bottom, we need to also trigger a reload // Since a move doesn't trigger a reload of the cell. - func reloadMovedMessage(newestChange: ListChange?) { - if newestChange?.isMove == true { - let movedIndexPath = IndexPath(item: 0, section: 0) - listView.reloadRows(at: [movedIndexPath], with: .none) - } + private func reloadMovedMessage(newestChange: ListChange?) -> [IndexPath] { + newestChange?.isMove == true ? [IndexPath(item: 0, section: 0)] : [] } } diff --git a/Sources/StreamChatUI/StreamSwiftyMarkdown/SwiftyMarkdown/PerfomanceLog.swift b/Sources/StreamChatUI/StreamSwiftyMarkdown/SwiftyMarkdown/PerfomanceLog.swift index b56350b153..d9f7ab5246 100644 --- a/Sources/StreamChatUI/StreamSwiftyMarkdown/SwiftyMarkdown/PerfomanceLog.swift +++ b/Sources/StreamChatUI/StreamSwiftyMarkdown/SwiftyMarkdown/PerfomanceLog.swift @@ -16,7 +16,7 @@ class PerformanceLog { init( with environmentVariableName : String, identifier : String, log : OSLog ) { self.log = log - self.enablePerfomanceLog = (ProcessInfo.processInfo.environment[environmentVariableName] != nil) + self.enablePerfomanceLog = false self.identifier = identifier } diff --git a/Tests/StreamChatUITests/SnapshotTests/ChatChannel/ChatChannelVC_Tests.swift b/Tests/StreamChatUITests/SnapshotTests/ChatChannel/ChatChannelVC_Tests.swift index 504a30a785..4e047c3117 100644 --- a/Tests/StreamChatUITests/SnapshotTests/ChatChannel/ChatChannelVC_Tests.swift +++ b/Tests/StreamChatUITests/SnapshotTests/ChatChannel/ChatChannelVC_Tests.swift @@ -652,6 +652,10 @@ final class ChatChannelVC_Tests: XCTestCase { vc.components.isMessageEditedLabelEnabled = true + // Also update the default because in snapshot tests, message cells are created before they are in the responder chain + defer { Components.default.isMessageEditedLabelEnabled = false } + Components.default.isMessageEditedLabelEnabled = true + AssertSnapshot(vc, variants: [.defaultLight]) } diff --git a/Tests/StreamChatUITests/SnapshotTests/ChatMessageList/ChatMessageListVC_Tests.swift b/Tests/StreamChatUITests/SnapshotTests/ChatMessageList/ChatMessageListVC_Tests.swift index 7af4d5bd26..81cf3155e7 100644 --- a/Tests/StreamChatUITests/SnapshotTests/ChatMessageList/ChatMessageListVC_Tests.swift +++ b/Tests/StreamChatUITests/SnapshotTests/ChatMessageList/ChatMessageListVC_Tests.swift @@ -506,7 +506,7 @@ final class ChatMessageListVC_Tests: XCTestCase { ]) mockedListView.updateMessagesCompletion?() - XCTAssertEqual(mockedListView.reloadRowsCallCount, 2) + XCTAssertEqual(mockedListView.reloadRowsCallCount, 1) XCTAssertEqual(mockedListView.reloadRowsCalledWith, [.init(item: 0, section: 0), .init(item: 1, section: 0)]) }