[General] -[NSAlert runModal] may not be invoked inside of transaction begin/commit pair

I receive the following error in terminal in my macOS and iOS targets that crashes a universal app written entirely in swiftUI.

[General] -[NSAlert runModal] may not be invoked inside of transaction begin/commit pair, or inside of transaction commit (usually this means it was invoked inside of a view's -drawRect: method.)

App structure is Sidebar Menu + Master List + Details using Core Data for persistence and Cloudkit NSPersistentCloudKitContainer for backup / mirroring.

There seems to be a lot of activity prior to the crash relating to IMKInputSession CFRunLoopObserver.

The crash seems to be associated with a Picker view within the detail view and whether it is "First Responder" at the time another item in the list is selected or a new item is added to the list (from the toolbar).

Apple has suggested that...

It seems that the app is presenting an alert in response to SwiftUI updating views. That use of -presentError: should be deferred outside of that immediate update (e.g. adding an observer block to the main run loop to present it on the next turn)

... however I have no alerts in the views that crash.

Has anyone else experienced this and if so do you have any suggestions on what worked for you?

Replies

I have the same error with this simple code :

DispatchQueue.global().async {
 // some code
       DispatchQueue.main.async {
          // some code
           NSAlert.init(error: ...... ).runModal()
        }
}

The answer given by the apple employee here https://developer.apple.com/forums/thread/88825 do not fix the problem. I am using Xcode 13.3 with macOS 12.3

Hello,

I found a solution to workaround that bug :

call runModal() in a function called from perform(#selector...) like this :

@objc func displayAlert(error: NSError)
{
        NSAlert.init(error: error).runModal()
}

self.perform(#selector(self.displayAlert), with: NSError, afterDelay: 0.3, inModes: [RunLoop.Mode.common])

Hope this will help you.

DispatchQueue asyncAfter do the trick too

DispatchQueue.main.asyncAfter(deadline: .now() + 0.3, execute: {
NSAlert.init(error: ...... ).runModal() }

Seems the problem is in the delay.