Crash in app window resizing code

When I enable resizing of app window in our macOS app, (new?) users get a crash at launch that I can't reproduce. On removing this code, the crashes stop. Symbolicated report below (crashed thread only). Any help appreciated:

Code Block
Crashed Thread: 0
Dispatch queue: com.apple.main-thread
Exception Type: EXC_CRASH (SIGABRT)
Exception Codes: 0x0000000000000000, 0x0000000000000000
Exception Note: EXC_CORPSE_NOTIFY
Application Specific Information:
*** Terminating app due to uncaught exception'NSGenericException', reason: 'The window has been marked as needing another Layout Window pass, but it has already had more Layout Window passes than there are views in the window.'
terminating with uncaught exception of type NSException
abort() called
Application Specific Backtrace 1:
0 CoreFoundation 0x00007fff35ab1727 __exceptionPreprocess + 250
1 libobjc.A.dylib 0x00007fff6ea17a9e objc_exception_throw + 48
2 CoreFoundation 0x00007fff35ab1585 +[NSException
raise:format:] + 181
3 AppKit 0x00007fff32ce198b -[NSWindow(NSDisplayCycle)
_postWindowNeedsLayoutUnlessPostingDisabled] + 1577
4 AppKit 0x00007fff32ce1307 -[NSWindow(NSConstraintBasedLayout)
_setViewsNeedLayout:] + 49
5 AppKit 0x00007fff32cb6a5b
-[NSView setNeedsLayout:] + 660
6 UIFoundation 0x00007fff66b3305a -[_NSCollectionViewCore
setNeedsLayout] + 53
7 UIFoundation 0x00007fff66b32a9a -[_NSCollectionViewCore
_invalidateLayoutWithContext:] + 3473
8 UIFoundation 0x00007fff66b31ce5 -[NSCollectionViewLayout
invalidateLayoutWithContext:] + 79
9 UIFoundation 0x00007fff66b31ae7
-[NSCollectionViewFlowLayout invalidateLayoutWithContext:] + 1901
10 UIFoundation 0x00007fff66b31367 -[NSCollectionViewLayout
invalidateLayout] + 129
11 APTX 0x000000010f6062d3
HomeViewController.configurePhotosCollectionViewAfterResize() (in APTX)
(<compiler-generated>:0)
12 APTX 0x000000010f6487b5
HomeViewController.windowDidResize(_:) (in APTX) (<compiler-generated>:0)
13 APTX 0x000000010f6489fd @objc
HomeViewController.windowDidResize(_:) (in APTX) + 109
14 CoreFoundation 0x00007fff35a2b259
__CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER__ + 12
....
Thread 0 Crashed:: Dispatch
queue: com.apple.main-thread
0 libsystem_kernel.dylib
0x00007fff6fcff33a __pthread_kill + 10
1 libsystem_pthread.dylib 0x00007fff6fdbfe60 pthread_kill +
430
2 libsystem_c.dylib
0x00007fff6fc86808 abort + 120
3 libc++abi.dylib 0x00007fff6ceed458 abort_message + 231
4 libc++abi.dylib 0x00007fff6cede8bf
demangling_terminate_handler() + 262
5 libobjc.A.dylib
0x00007fff6ea19a57 _objc_terminate() + 96
6 libc++abi.dylib 0x00007fff6ceec887 std::__terminate(void
(*)()) + 8
7 libc++abi.dylib
0x00007fff6ceef387 __cxa_rethrow + 99
8 libobjc.A.dylib 0x00007fff6ea17e1c objc_exception_rethrow
+ 37
9 com.apple.AppKit 0x00007fff32d79153
__NSWindowGetDisplayCycleObserverForLayout_block_invoke + 577
10 com.apple.AppKit 0x00007fff32d781f2
NSDisplayCycleObserverInvoke + 155
11 com.apple.AppKit
0x00007fff32d77d7c NSDisplayCycleFlush + 937
12 com.apple.QuartzCore 0x00007fff4153ce40
CA::Transaction::run_commit_handlers(CATransactionPhase) + 106
13 com.apple.QuartzCore 0x00007fff4153bb52
CA::Transaction::commit() + 230
14 com.apple.AppKit 0x00007fff32fec726 -[NSMoveHelper
_doAnimation] + 64
15 com.apple.AppKit 0x00007fff32feb620
-[NSMoveHelper(NSSheets) _moveParent:andOpenSheet:] + 1448
16 com.apple.AppKit 0x00007fff32feb052 -[NSWindow(NSSheets)
_orderFrontRelativeToWindow:] + 164
17 com.apple.AppKit 0x00007fff32df3bb2 -[NSWindow
_reallyDoOrderWindowAboveOrBelow:relativeTo:findKey:forCounter:force:isModal:]
+ 2217
18 com.apple.AppKit 0x00007fff32df2fb2 -[NSWindow
_reallyDoOrderWindow:relativeTo:findKey:forCounter:force:isModal:] + 135
19 com.apple.AppKit 0x00007fff32df1f3e -[NSWindow
_doOrderWindow:relativeTo:findKey:forCounter:force:isModal:] + 293
20 com.apple.AppKit 0x00007fff32f945a9 -[NSApplication
_orderFrontModalWindow:relativeToWindow:] + 232
21 com.apple.AppKit 0x00007fff32fea89a -[NSWindow
_beginWindowBlockingModalSessionForSheet:service:completionHandler:isCritical:]
+ 585
22 com.apple.AppKit 0x00007fff33047fd0 __54-[NSAlert
beginSheetModalForWindow:completionHandler:]_block_invoke + 281
23 com.apple.AppKit 0x00007fff33044755 -[NSAlert
beginSheetModalForWindow:completionHandler:] + 98
24 com.ail.aptxx 0x000000010f62eddf specialized
OtherHelper.showAlert(window:title:message:isInformational:arrButtonTitles:responseHandler:)
(in APTX) (<compiler-generated>:0)
25 com.ail.aptxx 0x000000010f60fc48
HomeViewController.checkForPhotosAppOpened() (in APTX)
(HomeViewController.swift:1408)
26 com.ail.aptxx 0x000000010f603fae
HomeViewController.viewWillAppear() (in APTX) (HomeViewController.swift:117)
27 com.ail.aptxx 0x000000010f604428 @objc
HomeViewController.viewWillAppear() (in APTX) (<compiler-generated>:0)

Accepted Reply

That seems to have fixed it! We still couldn't reproduce the crash, either before making the changes suggested by you or after, but have had no further complaints from users who were experiencing crashes. Thanks for your suggestions.

Replies

Hard to say without seeing code. Could you show it (please show the whole class).

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

Looks like you are entering an infinite loop, and system crashes when the number of passes exceed the number of views in the window

Thanks, Claude. Below is the code for app window resizing:

WindowDidResize Function
(Note that the app has two panes, and on reducing the window width the left (L) pane is hidden - as in Photos for Mac app).

Code Block
func windowDidResize(_ notification: Notification) {
if let window = self.view.window {
let arrScreens = NSScreen.screens
if arrScreens.count > 0 {
let theScreenRect = arrScreens[0]
// Hide the L pane
if (theScreenRect.frame.size.width / 3.0) >= window.frame.size.width {
window.setFrame(CGRect(x: window.frame.origin.x,
y: window.frame.origin.y,
width: (theScreenRect.frame.size.width / 3.0),
height: window.frame.size.height),
display: true,
animate: true)
theSplitView?.arrangedSubviews.first?.isHidden = true
if (theSplitView.arrangedSubviews.count) < 2 {
theSplitView?.removeArrangedSubview((theSplitView?.arrangedSubviews.first)!)
} else {
theSplitView = orignalSplitView
theSplitView?.arrangedSubviews.first?.setFrameSize(NSSize(width: 250.0, height: window.frame.size.height))
}
} else { // Show the L pane
window.setFrame(CGRect(x: window.frame.origin.x,
y: window.frame.origin.y,
width: window.frame.size.width,
height: window.frame.size.height),
display: true,
animate: true)
theSplitView = orignalSplitView
theSplitView?.arrangedSubviews.first?.setFrameSize(NSSize(width: 250.0, height: window.frame.size.height))
theSplitView?.arrangedSubviews.first?.isHidden = false
}
// Don't allow to resize
if (theScreenRect.frame.size.height / 1.5) >= window.frame.size.height {
window.setFrame(CGRect(x: window.frame.origin.x,
y: window.frame.origin.y,
width: window.frame.size.width,
height: (theScreenRect.frame.size.height / 1.5)),
display: true,
animate: true)
} else { // Allow to resize
window.setFrame(CGRect(x: window.frame.origin.x,
y: window.frame.origin.y,
width: window.frame.size.width,
height: window.frame.size.height),
display: true,
animate: true)
}
configurePhotosCollectionViewAfterResize()
}


Configure Photos Collection View

Code Block
func configurePhotosCollectionViewAfterResize() {
self.collectionViewPhotos.collectionViewLayout?.invalidateLayout()
}


These are only two functions being called when the window is resized. If anything else is needed, please let me know. Maybe we need to add something after invalidating the layout? Not sure. Many thanks.
Hard to see what is causing the possible recursion.

So, I would comment out some parts and see if the problem occurs (of course app will not work perfectly during this, but it is just to find cause of crash.
  • Comment all out: from line 7 to 52

It should not crash !
  • uncomment lines 7 to 33

does it crash ?
  • uncomment lines 34 to 50

does it crash ?
  • uncomment lines 34 to 50

does it crash ?
  • uncomment lines 51 to 52

does it crash ?

When some new users first reported the app crashing immediately on launch, we did comment out lines 7 to 52, and the crashes stopped. The main challenge is that we can't reproduce the crash on any of our test machines. So we can't check by uncommenting bit by bit. Somehow have to reproduce the crash or get clues from the crash report.

Anything you can spot in the crash report that we might have missed? It's EXC_CRASH (SIGABRT), so unhandled language exception or application passing bad data to a system routine? It mentions (<compiler-generated>:0) i.e. crashing in compiled generated code at Line 0? You have already pointed out the crash reason pointing to infinite loop possibility.
From the crashlog, problem could come from the collectionView, line 52, where you invalidate the whole collection.

Doc for UICollectionViewLayout says:

When designing your custom layouts, you can improve performance by invalidating only those parts of your layout that actually changed. When you change items, calling the invalidateLayout() method forces the collection view to recompute all of its layout information and reapply it. A better solution is to recompute only the layout information that changed, which is exactly what invalidation contexts allow you to do. An invalidation context lets you specify which parts of the layout changed. The layout object can then use that information to minimize the amount of data it recomputes.


So, could you either:
  • remove this invalidate completely (what happens to your app then ?)

  • use a more focused invalidate:

Code Block
func invalidateLayout(with: UICollectionViewLayoutInvalidationContext)
Invalidates the current layout using the information in the provided context object.

instead of
Code Block
func invalidateLayout()
Invalidates the current layout and triggers a layout update.
  • relocate the call to configurePhotosCollectionViewAfterResize() in another func

Code Block
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
configurePhotosCollectionViewAfterResize()
}

following an idea found here:
https://stackoverflow.com/questions/36884376/swift-how-to-refresh-uicollectionview-layout-after-rotation-of-the-device

Note: could you testFlight your app, or download yourself to test it ? TestFlight would be more appropriate here.

Thanks again, Claude. We are trying the third option you suggested, i.e. relocating the call in another func. Since ours is a macOS app, instead of viewDidLayoutSubviews we are using viewDidLayout method to call the configurePhotosCollectionViewAfterResize function. Also, TestFlight isn't for macOS apps, so we'll just have to try on a few different test machines. Will post an update later.
That seems to have fixed it! We still couldn't reproduce the crash, either before making the changes suggested by you or after, but have had no further complaints from users who were experiencing crashes. Thanks for your suggestions.
Glad that it helped.

So which was the correct answer in this thread ?