Description
performScrollTo crashes with NSInternalInconsistencyException when a scroll to the oldest message is requested while the table has no sections.
Sources/ExyteChat/Views/UIList.swift (current main, unchanged since at least 3.1.7):
case .oldestMessage:
let lastSection = max(tableView.numberOfSections - 1, 0)
let lastRow = max(tableView.numberOfRows(inSection: lastSection) - 1, 0)
guard lastRow >= 0 else { return }
…
With zero sections, max(0 - 1, 0) evaluates to 0, so the next line asks UIKit for the row count of a section that does not exist:
NSInternalInconsistencyException: Requested the number of rows for section (0) which is out of bounds.
-[UITableViewRowData numberOfRowsInSection:]
The guard lastRow >= 0 cannot help: max(_, 0) already forced the value non-negative, so the guard is always true, and it runs after the offending call regardless.
Why this is reachable in normal use
ChatView.swift arms this scroll from a status bar tap:
.onStatusBarTap {
self.pendingScrollTo = ScrollToParams(.oldestMessage)
}
View+StatusBar.swift implements that by swizzling UIStatusBarManager.handleTapAction: and posting a global NotificationCenter notification. The subscription is created in onAppear and released in onDisappear.
onDisappear does not fire for a view that stays mounted while off screen, which is the normal case for a ChatView inside a TabView tab or lower in a NavigationStack. The observer therefore survives navigation, and a status bar tap performed on a completely different screen still arms the scroll on that chat. If its table is empty at that moment (empty conversation, or messages not loaded yet), the app crashes.
The scroll runs inside UIView.animate wrapped in withCheckedContinuation (UIList.swift:128), so the exception surfaces from inside the animation block:
-[UITableViewRowData numberOfRowsInSection:]
…
+[UIView(UIViewAnimationWithBlocks) animateWithDuration:animations:completion:]
closure in withCheckedContinuation<T>
withCheckedContinuation<T>
Steps to reproduce
- Present a
ChatView for a conversation with no messages, or one whose messages load asynchronously.
- Navigate away while keeping the view mounted (switch tab, or push another screen onto the stack).
- Tap the status bar.
Observed on iOS 27.0, iPhone 13 Pro, ExyteChat 3.1.7. The same code path is present on main and in 3.2.7.
Suggested fix
Guard on the section count before touching the table, and drop both clamps so the row guard becomes meaningful for an empty section:
case .oldestMessage:
guard tableView.numberOfSections > 0 else { return }
let lastSection = tableView.numberOfSections - 1
let lastRow = tableView.numberOfRows(inSection: lastSection) - 1
guard lastRow >= 0 else { return }
Happy to open a PR with this.
Description
performScrollTocrashes withNSInternalInconsistencyExceptionwhen a scroll to the oldest message is requested while the table has no sections.Sources/ExyteChat/Views/UIList.swift(currentmain, unchanged since at least 3.1.7):With zero sections,
max(0 - 1, 0)evaluates to0, so the next line asks UIKit for the row count of a section that does not exist:The
guard lastRow >= 0cannot help:max(_, 0)already forced the value non-negative, so the guard is always true, and it runs after the offending call regardless.Why this is reachable in normal use
ChatView.swiftarms this scroll from a status bar tap:View+StatusBar.swiftimplements that by swizzlingUIStatusBarManager.handleTapAction:and posting a globalNotificationCenternotification. The subscription is created inonAppearand released inonDisappear.onDisappeardoes not fire for a view that stays mounted while off screen, which is the normal case for aChatViewinside aTabViewtab or lower in aNavigationStack. The observer therefore survives navigation, and a status bar tap performed on a completely different screen still arms the scroll on that chat. If its table is empty at that moment (empty conversation, or messages not loaded yet), the app crashes.The scroll runs inside
UIView.animatewrapped inwithCheckedContinuation(UIList.swift:128), so the exception surfaces from inside the animation block:Steps to reproduce
ChatViewfor a conversation with no messages, or one whose messages load asynchronously.Observed on iOS 27.0, iPhone 13 Pro, ExyteChat 3.1.7. The same code path is present on
mainand in 3.2.7.Suggested fix
Guard on the section count before touching the table, and drop both clamps so the row guard becomes meaningful for an empty section:
Happy to open a PR with this.