NSEvents pass through NSView when overlapping another - objective-c

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:

Related

Only subviews added in viewDidLoad automatically resize

I've created a custom subclass of UIViewController that acts like a UINavigationController or a UITabBarController. Let's call it a ToolbarNavController. It has a toolbar at the bottom with controls for the user to move to a different content view.
I have two content views aside from the ToolbarNavController's view. Both are loaded from a nib and have their own controllers. The app starts out showing one of them. A button in the toolbar allows the user to switch between them.
When I add these views as subviews of the ToolbarNavController's views in viewDidLoad, they are correctly resized to fill the area between the status bar and the toolbar without overlap/underlap.
But when I try to lazy load the second view, adding it as a subview for the first time only when the user presses the toolbar button, iOS does not resize the view to account for the toolbar in its parent view, and it underlaps the toolbar which messes up my Autolayout constraints. Also, when I don't add the subview in viewDidLoad, if I put the device in landscape orientation before switching to the second view, it loads with a portrait orientation frame.
Bottom line: When inserting a subview in viewDidLoad, iOS sizes it correctly and manages autorotation for it. When inserting it later, I need to detect orientation set the frame myself. (And for some reason when I do this, autorotation kicks in again).
What is going on?
In viewDidLoad, the view is not yet layout for the resolution and interface orientation, and view properties are as they were in the interface designer. So, if you had a portrait view, that is how the initial properties of the view are set when going into viewDidLoad. When you add your view there, you add it to the XIB view. Later, iOS performs layout on the view hierarchy and thus resizes your inserted view as needed. But when adding your view at a later point, the view hierarchy has already been layout, so it is expected that the new view you are adding is also layout correctly.
Best practice is to calculate the size you need using the size of the view you are inserting into. For example, half the width of the containing view, or third the bounds, etc. This way it is independent on the orientation the interface is in.

Prevent UITableView from scrolling when custom subview is handling touches

In my iOS app have a UITableView which contains a custom subview in one of it's cells. This cell is an interactive view that handles touch events (touchesBegan, touchesEnded, touchesMoved) to update itself.
The problem is that when the user 'drags' up or down, the tableView catches these touches (although I don't pass the touches up the responder chain), scrolls the table and prevents the subview from working correctly. I would like to prevent the table from scrolling as long as the user is touching that particular subview.
The subview has no reference at all to the tableView.
How can I prevent the scrolling behavior of the table?
Update
Despite accepting the answer below, I ended up handling my situation differently. I handle the touch events in my custom view now, pass them up the responder chain (to the table cell), the cell handles the touch events as well, using them to enable/disable scrolling on the superview (the table).
Turning off "Cancellable Content Touches" in the UITableView solved this for me (in the UITableView attributes inspector under Scroll View / Touch). I got this from this SO question: Scrolling a UITableView inside a UIScrollView
From the UIScrollView:canCancelContentTouches doc:
If the value of this property is NO, the scroll view does not scroll
regardless of finger movement once the content view starts tracking.
The most common method I use in such cases is to delegate information up the event chain. Set delegates in manner:
subview->cell->table
The second thing you can do is to send a notification via Notificaion Center. Table would listen to any events that forbid normal scrolling. It is overshoot but it will leave your code consistent.
I have no more ideas at the moment.

UIScrollView longer than VC

I've been working with some UIScrollView objects that are longer than the view controllers they are contained by (this is in storyboard). Right now if I want to move objects that aren't initially shown I have to resize both the view controller and the scroll view. I also have to do this to add new objects to an initially non-displayed area. I know that I can do these things with the dimension inspector, but if I do that I can't tell what the layout looks like.
Is there another way to do this? I haven't been able to find a way to make the scroll view "scroll" inside of storyboard/IB. And it's kind of a pain to do layout this way.

Set a subview as the responder to calls to a parent UIView subclass

I've got a UIView that I've subclassed to be the main view used throughout my app. In it, I have two subviews: banner and container. Banner is basically a place to put an ad or a disclaimer or whatever. Container is meant to act as the primary view, to which you can add, remove and whatever as if it were the only view.
Right now, I'm just overriding the methods of the parent view and sending the calls to the container view. I'm wondering if there is an easier way to do this, without having to write out stuff like this for every method:
- (void)addSubview:(UIView*)view {
[container addSubview:view];
}
Maybe something that lets you delegate all method calls to the view to a specific subview, rather than responding to the method calls itself.
Anyone know if this is possible?
I'm a little confused by the question.
The responder chain is present and passes ui events up through all visible views on screen, by hierarchy. It may be useful to read a little about the responder chain, because by design it passes events from the deepest view to the highest (root) in that order, which is the opposite of the direction you're seeking (if I'm reading this right).
If you need to forward events from a superview to a subview, to respect principles of encapsulation, you should define appropriate actions in your subview's subclass interface, and then then configure your superview to target the actions in that subview/class.

Programmatically change subviews from within subview's controller

In my iPhone application I've set up a default, blank view called Main View into which various child subviews will be loaded for different parts of the application. It's the same approach as if I was using a tool bar to switch between subviews. That case, in the MainView controller I could hook IBActions to buttons in the toolbar, so that when a button was pressed, MainView added different subviews to itself.
In my situation, though, I need to tell MainView to change its subview from within the subviews. So here are two sister subviews, each with their own controller and xib, that would be loaded as subviews of MainView:
- StartView
- FormView
In StartView, after some animations and welcome stuff, a button triggers the camera image picker. Once the image picker returns the image, I need to tell MainView to remove StartView and add FormView.
It may be the result of a long day or my newness to iPhone OS but I'm stuck getting my head around the right way to set up my objects/controllers.
You never have more than one view controller active at a time. (The nav and tabbar controllers don't control views, they control other controllers.) In this case, you will have a single controller that has the MainView as its view property. It will add StartView and formView as subviews of MainView.
However, this is not a good design. It will overload the MainView controller by forcing it to juggle many views. It would be better to use a hidden navigation controller or a tabbar. Hierarchies of controllers can create the illusion from the users point of view for almost any interface layout you can imagine. There is no need to create a logical structure that mimics the visual one.
From your description you may only need a single view/view-controller pair: Set the formView controller to open the camera view before it displays the formView. When the camera is dismissed it reverts to the formView automatically. No fuss, no muss.