In my "InitWithFrame" method of a view I'm setting a tracking area for which I want to capture mouse enter/exit events.
My problems are two fold:
Without NSTrackingInVisibleRect the events won't be called at all.
No matter what "rect" I put it, one that covers the entire view's frame or one that occupies just a small portion of it - the mouse enter/exited events are called for the entire view, regardless of where the mouse cursor is on the view.
this is how I initialize the tracking area:
trackingArea = [[NSTrackingArea alloc] initWithRect:rect
options: (NSTrackingMouseEnteredAndExited | NSTrackingInVisibleRect | NSTrackingActiveAlways )
owner:self userInfo:nil];
[self addTrackingArea:trackingArea];
Any clues why this happens? I want the mouse enter/exit events to be called only for a small portion (the bottom part) of my view.
Mike Abdullah's answer explains point 2.
Here is a guess about why you don't receive events at all when not using the NSTrackingInVisibleRect flag:
Probably the variable rect you provide is not within the view's coordinate system. You could use the following code as the designated initializer of your NSView subclass to receive mouseEntered: and mouseExited: events for the whole area of your view:
- (id)initWithFrame:(NSRect)frame
{
if ((self = [super initWithFrame:frame]))
{
//by using [self bounds] we get our internal origin (0, 0)
NSTrackingArea* trackingArea = [[NSTrackingArea alloc] initWithRect:[self bounds] options:(NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways) owner:self userInfo:nil];
[self addTrackingArea:trackingArea];
[trackingArea release];
}
return self;
}
Apple's documentation says:
When creating a tracking-area object,
you specify a rectangle (in the view’s
coordinate system), ...
Straight from the docs for NSTrackingInVisibleRect:
The NSTrackingArea object is automatically synchronized with changes in the view’s visible area (visibleRect) and the value returned from rect is ignored.
Related
I have the following code:
[[ticketsListScrollView documentView] setFrame: NSMakeRect(0, 0, [ticketsListScrollView frame].size.width, 53 * [tickets count])];
[[ticketsListScrollView documentView] setFlipped:YES];
for(int i = 0; i < [tickets count]; i++) {
TicketsListViewController *viewController = [[TicketsListViewController alloc] initWithNibName:#"TicketsListViewController" bundle:nil];
viewController.dateLabelText = tickets[i][#"date"];
viewController.timeLabelText = tickets[i][#"time"];
viewController.subjectLabelText = tickets[i][#"title"];
NSRect frame = [[viewController view] frame];
frame.origin.y = frame.size.height * i;
[viewController view].frame = frame;
[[ticketsListScrollView documentView] addSubview:[viewController view]];
}
if the list is large enough (many views), the NSScrollView starts at top-left, which is great. For less views (the views do not take the whole documentView, then NSScrollView starts at the middle.
Any idea why?
Thank you!
Views are not flipped by default, which means your document view is being pinned to the lower-left corner (the default, non-flipped view origin) of the scroll view. What you're seeing is a view not tall enough to push the "top" subview to the top of the scroll view. I see you tried flipping this view, so you already know about this, but you're not doing it correctly.
I'm not sure why you're not getting an error or a warning when calling -setFlipped: since the isFlipped property is read-only. In your document view (the view that's scrolled, and in which you're placing all those subviews), you can override it:
- (BOOL)isFlipped {
return YES;
}
Of course you'll have to put this in a custom NSView subclass and set that as your scroll view's document view's class in IB if you're not creating it at runtime. You'll also need to adjust the frames you use for layout, since you're currently expressing them in the coordinate system of the scroll view's frame. You should be expressing them in your container/layout view's bounds coordinates, which will also be flipped, and so, likely different from your scroll view's coordinates. You'll also need to implement -intrinsicContentSize (and call -invalidateIntrinsicContentSize when adding/removing subviews) so auto-layout can size the container appropriately.
I have an NSView with [self setAcceptsTouchEvents:YES];
After the cursor has left the NSView frame, this methods keeps on being called, until I click/begin a new gesture, I don't really understand when it stops.
-(void)touchesMovedWithEvent:(NSEvent *)theEvent {
[self doStuffs:theEvent];
}
with coordinates expressed in other windows coordinate systems.
Is there a way do prevent this, or convert the coordinates back the view coordinates system ?
NSResponder's touchesMovedWithEvent is called for touch events that start in your view, until they end (no matter if that's in your own view or elsewhere).
The event location is expressed in window coordinates. Converting to your view's coordinate system is easy:
- (void)touchesMovedWithEvent:(NSEvent *)event
{
NSPoint locInWindow;
NSPoint locInView;
locInWindow = [event locationInWindow];
locInView = [self convertPoint:locInWindow fromView:nil];
NSLog(#"Location in window: %#", NSStringFromPoint(locInWindow));
NSLog(#"Location in view: %#", NSStringFromPoint(locInView));
}
If you want to handle raw touches for your own multitouch gestures, this is likely not enough info. You'll want to use [event touchesMatchingPhase:NSTouchPhaseMoved inView:self], normalizedPosition, deviceSize etc. (see Apple's documentation).
My application has a custom view that displays a timeline of events. This view is wrapped in an NSScrollView to support horizontal scrolling of the timeline. Using notifications, I've implemented a mechanism that should show another custom view which displays detail information about an event when the user clicks that event in the timeline. Below is the code that handles the event when it is received by the timeline:
NSEvent *anEvent = [aNotification object];
NSPoint aLocationInSelf = [self convertPoint: [anEvent locationInWindow] toView: self];
// Create callout view and display
NSRect aRect = NSMakeRect(aLocationInSelf.x, aLocationInSelf.y, 300, 200);
TimelineCallout *aCallout = [[TimelineCallout alloc] initWithFrame: aRect];
[self addSubview: aCallout];
In the above code I do a conversion of the point of the mouse click as registered by the event from window coordinates to view (timeline) coordinates.
However, when I step through this with the debugger no conversion is taking place and locationInSelf shows the same coordinates as the point I get from [anEvent locationInWindow]. As a result the callout is drawn at the wrong place or not visible at all.
I must be doing something wrong but I can't figure out what...
In order to convert from window coordinates to view coordinates, you have to use:
NSPoint aLocationInSelf = [self convertPoint: [anEvent locationInWindow] fromView: nil];
This will convert from window base coordinates to the receiver coordinates as the event is not originated from a particular view.
For more information, refer to the documentation.
More detailed explanation:
The conversion methods are a bit tricky to understand. There mainly two general cases (the other ones are variants):
Convert a point from one view to another view when they are located in the same window
Convert a point expressed in window coordinate into view coordinates
For the 1st case, you have two views (view1 and view2) located in the same window. If you want to convert a point from view2 into the view1's coordinate system the code will be:
NSPoint pointInView1 = [view1 convertPoint:pointInView2 fromView:view2];
For the 2nd case, you have a point expressed in window coordinates (from the event). As the point is expressed in window coordinates, you don't specify a "from" view and the code will be:
NSPoint pointInView1 = [view1 convertPoint:pointInWindow fromView:nil];
This is the fallback behavior of the method.
I'm trying to install a NSTrackingArea into a fullscreen view in order to get mouse moved events.
However, whenever I do, I get an assertion error. I've searched the web, but have not been able to find any leads.
*** Assertion failure in -[_NSFullScreenWindow _setTrackingRect:inside:owner:userData:useTrackingNum:install:], /SourceCache/AppKit/AppKit-1038.25/AppKit.subproj/NSWindow.m:3944
Here's the code that sets up the tracking area (x=1024, y=768):
cocoaWindow = [[NSWindow alloc] initWithContentRect:NSMakeRect(0.0, 0.0, x,y)
styleMask: NSTitledWindowMask
backing: NSBackingStoreBuffered
defer:NO];
glView = [[WLMacGLView alloc] initWithFrame:NSMakeRect(0.0, 0.0, x,y) pixelFormat:[WLMacGLView defaultPixelFormat]];
[glView setCocoaController:self];
//add the glView as a subview of the window's content view
[[cocoaWindow contentView] addSubview:glView];
NSRect r = [glView frame];
NSTrackingArea *track = [[NSTrackingArea alloc] initWithRect:r options: NSTrackingMouseMoved | NSTrackingActiveWhenFirstResponder | NSTrackingActiveInKeyWindow
owner:self userInfo:nil];
[glView addTrackingArea:track];
[glView enterFullScreenMode:[NSScreen mainScreen] withOptions:nil];
[glView createContext];
The assertion happens right after the call to enterFullScreenMode: withOptions:
Anyone got any ideas? Is this not the approach I should be taking to get mouse moved events in a fullscreen window?
If you want to track mouse in the whole view, I think is will be easier to implement the mouseDown:, mouseMoved: and mouseUp: methods in order to get the mouse events.
So the answer to this question turned out to be a bug in my own code.
When initializing the NSTrackingArea, I was passing in the wrong object for owner. The proper thing to pass was the NSView. With that corrected, all works as expected.
I am doing manual layouting for my Cocoa application and at some point I need to figure out what the inner size of a NSView subclass is. (E.g. What is the height available for my child view inside of a NSBox?)
One of the reasons is that I am using a coordinate system with origin at the top-left and need to perform coordinate transformations.
I could not figure out a way to get this size so far and would be glad if somebody can give me a hint.
Another very interesting property I would like to know is the minimum size of a view.
-bounds is the one you're looking for in most views. NSBox is a bit of a special case, however, since you want to look at the bounds of the box's content view, not the bounds of the box view itself (the box view includes the title, edges, etc.). Also, the bounds rect is always the real size of the box, while the frame rect can be modified relative to the bounds to apply transformations to the view's contents (such as squashing a 200x200 image into a 200x100 frame).
So, for most views you just use [parentView bounds], and for NSBox you'll use [[theBox contentView] bounds], and you'll use [[theBox contentView] addSubview: myView] rather than [parentView addSubview: myView] to add your content.
Unfortunately, there is no standard way to do this for all NSView subclasses. In your specific example, the position and size of a child view within an NSBox can be computed as follows:
NSRect availableRect = [someNSBox bounds];
NSSize boxMargins = [someBox contentViewMargins];
availableRect = NSInsetRect(availableRect, boxMargins.width, boxMargins.height);
If you find yourself using this often, you could create a category on NSBox as follows:
// MyNSBoxCategories.h
#interface NSBox (MyCategories)
- (NSRect)contentFrame;
#end
// MyNSBoxCategories.m
#implementation NSBox (MyCategories)
- (NSRect)contentFrame
{
NSRect frameRect = [self bounds];
NSSize margins = [self contentViewMargins];
return NSInsetRect(frameRect, margins.width, margins.height);
}
#end
And you would use it like so:
#import "MyNSBoxCategories.h"
//...
NSRect frameRect = [someNSBox contentFrame];
[myContentView setFrame:frameRect];
[someNSBox addSubview:myContentView];
The bounds property of NSView returns an NSRect with the origin (usually (0,0)) and the size of an NSView. See this Apple Developer documentation page.
I'm not sure (I never had to go too deep in that stuff), but isn't it [NSView bounds]?
http://www.cocoadev.com/index.pl?DifferenceBetweenFrameAndBounds