Find the top-most NSView object located under NSPoint position - objective-c

I know this question was already asked here in few forms but unfortunately non of the suggested solutions worked for me.I am implementing a multi-touch capability for my osx app.My problem:I need to resolve a location on the screen, described by NSPoint in screen coordinates, to the very top NSView object from my application that resides under this location (if any).This is a bit like HWND WindowFromPoint( Point) under Windows OS.
Few points to consider:
My application manage several NSWindows, where each window contains an hierarchy of several NSViews,
The NSView that I am interested in is not necessarily the application Key Window or Main Window, as it can be any of my other currently non-active NSView objects that happen to be under this screen location,
It is possible that under a particular NSPoint I will have more then one NSWindows and/or NSViews. In this case I wll be interested only in the very top-most NSView,
It is possible that NSView A is on top of NSView B, partialy hiding it, but the NSPoint location is present only on B, which is not the very top-most window of the application (but only the very top-most for this location). Here again I will be interested in B.
Things that I managed todo:
Enumerate NSApp for all its windows (NSApp.windows),
Enumerate NSWindow for its views (NSWindow.contentView.subviews),
Enumerate NSView for its sub-views (NSView.subviews)
Doing this I managed to enumerate all NSViews of my application, but I still need to filter out non-relevant NSViews, which are not visible.
Things that did not work for me:
NSView.hitTest returned nil for valid locations,
NSView.layer.zPosition alwyas is zero (0),
The order of the NSView.subviews list also does not reflacts the current GUI layout,
Testing if NSView is Visible also did not help as it returns true also if this window is hidden by other window.
My environment:Mac, OSX El-Capitan, XCode-7, Cocoa, Objective-C
Thanks for any help!PazO

Found it:
// 1. Get the Window-Number of the NSWindow object owning this point:
NSInteger wndNumber = [NSWindow windowNumberAtPoint:(point) belowWindowWithWindowNumber:(0)];
// 2. Get the NSWindow object associated with this particular Window-Number:
NSWindow* window = [NSApp windowWithWindowNumber:(wndNumber)];
// 3. Convert NSPoint values from System-coordinates to NSWindow-coordinates (Thanks #Willeke for his comment)
NSRect rctScreen;
rctScreen.origin = point;
rctScreen.size.height = rctScreen.size.width = 0;
NSRect rctWindow = [window convertRectFromScreen:rctScreen];
// 4. Now do the hitTest:
NSView* viewFound = [[window contentView] hitTest:rctWindow.origin];
Obviously each line should be tested for nil, but I leave this out for code brevity.

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?

Create dragable view for selecting screen area for screen recording in mac

can anyone give idea me how can i create this type of view. In which i can drag a part of mac screen for screen recording(the image shown below is the only example)
For this kind of thing you need what is called a cover window.
It's a type of borderless window that happens to take up the full screen.
Within that you need a draggable and resizable view.
Those are two separate things that can be easily implemented but you will benefit most by doing the rest of the footwork yourself to find out how to code these.
Some keywords that might help.
NSWindow
NSBorderlessWindowMask
NSView
NSViewController
NSTrackingArea
NSBezierPath
NSRect
CGRect
NSEvent
NSPoint
CGPoint

Cocoa get the screen location on an NSMenu or NSMenuItem

Is it possible to get the location (frame) of an NSMenu or one of the NSMenuItems inside it?
I have tried adding a custom view to one of the menu items and getting its frame, however it returns zero. All I need is the x coordinate, relative to the screen.
Edit
Calling [[view window] frame] on the NSView inside the NSMenuItem after the menu is visible works, however window is deprecated as of 10.6. I'll use this unless someone posts a better way...

How to arrange multiple NSViews side-by-side?

I am building an application, where I need to put two seperate NSViews side-by-side when in fullscreen mode. As I have seen, probably there are two ways to do this, either I can make a super NSView and two subviews, or put two NSViews in a NSWindow. The problem is that though the process works for me for one NSView, I can't put them together at the same time. The method setContentView: allows me to set only one view for a window. And the method setSubViews: covers up the whole of the NSView. How can one specify the exact coordinated of the views to place and size them?
An NSWindow only has one root NSView... and that's the Window's contentView. Store any other views in that root view.
Using the Interface Builder offers an easy way of configuring the location/size of views in a graphical way.. but you can manually configure their frames with something around the lines of :
NSView *myView = [[MyCustomView alloc] init];
[[myWindow contentView] addSubview:myView];
[myView setFrame:NSMakeRect( /* ... */ )];
Some reading : NSView
Set one NSView as contentView of NSWindow and add the other two NSView's on using -addSubview method of NSView

Cocoa window position anomaly

I have a weird problem with positioning a window on screen. I want to center the window on the screen, but i don't know how to do that. Here's what i've got. The window is created from nib by the main controller:
IdentFormController *ftf = [[IdentFormController alloc] initWithWindowNibName:#"IdentForm"];
[[ftf window] makeKeyAndOrderFront:self];
Now the IdentFormController has awakeFromNib() method in which it tries to position the window. For the sake of simplicity i've just tried to do setFrameOrigin(NSMakePoint(0, 0)). What happens is as follows:
The first time i create this window, everything works as expected. But if i create it again after releasing the previous, it starts appearing at random positions. Why does it do that?
So as I understand it you want to center the window on the screen?
Well assuming NSWindow *window is your window object then there are two methods...
[window center];
This is the best way to do it but it will ofset to take into account visual weight and the Dock's presence.
If you want dead center then this would work...
// Calculate the actual center
CGFloat x = (window.screen.frame.size.width - window.frame.size.width) / 2;
CGFloat y = (window.screen.frame.size.height - window.frame.size.height) / 2;
// Create a rect to send to the window
NSRect newFrame = NSMakeRect(x, y, window.frame.size.width, window.frame.size.height);
// Send message to the window to resize/relocate
[window setFrame:newFrame display:YES animate:NO];
This code is untested but it gives you a fair idea of what you need to do to get this thing working the way you want, personally I would advise you stick with Apple's code because it has been tested and is what the user would expect to see, also from a design perspective as a designer my self I don't always rely on the actual center to be where the optical center is.
You're probably running afoul of automatic window positioning. Have you tried calling
[myWindowController setShouldCascadeWindows: NO];
?
First of all, it sounds like you need to check "dealloc on close" or "release on close" in the NSWindow's property inspector. Then the window will clean up after itself and you can remove the (risky) call to [self release] in your own code.
awakeFromNib is called after all objects from the nib have been unarchived and outlets have been connected, but that may be too early to be setting the window coordinates. I believe Cocoa does some work to automatically position subsequent windows below and to the right of existing windows, so that new windows don't completely obscure old ones. It is likely doing this AFTER you set the position in awakeFromNib, stomping on your changes.
The best place to set your window position is probably in one of the NSWindow delegate methods (windowWillBecomeVisible: perhaps), or possibly right before you call makeKeyAndOrderFront:.
Check out if you can set the centre of your window with the centre of your screen. And set the window position on it. It might work out.