Skip to content

Fix crash: replace usesAutomaticRowHeights with deferred self-sizing#85

Open
beezly wants to merge 1 commit intoviktorstrate:mainfrom
beezly:fix/deferred-row-sizing
Open

Fix crash: replace usesAutomaticRowHeights with deferred self-sizing#85
beezly wants to merge 1 commit intoviktorstrate:mainfrom
beezly:fix/deferred-row-sizing

Conversation

@beezly
Copy link
Contributor

@beezly beezly commented Mar 17, 2026

Problem

NSHostingView inside NSTableView with usesAutomaticRowHeights enters an infinite layout loop when SwiftUI view content changes during layout. AppKit detects this and crashes with NSGenericException:

The window has been marked as needing another Update Constraints in Window pass, but it has already had more Update Constraints in Window passes than there are views in the window.

The crash reproduces when scrolling into rooms containing video/image messages with reactions or read receipts. It is timing-dependent — more likely when launched from Finder than under a debugger.

Root Cause

During layout, NSHostingView.layout() flushes SwiftUI transactions. If the SwiftUI view tree has changed (e.g. async content loaded), this calls invalidateIntrinsicContentSize, which triggers constraint updates, which re-enters layout — exceeding AppKit's constraint update pass limit (one pass per view in the window).

This was latent since the NSTableView rewrite (#47) but became reliably triggerable as view complexity grew (reactions, read receipts, video playback).

Fix

Replace the entire auto-sizing mechanism with SelfSizingHostingView:

  • sizingOptions = [] — prevents the hosting view from participating in Auto Layout constraint solving
  • invalidateIntrinsicContentSize() overridden to schedule a coalesced async height update instead of calling super (which would re-enter the constraint system)
  • measureHeight() temporarily re-enables sizing, pins a width constraint matching the column width, reads fittingSize, then resets — all outside the layout pass
  • heightOfRow returns cached heights (default 60px); noteHeightOfRows called with animation duration 0

Also removes the old measurementHostingView (separate NSHostingController per height query) and handleTableResize observer.

Depends on

@beezly beezly force-pushed the fix/deferred-row-sizing branch 2 times, most recently from f959378 to 019121b Compare March 17, 2026 11:05
NSHostingView inside NSTableView with usesAutomaticRowHeights enters an
infinite layout loop: layout() flushes SwiftUI transactions, which
invalidate constraints, which re-enter layout. AppKit detects this and
crashes with NSGenericException.

Replace the entire auto-sizing mechanism with SelfSizingHostingView:

- sizingOptions = [] prevents the hosting view from participating in
  Auto Layout constraint solving.

- invalidateIntrinsicContentSize() is overridden to NOT call super
  (which would re-enter the constraint system) but instead schedule
  a coalesced async height update via DispatchQueue.main.async.

- measureHeight() uses a temporary NSHostingController.sizeThatFits()
  to properly measure content at the cell width, including text wrapping.

- heightOfRow returns cached heights (default 60px). noteHeightOfRows
  is called with animation duration 0 to avoid visible row resizing.

Also removes the old measurementHostingView and handleTableResize.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant