Updating an NSView with processing in a separate thread - macos

Suppose you have a scrollable NSView. When you scroll it, the part coming into the clipping rectangle will need to be refreshed, and the view's drawRect (Swift draw) method will be called for a rectangle containing that new part. Suppose now that before that part can be drawn, some time-consuming processing will need to be done. Since you don't want to burden the main thread (from which drawRect is called) with that processing, you might want to unload it on a secondary thread. Now my question is, what's a good way to do this? For example, you can:
Initiate asynchronous processing from drawRect, exit it without drawing, and have the secondary thread, when it's finished, store the data somewhere and call setNeedsDisplay and thus have drawRect get called again.
As above, but when the processing is finished, draw into the view directly (in the main thread), without going through drawRect.
Set canDrawConcurrently for the view beforehand; but that, as far as I can tell, will not necessarily force drawRect into a secondary thread, unless the system decides to do so.
Or, maybe some other way? This seems like a common enough situation, but I can't find any tips on how to do this in a clean way.

Related

Is [NSWindow setFrame:display:] API asynchronous?

I have a strange UI behavior which made me think that this API might be asynchronous. Assuming it is (async), I've made some changes and now the UI works as expected. I didn't find the answer in Apple's documentation.
Is anyone familiar or had some experience with this? Is it really async? Thanks!
EDIT: All UI changes are being made from the main thread.
setFrame:display: updates the frame and tells the system to redraw the window (and possibly the subviews) during its next drawing cycle. Cocoa drawing is always done during its drawing cycle, not at the moment you call a drawing method. See What is the most robust way to force a UIView to redraw? for some more discussion on that.
This is better thought of as drawing being "consolidated" rather than "asynchronous." Unless there are animations, all drawing should generally be complete before the next event loop. It's possible that a view will somehow delay its drawing to a later event loop, but rare. Layer-backed views in 10.8+ can support actual async drawing, but this is not the default.
From you description, it's possible you're calling setFrame:display: from a thread other than the main thread. AppKit drawing is not thread safe. You cannot call drawing methods from anywhere but the main thread, or you'll get "strange UI behavior." (This isn't completely true. On OS X, you can use lockFocus to draw from other threads, but they still won't actually be applied until the next drawing cycle after you unlock focus. But normally it's best just to draw from the main thread in modern OS X since GCD makes this simple.)

NSWindow display and setViewsNeedDisplay

In NSWindow class I found, among others, two methods: display and setViewsNeedDisplay:. But I don't know the difference between these two methods. Although documentation says: "You rarely need to invoke this method", I need to call one of these to update window's contentView. The problem that the I don't know which method to call. Maybe somebody can tell me difference between those 2 methods?
Thanks.
P.S.contentView of window is my custom view.
For both windows and views, display method redraws the object immediately, and setViewsNeedDisplay:/setNeedsDisplay: set a flag that redraw is needed while the actual redraw will happen later. Repeatedly displaying a view is expensive, repeatedly marking it for display is very cheap.
Most of the time you need to call setNeedsDisplay: on the view you want to be redrawn. Or even setNeedsDisplayInRect: to mark only a part of it, not the whole view. So if all you need is contentView to be redrawn, call [[window contentView] setNeedsDisplay:YES] and that will be it.
In rare cases, for example, before invoking a blocking API call or displaying a modal alert, you will have to call display on the view instead, otherwise the call will block for a long time before redraw happens.
In even more rare cases, you will have to call display on the window, for example, if you tinkered with areas outside content view, like title and borders.
And you almost never need -[NSWindow setViewsNeedDisplay:]. I don't know an example when one needs it.

Call initWithCoder explicitly or ViewDidAppear equivalent in UIView?

In a UIView I have a nav button with an IBAction & method in the top-level view controller.
In the IBAction code, I flip a boolean so that when execution returns to the UIView, there's some new setup prior to drawRect: repainting the view.
If all this were in the ViewController, I could put the new setup code in something like ViewDidAppear so it executes each time the button is pressed. However, there's no such method at the UIView level. There is initWithCoder, but this only seems to be executed once (when the storyboard/nib loads).
So my question is - either, is there a way to call the initiWithCoder method explicitly from my IBAction at the VC level (I've tried [self initWithCoder:nil] but the breakpoint at the UIView level doesn't trigger) or is there a method that runs when execution returns to the UIView level, a la ViewDidAppear?
Thanks
Image of goal:
Unless you really know what you're doing (I mean really know), don't call -initWithCoder: yourself. You're meant to implement it just as you implement -drawRect: and let the system call it. If you ever find yourself calling something like this directly and you can't explain the deep technical reasons why there's no other way, then it's the wrong approach. Read and follow the documentation (not just the method's doc) to make sure you understand whatever method you're using. It'll tell you.
That said, what you're wondering is if there's a point in a view's lifecycle where you can "do something" (check a BOOL and perform some work if YES/NO) any time the view "appears". The answer is yes, and -willMoveToSuperview "can" work.
BUT
That's the "wrong" approach, IMO. The BOOL property ('draw a twiddle next time I'm asked to draw) can and probably should live in the UIView, but its state should be set in its controller since this is specific to your app. Views are supposed to be (highly) reusable; controllers are supposed to implement your app's specific logic and drive the views according to the model state and user (or system) actions.
So: when you want to enable the "draw a twiddle" operation, your view controller should set the view instance's drawTwiddle flag then probably flag the view for drawing. Your view will then have -drawRect: called at some point you shouldn't try to control and, when it does, it sees that self.drawTwiddle == YES and draws the twiddle along with whatever other drawing it does.
At that point, you might be tempted to have the view set its own drawTwiddle flag to NO since the behavior is intended to fire once. Don't do this. BEWARE: Other user actions or system events may call -drawRect: at any time so the twiddle may not actually be seen by the user (it may appear and disappear faster than is visible). 'So', the right thing to do is to make the controller (via some direct action, system event, or timer) responsible for setting and unsetting the drawTwiddle flag, then flagging the view for redisplay.
Adding
It's also unusual to put an IBOutlet or an IBAction in a UIView. Most of the time, unless you're creating some compound control whose parts aren't intended to be accessed and managed individually, your architecture is clearer (and more closely follows the spirit of the MVC design pattern) by letting the controller manage/own the outlets and actions.

Layer backed NSOpenGLView + animation timer = strange drawing behavior?

I am creating an application which subclasses NSOpenGLView to do some OpenGL drawings. Now i wanted to add an overlay control, similar to QuickTime X. First thing i tried was setting NSOpenGLCPSurfaceOrder to -1 and making my window none-opaque. It did work, but i lost the windows shadow, so not a viable solution.
I then made my NSOpenGLView subclass layer-backed by calling [setWantsLayer:YES] and adding my control box as a subview. It worked, but i noticed a big drop in performance. I did some research and found this:
I am using an NSTimer object to call a method [timerFired:] 60 times per second. Works perfectly. The only thing i do within this method is calling [self setNeedsDisplay:YES], because when using a layer-backed OpenGLView one needs to overload the [drawRect:rect] method and do all the OpenGL drawing there. The problem: [drawRect:rect] often gets called although the timer didn't fire.
At first i thought "of course it does, it draws the NSOpenGLView, so it might be called by the window manager or something". So deleted my timer object to determine how it was called between [timerFired:] calls. The result: It wasn't called at all, at least not when not resizing or dragging the window.
So next i experimented with my timers time interval. Turns out, up until 55 times per second the [drawRect:rect] is called between 60 and 70 times per second, and from a timer interval of 59 times a second onward the [drawRect:rect] is called between 100 and 120 times a second.
I suspect that this highly unpredictable manner in which my drawing is called leads to the performance loss, either by being uneven or by clogging the thread with a lot of OpenGL drawings and not enough time to be executed or something. I also read that layer-backed OpenGLViews don't work with vsync, although i can't confirm this.
Does anyone have an explanation? Does anyone have an idea on how to only draw my OpenGL on timer fires?
I already tried the naive approach and added a boolean variable, set it true in my [timerFired:] and made my [drawRect:rect] only calling the OpenGL drawing method if the variable is true. The result was an extremely flickery and stuttery animation, so no luck.
What about using an CAOpenGLLayer with asynchronous animation oder CVDisplayLink? Would either help?
edit another thing which might be helpful information: I already used [setNeedsDisplay:YES] without my view being layer-backed, so i could easily switch between layer-backed and not-layer-backed, and i didn't have any of the problems described above, so it definitely has to do with my view being layer-backed. Everything gets a mess by just calling [setWantsLayer:YES].
So i replaced my NSTimer object with a CVDisplayLink and everything is smooth again. Although i don't actually understand why. It seems like requesting a backing layer broke the vertical sync, but it does not explain the random calls to drawRect that appeared out of nowhere.
Also i would really like to if vsync works with a layer backed NSOpenGLView and / or with a CAOpenGLLayer. I can't find any official information.

setNeedsDisplay:True not redrawing unless activated?

I have a custom view that is a subview of the main window. I have a timer that fires a [self setNeedsDisplay:TRUE] that will update drawing on the view. But from what I can see, if I leave the application on the background and switch to another, it does not reflect the new drawing functions until I click again on the application.
What could I be missing?
Thank you,
Jose.
setNeedsDisplay will not fire if it thinks the user cant see what its drawing for obvious reason. This includes, offscreen and obscured views even when the app is running.
In an app where you might be drawing data over time you would draw all the relevant data while backgrounded out at once when the app resumes.

Resources