Prevent an instance of NSView from receiving mouse events - cocoa

I have an NSView in a window with core animation layer turned on.
I use this view to display images with animation from time to time. I need the entire view to be the back layer not only the images. Behind this view which I call AnimationBaseView are other views which the user interacts to.
Everything is ok except the AnimationBaseView prevents the other views from getting rightMouseDown events.
I've tried the following:
returning NO to acceptsFirstResponder to AnimationBaseView
Hiding and unhiding the AnimationBaseView as needed, but produces a nasty flicker.
Thank you for your help,
Jose.

Override - (NSView *)hitTest:(NSPoint)aPoint to return either nil or the view that should handle the mouse events

Related

SetNeedsDisplay on NSView triggers the redrawing of the whole views hierarchy

I'm working on an app made by a NSWindow which own a lot of custom subviews, that could be opaque or not.
Whenever I call SetNeedsDisplay: or SetNeedsDisplayInRect: on a subview, the system calls the drawRect of each single subview starting from the content view of the parent NSWindows.
How can it be avoided? How can I redraw just the dirty subview (it should be the default behaviour)? Is there something that I'm missing maybe in subclassing the NSView? Or in setting the properties or the syle of the parent NSWindow?
Thanks
The that could be opaque or not is the trouble-some bit. Any non-opaque view triggers a redraw of the entire view hierarchy, because the window must restore that views background to a pristine state. Only views set to opaque may not require anything else below them to be redrawn. They might still trigger redraws "above" though, if the opaque view itself is partially covered by other views.
Ok, I think I’ve figured it out. It seems that turning all the subviews into layer-backed views did the trick.
And this is reasonable giving the way the layers are managed by the gpu and how the layer compositing is performed.
But I still don't understand why, using the "classic" NSViews, no matter if they are opaque or not, siblings or children, overlapped or not , I cannot invalidate a single view without the system calls the re-drawing of the entire view hierarchy of the window

Click events getting routed to the wrong view

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.)

How to "stick" a UIScrollView subview to top/bottom when scrolling?

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:

NSTextView overlay causes oddities with first responder status

I have an NSTextView in an NSScrollView, and I am programmatically inserting an NSView subclass as a subview of the NSTextView. This NSView acts as an overlay, superimposing graphical information about the text beneath it.
I thought it was working quite well until I noticed that the text view does not respond to right clicks. Other operations (editing, selection) seem to work just fine.
Also, if the first responder is changed to a sibling of the scroll view (an outline view, for example) the text view does not regain first responder status from clicking on it. The selection will change in response to clicking, but the selection highlight is gray instead of blue (indicating that the text view is not the first responder).
If I offset the frame of the overlay subview, the text view behaves 100% normally in the area not overlapped by the overlay, but the overlapped area behaves incorrectly, as outlined above.
Steps To Replicate This Behavior on Mac OS X 10.6.4:
Create a plain old non-document-based Cocoa app.
Add an `NSTextView' IBOutlet to the app delegate .h.
Add an NSTextView to the window in MainMenu.xib. Connect the textView outlet.
Type in a bit of code:
In applicationDidFinishLaunching:
NSView *overlay = [[NSView alloc] initWithFrame:textView.bounds];
[textView addSubview:overlay];
[overlay release];
Run the app, observe that right click in the text area does not work as it should, yet you can still otherwise interact with the text view.
Next, add an NSOutlineView to the window in the xib. Observe that once focus leaves the text area (if you click on the outline view) with the overlay in place, you cannot set the focus back to the text view (it will not become first responder again).
Is there some way I can enable the NSTextView to receive all of its events, even though my NSView overlay does not accept first responder or mouse events? I suspect this might be related to the field editor – perhaps it is ignoring events it thinks are destined to the overlay view?
You probably need to make your overlay an instance of a custom view class that forwards all events and accessibility messages to the text view. You may also need to convert any view-relative coordinates to the text view's coordinate system.
I don't have a lot of experience with it, but another possibility would be to use a Core Animation layer as an overlay.
A clean way to handle this is by making your overlay view a custom subclass of NSView, and then overriding the hitTest: method to always return nil. This will prevent the overlay view from participating in the responder chain. Instead, events will get sent automatically to it's superview or views higher up the view hierarchy. You might also want to override acceptsFirstResponder to return NO to be safe (in case it's accidentally set programatically).

How to make a view controller first responder for an NSView in Cocoa

I'm trying to implement a view controller for a custom NSOpenGLView based view (this is Cocoa, not Cocoa Touch).
The view is contained within a NIB loaded window but it does not have its own NIB. In fact the window contains multiple instances of the view.
I want to route mouse events to the controller instead of to the view. I would like for this to happen as soon as the user clicks within the corresponding view.
So how can this be done ?
I've tried having the view's becomeFirstResponder method call makeFirstResponder with the controller as argument. However that doesn't seem to work, the view still receives the mouse events instead of the controller if NSView::becomeFirstResponder returns YES. If it returns NO then neither of my classes receive the mouse events.
Of course I could implement the mouse event handling methods in the view and explicitly forward them to the controller but it seems like there should be a better way to handle this.
For general "first responder" status, I recommend Charles Parnot's MTViewController, an NSViewController subclass that uses KVO to make certain the controller is in the responder chain with no extra effort on your part.
However, in your case, you want mouse events too. There's really no way around this - your view will need to translate mouse events into controller interactions.

Resources