NSView ordering not maintain - objective-c

I have two NSView subclass objects. One i have already added to window content view. Other I have added as a subview below the first subview as below.
[window.contenView addSubview:secondSubview positioned:NSWindowBelow relativeTo:firstView];
[secondSubView setWantsLayer:YES];
When I change the secondSubview to frame using animator, it animates above firstSubview.
As the secondSUbview lies below the firstSubview, should it not animate below the firstSubview.

you misunderstood the point of my comment so I'll post this here as an answer.
You need to put a layer on the superview doing so will put a layer on all the subviews and should fix the problem.
I even found this other SO question and answer that verifies what I was trying to tell you.

Related

Layer hosting NSView within NSOutlineView

I am trying to create a custom NSView that hosts a CALayer hierarchy to perform efficient display. This NSView is then embedded within a NSTableCellView that is displayed by a View-Based NSOutlineView.
The problem is that whenever I expand or collapse an item, all rows are being moved, but the layer's content remains displayed at the position it was before changing the outline.
Scrolling the NSOutlineView seems to refresh the layers and they resync with their rows at that point.
I have debugged this behavior using Instruments and it seems that the scrolling provokes a layout operation which updates the layers with a setPosition: call that should have occured when expanding or collapsing items.
Here is some sample code for a simple layer hosting NSView subclass.
#interface TestView : NSView
#end
#implementation TestView
- (instancetype)initWithFrame:(NSRect)frameRect
{
self = [super initWithFrame:frameRect];
CAShapeLayer* layer = [CAShapeLayer layer];
layer.bounds = self.bounds;
layer.position = CGPointMake(NSMidX(self.bounds), NSMidY(self.bounds));
layer.path = [NSBezierPath bezierPathWithOvalInRect:self.bounds].CGPath;
layer.fillColor = [NSColor redColor].CGColor;
layer.delegate = self;
self.layer = layer;
self.wantsLayer = YES;
return self;
}
#end
I have tried a lot of potential solutions to this problem but I couldn't find any interesting method that gets called on the NSView instance that could be overriden to call [self.layer setNeedsDisplay] or [self.layer setNeedsLayout]. I also tried various setters on the CALayer itself such as :
layer.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable;
layer.needsDisplayOnBoundsChange = YES;
self.layerContentsRedrawPolicy = NSViewLayerContentsRedrawOnSetNeedsDisplay;
Can anyone help me figure out how to make this layer display properly inside a NSOutlineView?
I ended up answering my question. The problem wasn't in the way my TestView was implemented. I simply missed one of the steps for enabling CoreAnimation support within the application. The relevant reference is within the Core Animation Programming Guide.
Basically, in iOS Core Animation and layer-backing is always enabled by default. On OS X, it has to be enabled this way :
Link against the QuartzCore framework
Enable layer support for one or more of your NSView objects by doing one of the following
In your nib files, use the View Effects inspector to enable layer support for your views. The inspector displays checkboxes for the selected view and its subviews. It is recommended that you enable layer support in the content view of your window whenever possible
For views you create programmatically, call the view’s setWantsLayer: method and pass a value of YES to indicate that the view should use layers.
Once I enable layer support on any of the NSOutlineView's parents, the various glitches are solved.
It is difficult to read the NSOutlineView reference documents and find the information about cell reuse that is likely giving you fits here.
You may have looked at outlineViewItemDidCollapse: but it's kind of a useless for our issue, because it doesn't have a pointer to an NSView, and that's because it's older than view-based outline views.
Perhaps the one helpful mention, buried within the NSOutlineViewDelegate protocol, down in the section on view-based NSOutlineView methods, there is a single mention within outlineView:didRemoveRowView:forRow: that:
The removed rowView may be reused by the table, so any additionally inserted views should be removed at this point.
In other words, when you call the outline view's makeViewWithIdentifier:owner:, for a cellView or rowView with a particular ID you often get a recycled view. Especially often because of collapse. Incidentally, that method is from the NSTableView superclass, and in that reference, there's also this comment:
This method may also return a reused view with the same identifier that is no longer available on screen. If a view with the specified identifier can’t be instantiated from the nib file or found in the reuse queue, this method returns nil.
So you have the option of altering the view hierarchy or niling properties in didRemoveRowView:forRow. However, buried within a third cocoa reference, that for NSView, there is within the commentary on prepareForReuse, this comment:
This method offers a way to reset a view to some initial state so that it can be reused. For example, the NSTableView class uses it to prepare views for reuse and thereby avoid the expense of creating new views as they scroll into view. If you implement a view-reuse system in your own code, you can call this method from your own code prior to reusing them.
So, TL;DR, you need to implement prepareForReuse.
The pertinent references are (mostly) the superclasses of both NSOutlineView and NSTableCellView.
And, FWIW, there was a similar question here, where the questioner seems to indicate things are even worse than I think, in that NSOutlineView is more creative behind the scenes than NSTableView.
In my own work with outline views and embedded NSTextViews, I've seen wildly terrible rendering hiccups relating to expand/collapse/scroll that I seem to have managed in just the NSOutlineViewDelegate methods. On iOS they did everyone the favor of renaming makeViewWithIdentifier to the more explicit dequeueReusableCellViewWithIdentifier.
You shouldn't have to enable layer backing for any of the ancestor views (like the outline view).
In my experience, the layer immediately assigned to a view (as opposed to sublayers) doesn't need its bounds, position, or autoresizing mask to be set. It is automatically made to track the bounds of the view. In fact, I would avoid setting those properties, just in case that breaks the automatic synchronization with the view's bounds rect.
So, the question is: how are you arranging for the view to move or resize with its superview? Are you using auto layout? If so, did you turn off its translatesAutoresizingMaskIntoConstraints? If yes to both, what constraints are you setting on the view? If no to either, how did you position the view within its superview? What frame did you set? Also, is the superview configured to autoresize its subviews (probably yes, since that's the default)? What is your view's autoresizingMask?
You could also override -setFrameOrigin: and -setFrameSize: in your custom view class and call through to super. Also, add logging to show when that's happening and what the new frame rect is. Is your view being moved as you expect when you expand or collapse rows?

Rotate only subview of a uiviewcontroller

I read up other questions and it isn't very clear, as to what I could do about this problem.
Decided to ask anyway.
So I have a UIViewController, wherein I block auto-rotation.
I bring up one of the subviews added to the controller. Now, I rotate the device. And this UIView should change orientation, I will use autolayout to support all orientations on this view alone.
Is this possible?
What I tried is to, listen for device orientation changes, and then send the orientation to the subview and then was thinking probably transform, but that won't be accurate and it is probably a lot of work.
Any clue?
So, there isn't any direct method without using another view controller to hitch the view to. Add a new UIViewController and make your UIView the view controller's view. Add your rotation parameters/methods to the newly made view controller.
So its not the best solution because I have to now present that view from a view controller which is adding more code. That's kinda fundamental to a UIView. It can't handle itself unless you write hacks and override your view's behavior.
Another solution is to listen to changes in interface orientations and then relayout your view based on that change.

NSPopover padding content on one side

I have two NSPopovers, both of which are set up exactly the same (just linking a custom NSView from IB). Both pop up just fine, but one appears to offset the content by about 20px.
This NSPopover is (properly) not padding the content...
but this one adds about 20px from the ride side.
Here are the two views laid out in IB
As you can see, the search bar should be pinned to the right side like it is the left, but for some reason it is not. At first I thought it was a contraints issue, but after messing around with them for a while I can confirm it is not.
Any clue whats up?
EDIT: Decided to subclass the view and fill its rect, got some very strange results! The view appears to be offset. I have no clue why this is...
From here, this caught my eye (emphasis mine):
The principle circumstance in which you should not call
setTranslatesAutoresizingMaskIntoConstraints: is when you are not the
person who specifies a view’s relation to its container. For example,
an NSTableRowView instance is placed by NSTableView. It might do this
by allowing the autoresizing mask to be translated into constraints,
or it might not. This is a private implementation detail. Other views
on which you should not call
setTranslatesAutoresizingMaskIntoConstraints: include an
NSTableCellView, a subview of NSSplitView, an NSTabViewItem’s view, or
the content view of an NSPopover, NSWindow, or NSBox. For those
familiar with classic Cocoa layout: If you would not call
setAutoresizingMask: on a view in classic style, you should not call
setTranslatesAutoresizingMaskIntoConstraints: under autolayout.
Does it apply to you?
if you have an outlet to your content view, you should be able to force it into place with:
[contentView setFrame:[[contentView superview]bounds]];
or more modernly I guess...
contentView.frame = contentView.superview.bounds;
Proplem solved, but I still don't exactly know why it required solving. The NSPopovers content view (or at least mine) requires an origin point of X: 13, Y: 13. For some reason, only one of my views was getting this value.
To solve this, I subclassed NSView and overrode the setFrame method forcing its x and y to always be 13, 13
-(void)setFrame:(NSRect)frameRect {
[super setFrame:NSMakeRect(13, 13, frameRect.size.width, frameRect.size.height)];
}

How to get UIView given a CGPoint?

I am trying to find a way to get a particular UIView given a CGPoint. Briefly, I want to do a hit test.
For example, I have a UIView which has many subviews whose sizes are smaller than the parent UIView. What I want to do is, when a touchMoved event happens, to check the other subviews around the touched subview.
For that purpose, it would be nice to be able to convert a CGPoint to a subview UIView.
I'm new to Objective-C. Is there good way to do it? Any suggestion would be really helpful. :)
There is a hitTest:withEvent: selector on UIView. This returns the deepest subview in the view hierarchy that contains that point. Try calling this on your topmost view.

Scrolling a UITableView inside a UIScrollView

I have a UITableView which is a subview of a UIView, then that UIView is a subview of a UIScrollView. How do I detect the touches that should scroll the UITableView?
The UITableView can get item selection events (a cell in the table is selected/tapped) just fine, except that you have to hold down on the cell before it fires. But I can't get the UITableView to scroll, its always the UIScrollView that reacts to the pan gesture.
Any help is greatly appreciated. Thanks in advance!
EDIT:
Solved, though I asked the wrong question. It does work by default as Roman K pointed out. I think the problem was related to having a part of the UITableView outside the bounds of the UIScrollView (the UITableView went over the bottom bounds of the UIScrollView). Setting it to correctly fit inside the UIScrollView fixed it.
Please, make sure that UIScrollView's properties delaysContentTouches and canCancelContentTouches are set appropriately. They control how UIScrollView instance passes touch information to its subviews. By default delaysContentTouches is set to YES. Also, make sure that, if you extended UIScrollView, touchesShouldBegin:withEvent:inContentView: allow touches in the subview.
Otherwise, UITableView scrolling should work by default in your scenario. If you create a test project with just the view hierarchy as described you will see that it is the case. So, compare the two and see what difference affects the scrolling.