NSTableView steals -mouseUp: from mouse drag sequence started inside an NSView sibling - objective-c

NSView A contains an NSTableView (in an NSScrollView), a child NSView B, and listens to mouse events by implementing -mouseDown/Dragged/Up:. NSView B implements neither of these methods, so the original NSResponder ones should forward the corresponding messages to its next responder (parent view).
When the mouse is clicked inside, dragged from NSView B, and released over any object other than the NSTableView, the whole dragging sequence (down/dragged/up) is sent to the parent view normally.
If, however, the mouse is released over the NSTableView, the -mouseUp: message is never sent to the parent view. Instead, it is stolen by the NSTableView.
This behaviour can be changed to normal by subclassing NSView B and implementing the -mouseUp: message so that it simply passes the event to its next responder (precisely what the original NSResponder method is supposed to do).
Observed on OSX 10.7.4/Xcode 4.4. A test project can be downloaded from here: http://www.filefactory.com/file/41ung2am0aax/n/TestDragging_zip
Is this an expected behaviour or a bug?

Related

NSEvents pass through NSView when overlapping another

In my custom NSWindow class, I have a Parent view (set as the window's contentView), with a Child. Throughout my application, I have appended subviews to my Child view, which all appear to receive mouseDown responses perfectly. However, I wanted to have a panel slide into place above the Child view (yet still housed within the parent view) upon clicking of a button (I'm calling it slidingOverlay in my application). Upon adding the slidingOverlay subview (which subsequently contains more subviews) to the Child view, I am unable to click any of its elements, as they all pass on through to the lower, Child view.
A couple details:
I have tried overlaying the slidingOverlay as a subview of both the Child view and the Parent view, both resulting in the Child view receiving NSEvents.
The slidingOverlay appears to be non-opaque (translucent? not sure if Apple has a term for it), despite being plainly visible, layer-backed, and having a solid color. Perhaps this is the origin? I'm rather new to Cocoa.
All three views are actually layer-backed (Parent, Child, and slidingOverlay).
All three views were created programmatically.
I have tried overriding (NSView*)hitTest:(NSPoint)aPoint for Child, and forwarding the NSEvent to slidingOverlay's subviews works. However, this method seems kinda hack-like and requires me to predetermine which slidingOverlay subview will receive events. I want all slidingOverlay subviews to be able to get clicks, ignoring all lower subviews, while slidingOverlay is visible.
Just to summarize, there is an NSWindow, with a contentView of Parent. Parent has one Child with subviews (NSButtons, drop downs, custom classes, et cetera), all receiving clicks. The slidingOverlay, in its present state, is appended to Child as a subview (which should work fine according to my grasp on things), and its visibility can be toggled. When toggled on the mouseDown NSEvents to slidingOverlay and its children are being passed to the underlying subviews of Child - for instance, clicking point 100,400 will result in the drop down (direct subview of Child, but underneath the visible slidingOverlay layer) to be expanded.
I guess the question is - when an NSView (with containing subviews) is atop another view, how would it be perceived as "solid" and clickable, in favor of it's parent, or underlying views?
Looks like attaching a new NSWindow (replacing slidingOverlay) works (in favor of further layering Child). Still no idea why NSEvents pierce through after a certain number of subviews are attached, though…
It is related to the placement of subviews in their array.
Whenever subviews are overlapping, the subview that added later will take the mouse event. The zPosition in layer-backed views won't help either.
You can fix this by sorting the subviews using the following method:
- (void)sortSubviewsUsingFunction:context:

NSView loses mouseMoved/mouseDragged if dragged in from subview

A = the parent NSView;
B = the child NSView;
B has a small NSTrackingArea;
B is a small subview of A;
A correctly receives mouseMoved and mouseDragged events if moved,clicked, dragged in a empty area.
If i click in B and drag the mouse outside of its bounds into A, A does not receive any mouseMoved or mouseDragged events.
Can someone point me to what i could do to get the superview A to react to this drag?
I've observed this and I was able to solve it by doing as follows:
Create a NSViewController and set viewA as its "view" property.
In my case, I implemented mouseDragged in the view-controller but
you can try on the view and see what results you get. mouseEntered
and mouseExited were implemented in the view subclass.
Tip: Make sure you're updating the tracking area bounds as needed.
Hope this helps.

How to block NSView events under another NSView?

Here is the idea:
I have a NSWindow containing 2 NSView, let's call them ViewA and ViewB.
ViewA has a list of subview objects, each object has its own tracking area set and handles a mouseDown event. ViewB is a hidden view, which appears above ViewA.
The problem is when ViewB appears, ViewA still receives mouseDown events. So when I click on ViewB, the object behind the ViewB receives the mouseDown event. I would like to know if there's any way to block the events of ViewA while ViewB is over it.
I know I can remove the tracking area from every object, but it still responds to the mouseDown event.
You can override sendEvent: method on NSWindow and test 'firstResponder', if it is ViewA, than not call [super sendEvent:event] so ViewA will not receive any event.
If view B is a subview of A, the problem is that it's hidden. Don't hide it: just set its opacity to 0. That way you won't see it, but the responder chain will.
In case anyone still looking for answer for these kind of questions nowadays, I only managed to do this with a child window, solution described in this accepted answer. Also, if you want to make the window transparent (/clear colored), but still receive mouse events on it, put this line into action as well:
[overlayWindow setIgnoresMouseEvents:NO];
Sibling views block, descendant views dont as the child will propegate mouse events upstream to its parent. To block descendants propegating events to their parent you must overide the event in the child and not call super on the same event. Calling super will propegate the event to its parent. Here is a full explination on Events and hittesting sibling/descending views: (be warned its dense) http://stylekit.org/blog/2016/01/28/Hit-testing-sub-views/
you can also disable the touch events for ViewA by [ViewA setAcceptsTouchEvents:NO];
and can enable them again as per your requirement by setting YES again.

No UIControls responding to user events

I am creating an iOS app without using Interface Builder and I seem to be missing something vital whereby the controls I am creating (UITextField, UIButton, etc.) are not responding to touch events.
Here's my view hierarchy:
UIWindow->UIView->(UITextField, UIButton)
I am able to create the above hierarchy and everything is showing up fine on the screen, but tapping on the UITextField or the UIButton do nothing. I also tried adding some UIGestureRecognizer subclass instances to the UIView to no avail.
I am able to call becomeFirstResponder on the UITextField and the application starts with the keyboard up and able to receive input. However when the keyboard is dismissed the interface goes back to its "dead" mode.
What am I missing?
I was calling init on my UIWindow instead of initWithFrame:. For some reason everything was drawing correctly but the UIWindow was not responding to events because every user tap was outside the bounds of the zero-size window.

Handling touch event logic in Subview or ViewController

I have a series of UIView subclasses that are added as subviews. Each subview can be dragged and dropped. When they are dropped (touchesEnded), I need to run a method in the viewController to do some work. I currently have the touchEvents handled in each subview class. Should I be handling these touch events in the viewController or should I be passing a pointer to the parent viewController as a property of each class I have added as subviews?
UPDATE: Or is this a job for NotificationCenter?
UIViewController is a subclass of UIResponder, and instances are automatically inserted in the responder chain behind the views they control. As a result, you can implement the same event methods in subclasses of UIViewController as in subclasses of UIView, and they will 'just work'; that is, they'll be called automatically.
So if your view controller needs to respond to -touchesEnded:withEvent:, just implement the method directly in your UIViewController subclass. If the view also needs to do something in response to the event, you can always send it a message from within your -touchesEnded:withEvent: (or whatever) implementation.
You might try delegating your desired touch handling event from your uiview subclass to the uiviewcontroller using a delegate protocol. The view controller can set itself as delegate as it instantiates or adds each subview.
In MVC paradigm, if you want to handle a touch that's specific to a V view's internals (button appearance/location), then you might want to handle that touch in the V view, but if the touch effects some state outside the view (it's position in a bigger window, etc.) you might want to pass handling that touch up to the C controller to set state in the M model.