Skip to content

Commit 1b2a1db

Browse files
authored
Fix message list not scrolling to newest message with long text (#958)
* Fix message list not scrolling to newest message with long text * Update CHANGELOG.md * Fix not scrolling to bottom when sending a new message * Improve name of the variable * Fix tests
1 parent 5febf80 commit 1b2a1db

File tree

4 files changed

+51
-31
lines changed

4 files changed

+51
-31
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
1111
- Fix swipe to reply enabled when quoting a message is disabled [#977](https://github.com/GetStream/stream-chat-swiftui/pull/957)
1212
- Fix composer not showing images in the composer when editing signed attachments [#956](https://github.com/GetStream/stream-chat-swiftui/pull/956)
1313
- Fix replacing an image while editing a message not showing the new image in the message list [#956](https://github.com/GetStream/stream-chat-swiftui/pull/956)
14+
- Improve precision when scrolling to the newest message with long text #958
1415

1516
# [4.88.0](https://github.com/GetStream/stream-chat-swiftui/releases/tag/4.88.0)
1617
_September 10, 2025_

Sources/StreamChatSwiftUI/ChatChannel/ChatChannelViewModel.swift

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
3636
private var readsString = ""
3737
private var canMarkRead = false
3838
private var hasSetInitialCanMarkRead = false
39-
39+
private var currentUserSentNewMessage = false
40+
4041
private let messageListDateOverlay: DateFormatter = DateFormatter.messageListDateOverlay
4142

4243
private lazy var messagesDateFormatter = utils.dateFormatter
@@ -266,10 +267,7 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
266267

267268
/// The user tapped on the message sent button.
268269
public func messageSentTapped() {
269-
// only scroll if the message is not being edited
270-
if editedMessage == nil {
271-
scrollToLastMessage()
272-
}
270+
currentUserSentNewMessage = true
273271
}
274272

275273
public func jumpToMessage(messageId: String) -> Bool {
@@ -448,6 +446,9 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
448446

449447
if !showScrollToLatestButton && scrolledId == nil && !loadingNextMessages {
450448
updateScrolledIdToNewestMessage()
449+
} else if changes.first?.isInsertion == true && currentUserSentNewMessage {
450+
updateScrolledIdToNewestMessage()
451+
currentUserSentNewMessage = false
451452
}
452453
}
453454

Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageListView.swift

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -251,13 +251,19 @@ public struct MessageListView<Factory: ViewFactory>: View, KeyboardReadable {
251251
.frame(maxWidth: .infinity)
252252
.clipped()
253253
.onChange(of: scrolledId) { scrolledId in
254-
if let scrolledId = scrolledId {
255-
let shouldJump = onJumpToMessage?(scrolledId) ?? false
256-
if !shouldJump {
257-
return
258-
}
259-
withAnimation {
260-
scrollView.scrollTo(scrolledId, anchor: messageListConfig.scrollingAnchor)
254+
DispatchQueue.main.async {
255+
if let scrolledId = scrolledId {
256+
let shouldJump = onJumpToMessage?(scrolledId) ?? false
257+
if !shouldJump {
258+
return
259+
}
260+
withAnimation {
261+
if messages.first?.id == scrolledId {
262+
scrollView.scrollTo(scrolledId, anchor: .top)
263+
} else {
264+
scrollView.scrollTo(scrolledId, anchor: messageListConfig.scrollingAnchor)
265+
}
266+
}
261267
}
262268
}
263269
}

StreamChatSwiftUITests/Tests/ChatChannel/ChatChannelViewModel_Tests.swift

Lines changed: 31 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -94,25 +94,6 @@ class ChatChannelViewModel_Tests: StreamChatTestCase {
9494
XCTAssert(viewModel.scrolledId!.contains(messageId))
9595
}
9696

97-
func test_chatChannelVM_messageSentTapped() {
98-
// Given
99-
let messageId: String = .unique
100-
let message = ChatMessage.mock(
101-
id: messageId,
102-
cid: .unique,
103-
text: "Test message",
104-
author: ChatUser.mock(id: chatClient.currentUserId!)
105-
)
106-
let channelController = makeChannelController(messages: [message])
107-
let viewModel = ChatChannelViewModel(channelController: channelController)
108-
109-
// When
110-
viewModel.messageSentTapped()
111-
112-
// Then
113-
XCTAssert(viewModel.scrolledId!.contains(messageId))
114-
}
115-
11697
func test_chatChannelVM_messageSentTapped_whenEditingMessage_shouldNotScroll() {
11798
// Given
11899
let messageId: String = .unique
@@ -209,6 +190,37 @@ class ChatChannelViewModel_Tests: StreamChatTestCase {
209190
XCTAssert(initialListId == newListId)
210191
}
211192

193+
func test_chatChannelVM_newMessageSentScrollsToNewestMessage() {
194+
// Given
195+
var messages = [ChatMessage]()
196+
for i in 0..<5 {
197+
let message = ChatMessage.mock(
198+
id: .unique,
199+
cid: .unique,
200+
text: "Test Message \(i)",
201+
author: ChatUser.mock(id: chatClient.currentUserId!)
202+
)
203+
messages.append(message)
204+
}
205+
let channelController = makeChannelController(messages: messages)
206+
let viewModel = ChatChannelViewModel(channelController: channelController)
207+
208+
// When
209+
viewModel.showScrollToLatestButton = true
210+
viewModel.messageSentTapped()
211+
viewModel.dataSource(
212+
channelDataSource: ChatChannelDataSource(controller: channelController),
213+
didUpdateMessages: LazyCachedMapCollection(elements: messages),
214+
changes: [
215+
.insert(messages[0], index: .init(item: 0, section: 0)),
216+
.update(messages[1], index: .init(item: 1, section: 0))
217+
]
218+
)
219+
220+
// Then
221+
XCTAssertEqual(viewModel.scrolledId, messages[0].id)
222+
}
223+
212224
func test_chatChannelVM_messageThread() {
213225
// Given
214226
let channelController = makeChannelController()

0 commit comments

Comments
 (0)