NSAlert appearing while NSMenu is open causes UI to freeze

As the title says, when my status bar menu is open and a NSAlert is triggered from another thread, the UI freezes.

Presumably this is because both things are running on the main thread. But since I'm dealing with an NSAlert and an NSMenu, don't I have to run these on the main thread?

NSAlert Code

func showWallpaperUpdateErrorAlert(messageText: String, informativeText: String) {
    DispatchQueue.main.async {
        NSApp.activate(ignoringOtherApps: true)

        let updateErrorAlert = NSAlert()
        updateErrorAlert.messageText = messageText
        updateErrorAlert.informativeText = informativeText
        updateErrorAlert.addButton(withTitle: "OK")
        updateErrorAlert.runModal()
    }
}

NSMenu Code

func createStatusBarMenu() {
    // Status bar icon
    guard let icon = NSImage(named: "iconFrame44")
        else { NSLog("Error setting status bar icon image."); return }
    icon.isTemplate = true
    statusBarItem.image = icon

    // Create Submenu items
    let viewOnRedditMenuItem = NSMenuItem(title: "View on Reddit...", action: #selector(viewOnRedditAction), keyEquivalent: "")
    viewOnRedditMenuItem.target = self

    let saveThisImageMenuItem = NSMenuItem(title: "Save This Image...", action: #selector(saveThisImageAction), keyEquivalent: "")
    saveThisImageMenuItem.target = self

    // Add to title submenu
    let titleSubmenu = NSMenu(title: "")
    titleSubmenu.addItem(descriptionMenuItem)
    titleSubmenu.addItem(NSMenuItem.separator())
    titleSubmenu.addItem(viewOnRedditMenuItem)
    titleSubmenu.addItem(saveThisImageMenuItem)

    // Create main menu items
    titleMenuItem = NSMenuItem(title: "No Wallpaperer Image", action: nil, keyEquivalent: "")
    titleMenuItem.submenu = titleSubmenu
    titleMenuItem.isEnabled = false
    getNewWallpaperMenuItem = NSMenuItem(title: "Update Now", action: #selector(getNewWallpaperAction), keyEquivalent: "")
    getNewWallpaperMenuItem.target = self

    let preferencesMenuItem = NSMenuItem(title: "Preferences...", action: #selector(preferencesAction), keyEquivalent: "")
    preferencesMenuItem.target = self

    let quitMenuItem = NSMenuItem(title: "Quit Wallpaperer", action: #selector(quitAction), keyEquivalent: "")
    quitMenuItem.target = self

    // Add to main menu
    let statusBarMenu = NSMenu(title: "")
    statusBarMenu.addItem(titleMenuItem)
    statusBarMenu.addItem(NSMenuItem.separator())
    statusBarMenu.addItem(getNewWallpaperMenuItem)
    statusBarMenu.addItem(NSMenuItem.separator())
    statusBarMenu.addItem(preferencesMenuItem)
    statusBarMenu.addItem(quitMenuItem)

    statusBarItem.menu = statusBarMenu

    statusBarMenu.delegate = self
}

Answers


In my case the solution was to dismiss the menu before showing the alert.

I had to access the menu from the NSStatusItem's menu property and call cancelTrackingWithoutAnimation() (regular cancelTracking() wasn't as smooth). I also had to do this outside the main thread, for whatever reason.

func showWallpaperUpdateErrorAlert(messageText: String, informativeText: String) {
    statusBarItem.menu?.cancelTrackingWithoutAnimation() // This is new

    DispatchQueue.main.async {
        NSApp.activate(ignoringOtherApps: true)

        let updateErrorAlert = NSAlert()
        updateErrorAlert.messageText = messageText
        updateErrorAlert.informativeText = informativeText
        updateErrorAlert.addButton(withTitle: "OK")
        updateErrorAlert.runModal()
    }
}

Need Your Help

FCM (Firebase Cloud Messaging) Push Notification with Asp.Net

android asp.net firebase firebase-cloud-messaging

I have already push the GCM message to google server using asp .net in following method,

Fade a div in on form submit

php jquery ajax

What I need to do is submit the form with Ajax, post the form content as post data to the same page, and fade the div in when it's finished.