Skip to content

Commit 96ec231

Browse files
authored
Fix channel list skipping some updates on iPad (#1059)
1 parent 1091c9f commit 96ec231

File tree

3 files changed

+58
-28
lines changed

3 files changed

+58
-28
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
88
- Expose `QuotedMessageViewContainer` [#1056](https://github.com/GetStream/stream-chat-swiftui/pull/1056)
99
- Add `QuotedMessageContentView` and `ViewFactory.makeQuotedMessageContentView()` [#1056](https://github.com/GetStream/stream-chat-swiftui/pull/1056)
1010
- Allow customizing the attachment size and avatar size of the quoted message view [#1056](https://github.com/GetStream/stream-chat-swiftui/pull/1056)
11+
### 🐞 Fixed
12+
- Fix channel list skipping some updates on iPad [#1059](https://github.com/GetStream/stream-chat-swiftui/pull/1059)
1113

1214
# [4.93.0](https://github.com/GetStream/stream-chat-swiftui/releases/tag/4.93.0)
1315
_November 18, 2025_

Sources/StreamChatSwiftUI/ChatChannelList/ChatChannelListViewModel.swift

Lines changed: 26 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -25,42 +25,40 @@ open class ChatChannelListViewModel: ObservableObject, ChatChannelListController
2525

2626
/// Used when screen is shown from a deeplink.
2727
private var selectedChannelId: String?
28-
29-
/// Temporarly holding changes while message list is shown.
30-
private var queuedChannelsChanges = LazyCachedMapCollection<ChatChannel>()
31-
28+
3229
private var timer: Timer?
3330

3431
/// Controls loading the channels.
3532
public private(set) var loadingNextChannels: Bool = false
3633

37-
/// Checks if the queued changes are completely applied.
38-
private var markDirty = false
39-
34+
/// True, if channel updates were skipped and are applied when selectedChannel is set to nil
35+
private var skippedChannelUpdates = false
36+
37+
/// True, if channel updates can be skipped for optimizing view refreshes while showing message list.
38+
///
39+
/// - Important: Only meant for stacked navigation view style.
40+
private var canSkipChannelUpdates: Bool {
41+
guard isIphone || !utils.messageListConfig.iPadSplitViewEnabled else { return false }
42+
guard selectedChannel != nil || !searchText.isEmpty else { return false }
43+
return true
44+
}
45+
4046
/// Index of the selected channel.
4147
private var selectedChannelIndex: Int?
4248

4349
/// When set, scrolls to the specified channel id (if it exists).
4450
@Published public var scrolledChannelId: String?
4551

4652
/// Published variables.
47-
@Published public var channels = LazyCachedMapCollection<ChatChannel>() {
48-
didSet {
49-
if !markDirty {
50-
queuedChannelsChanges = []
51-
} else {
52-
markDirty = false
53-
}
54-
}
55-
}
53+
@Published public var channels = LazyCachedMapCollection<ChatChannel>()
5654

5755
@Published public var selectedChannel: ChannelSelectionInfo? {
5856
willSet {
5957
hideTabBar = newValue != nil
6058
if selectedChannel != nil && newValue == nil {
6159
// pop happened, apply the queued changes.
62-
if !queuedChannelsChanges.isEmpty {
63-
channels = queuedChannelsChanges
60+
if skippedChannelUpdates {
61+
updateChannels()
6462
}
6563
}
6664
if newValue == nil {
@@ -317,8 +315,8 @@ open class ChatChannelListViewModel: ObservableObject, ChatChannelListController
317315
// MARK: - private
318316

319317
private func handleChannelListChanges(_ controller: ChatChannelListController) {
320-
if selectedChannel != nil || !searchText.isEmpty {
321-
queuedChannelsChanges = controller.channels
318+
if canSkipChannelUpdates {
319+
skippedChannelUpdates = true
322320
updateChannelsIfNeeded()
323321
} else {
324322
channels = controller.channels
@@ -537,15 +535,16 @@ open class ChatChannelListViewModel: ObservableObject, ChatChannelListController
537535
}
538536

539537
private func updateChannels() {
538+
skippedChannelUpdates = false
540539
channels = controller?.channels ?? LazyCachedMapCollection<ChatChannel>()
541540
}
542541

543542
private func handleChannelAppearance() {
544-
if !queuedChannelsChanges.isEmpty && selectedChannel == nil {
545-
channels = queuedChannelsChanges
546-
} else if !queuedChannelsChanges.isEmpty {
547-
handleQueuedChanges()
548-
} else if queuedChannelsChanges.isEmpty && selectedChannel != nil {
543+
if skippedChannelUpdates && selectedChannel == nil {
544+
updateChannels()
545+
} else if skippedChannelUpdates {
546+
updateSelectedChannelData()
547+
} else if !skippedChannelUpdates && selectedChannel != nil {
549548
if selectedChannel?.injectedChannelInfo == nil {
550549
selectedChannel?.injectedChannelInfo = InjectedChannelInfo(unreadCount: 0)
551550
}
@@ -562,10 +561,10 @@ open class ChatChannelListViewModel: ObservableObject, ChatChannelListController
562561
}
563562
}
564563

565-
private func handleQueuedChanges() {
564+
private func updateSelectedChannelData() {
566565
let selected = selectedChannel?.channel
567566
var index: Int?
568-
var temp = Array(queuedChannelsChanges)
567+
var temp = Array(controller?.channels ?? [])
569568
for i in 0..<temp.count {
570569
let current = temp[i]
571570
if current.cid == selected?.cid {
@@ -583,7 +582,6 @@ open class ChatChannelListViewModel: ObservableObject, ChatChannelListController
583582
if let index = index, let selected = selected {
584583
temp[index] = selected
585584
}
586-
markDirty = true
587585
channels = LazyCachedMapCollection(source: temp, map: { $0 })
588586
}
589587

StreamChatSwiftUITests/Tests/ChatChannelList/ChatChannelListViewModel_Tests.swift

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -532,6 +532,36 @@ class ChatChannelListViewModel_Tests: StreamChatTestCase {
532532
// Then
533533
XCTAssertNil(viewModel.selectedChannel, "selectedChannel should be cleared immediately when opening another channel")
534534
}
535+
536+
// MARK: - Optimized Channel List Updates
537+
538+
func test_channelListOptimizedUpdates_whenStackedViewAndSelection_thenUpdatesSkipped() {
539+
// Given
540+
let config = MessageListConfig(updateChannelsFromMessageList: false, iPadSplitViewEnabled: false)
541+
streamChat = StreamChat(chatClient: chatClient, utils: Utils(messageListConfig: config))
542+
let existingChannel = ChatChannel.mockDMChannel()
543+
let channelListController = makeChannelListController(channels: [existingChannel])
544+
545+
// When
546+
let viewModel = ChatChannelListViewModel(
547+
channelListController: channelListController,
548+
selectedChannelId: nil
549+
)
550+
viewModel.selectedChannel = .init(channel: existingChannel, message: nil)
551+
let insertedChannel = ChatChannel.mockDMChannel()
552+
channelListController.simulate(
553+
channels: [insertedChannel, existingChannel],
554+
changes: [.insert(insertedChannel, index: IndexPath(item: 0, section: 0))]
555+
)
556+
557+
// Then
558+
XCTAssertEqual(viewModel.channels.count, 1)
559+
560+
// When selection is popped, changes are applied
561+
viewModel.selectedChannel = nil
562+
563+
XCTAssertEqual(viewModel.channels.count, 2)
564+
}
535565

536566
// MARK: - private
537567

0 commit comments

Comments
 (0)