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.
Related
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.
I have a window with two views in it:
Both the movie view and the controller view override mouseDown: and mouseUp:, for different purposes (the movie view has a target and action and reacts as a button, whereas the controller seeks and supports dragging).
The twist is, when I click within the movie view—which, as shown in the screenshot, will generally be large enough to make missing it and hitting the controller view instead wildly improbable—the mouseDown: and mouseUp: messages are sent to the controller view! The movie view never receives either message.
I have not overridden hitTest: in the movie view or in its parent view (which is a plain NSView), only in the controller view (for reasons that have to do with a tracking area I have on that view—my implementation simply returns self, which is the controller view).
So, what gives?
Well, you probably guessed it: It was my hitTest: implementation in the controller view.
It turns out that hitTest: is not only sent to the views whose frames the mouse location lies within; it is sent to every view in the window, even views that were nowhere near the mouse at either end of the click.
So, when overriding hitTest:, make sure that you verify that the point is actually within yourself. The simplest way is to send [super hitTest:thePoint] and only do your custom hit-test if the result is not nil. (Unless you want to steal clicks from other views in the window.)
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.
You see this in iPhone apps like Gilt. The user scrolls a view, and a subview apparently "sticks" to one edges as the rest of the scrollView slides underneath. That is, there is a text box (or whatever) in the scrollView, that as the scrollView hits the top of the view, then "sticks" there as the rest of the view continues to slide.
So, there are several issues. First, one can determine via "scrollViewDidScroll:" (during normal scrolling) when the view of interest is passing (or re-appearing). There is a fair amount of granularity here - the differences between delegate calls can be a hundred of points or more. That said, when you see the view approach the top of the scrollView, you turn on a second copy of the view statically displayed under the scrollView top. I have not coded this, but it seems like it will lack a real "stick" look - the view will first disappear then reappear.
Second, if one does a setContentOffset:animated, one does not get the delegate messages (Gilt does not do this). So, how do you get the callbacks in this case? Do you use KVO on "scroll.layer.presentationLayer.bounds" ?
Well, I found one way to do this. When the user scrolls by flicking and dragging, the UIScrollView gives its delegate a "scrollViewDidScroll:" message. You can look then to see if the scroller has moved the content to where you need to take some action.
When "sticking" the view, remove it from the scrollView, and then add it to the scrollView's superview (with an origin of 0,0). When unsticking, do the converse.
If you use the UIScrollView setContentOffset:animated:, it gets trickier. What I did was to subclass UIScrollView, use a flag to specify it was setContentOffset moving the offset, then start a fast running timer to monitor contentOffset.
I put the method that handles the math and sticking/unsticking the child view into this subclass. It looks pretty good.
Gilt uses a table view to accomplish this. Specifically, in the table view's delegate, these two methods:
– tableView:viewForHeaderInSection:
and – tableView:heightForHeaderInSection:
According to the NSWindow Class Reference, you should "rarely need to invoke" the NSWindow methods "display" or "setViewsNeedDisplay". So what is the usual way to redisplay the window's contents?
EDIT: I am having trouble dealing with resizing events. I just want to have everything scale proportionally. See this question. As no one seems to have any ideas for using masks to get it to happen, I want to redraw the whole thing.
Jason's comment really should be an answer:
Generally you don't need to. Instead, you invalidate whatever view needs to be invalidated for whatever reason within the window.
In addition to that comment, I'd add that you might want to explain why you feel you need to do this. While there are sometimes perfectly valid reasons to force the whole window to redraw, they are rare and you should suspect You're Doing It Wrong™.
Use this method to flag subviews for redisplay:
- setNeedsDisplay:YES