Search code examples
multithreadingwindowmenubar

SpriteKit windows do not redraw until "applicationDidFinishLaunching" method quits


I use SpriteKit for Mac OS X (not iOS) to run my programs.

At the end of the "applicationDidFinishLaunching" method of the "AppDelegate"-Class I start all things which are needed for initialization. Some methods do not like to be called from a background-thread like setting window-titles, resizing windows and some other tasks. So all these things are done in the main-thread.

Then we come to my problem: I cannot simply run my main program at the end of the "applicationDidFinishLaunching" method, because when I do so, the "applicationDidFinishLaunching" method does not quit until my main program quits. And my main program does not quit, because it shows some animation on the screen directly after starting the program.

In the case, that the "applicationDidFinishLaunching" method does not quit, SpriteKit does not redraw the window, so my animation runs but I see a white window.

After quitting my program, the "applicationDidFinishLaunching" method quits, too, and I see the last picture of the animation.

So I realized a workaround: I now do the initialization in the "applicationDidFinishLaunching" method and then start a background thread which runs my main program.

The "applicationDidFinishLaunching" quits after starting the background-thread and the window is updated as expected. Everything runs fine with the background-thread doing the animation.

And now the problem, I cound not solve: I need to hide the menu bar, not directly when starting the program, but after some time.

NSMenu.setMenuBarVisible(false)

Doing so is no problem when calling from the main-thread but if I hide the menu-bar from my background thread, then I can hide it once, make it visible once, hide it a second time and when making it visible a second time an exception in the AppDelegate Class stops my program:

Thread 1: EXC_BAD_ACCESS (code=EXC_i386_GPFLT)

My idea to solve this problem, was to post an event, which is handeled by the main-thread. But if I post a keyboard event for example, the event-handling is done within the background-thread, too.

Events like selecting a menu by the user, not programmatically are handeled from the main thread but I did not find a way to post an event which is then handeled in the main thread instead of the thread, which contains the sendEvent-command:

NSApplication.sharedApplication().sendEvent(event!) // Called from background-thread

Has anybody an idea of sending an event which is handeled by the main-thread

or

Running my program completely in the main-thread without having the problem, that the window-content is not drawn at all. This second solution would be my favourite, because there are some more things, which make problems within a background thread.

Perhaps I can start my main program from another method, some time after "applicationDidFinishLaunching" has finished.


Some deeper information to the topic above but still no solution:

I discovered, that there exists a function "performSelectorOnMainThread" which can be called from swift like this:

NSApplication.performSelectorOnMainThread(Selector(myFunctionToCall()), withObject: nil, waitUntilDone: true)

This call compiles, my function is called but in my background thread not on the main thread and an error is dumped:

2015-01-17 20:11:09.142 AudioDatabase[4449:2099588] +[NSApplication (null selector)]: unrecognized selector sent to class 0x7fff7b1d8be0

But execution continues. I was not able to call the function on any other than a few types like NSApplication, NSObject, NSThread like a class function. But I never reached the main loop with this.

Another idea was to use NSInvocation, but when I look in the documentation, only the Objective-C Part appears.

It would help, if it was possible, to simply call a function of mine with or without arguments that runs in the main thread and can do there something.


Solution

  • While running my program in a background thread, I discovered a way, to execute neccessary commands in the main-thread asynchronous. To do so, you have to call:

    dispatch_async(dispatch_get_main_queue())
    {
        // This block runs in the main thread
    }
    

    So my question was, so show and hide the menu bar without crashing my program. Here are the finished functions which work, when called from a background-thread:

    func m_MenuBarShow ()
    {
        dispatch_async(dispatch_get_main_queue())
        {
            NSMenu.setMenuBarVisible(true) // Class func, must be called on the Class (NSMenu) and not on the Instance (NSApp.sharedApp.mainMenu)
        }
    }
    
    func m_MenuBarHide ()
    {
        dispatch_async(dispatch_get_main_queue())
        {
            NSMenu.setMenuBarVisible(false) // Class func
        }
    }
    

    Please note that there is a small restriction on using this: The block is called asynchronous, that means you have to make sure, that it is finished, until doing something with the result. In the case of showing the menu bar this is no problem. But if you want to do something like opening a file, you must handle this.

    I will explain this as an answer to another question of mine. Please have a look at: Open File Dialog crashes in Swift