How can I get a notification while a NSWindow
's position is changed by dragging its titlebar?
I know I can use the windowWillMove:
and windowDidMove:
notifications, but those will give me a notification only when a drag is started or finished.
4 Answers
I have a solution that allows you to determine the window's position while it is being dragged.
The two issues are that there's no built-in way to get notified while the window is being dragged and that the window's frame doesn't update until it stops moving. My approach works around these issues by setting up a repeating timer and keeping track of the cursor's displacement.
First, subscribe to NSWindowWillMoveNotification
and NSWindowDidMoveNotification
to determine when the window starts and stops moving.
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(windowWillMove:)
name:@"NSWindowWillMoveNotification"
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(windowDidMove:)
name:@"NSWindowDidMoveNotification"
object:nil];
When the window is about to move, record the position of the cursor and start a repeating timer that calls your own "window is being dragged" method.
- (void)windowWillMove:(NSNotification *)notification {
if (notification.object == self.view.window) { // make sure we have the right window
self.dragCursorStartPos = [NSEvent mouseLocation];
const NSTimeInterval dragDelaySeconds = 0.1; // polling rate delay
self.dragWindowTimer = [NSTimer scheduledTimerWithTimeInterval:dragDelaySeconds
target:self
selector:@selector(myMethod)
userInfo:nil
repeats:YES];
}
}
When the window is finished moving, stop the repeating timer.
- (void)windowDidMove:(NSNotification *)notification {
if (notification.object == self.view.window) { // make sure we have the right window
if (self.dragWindowTimer != NULL) {
[self.dragWindowTimer invalidate];
self.dragWindowTimer = NULL;
}
}
}
Now, the clever/hacky part is that we determine the frame's actual position by calculating the cursor's displacement from its starting position and adding this displacement to the frame's reported origin, which hasn't changed since the window started moving.
- (void)myMethod {
NSPoint cursorPos = [NSEvent mouseLocation];
NSPoint cursorDisplacement = NSMakePoint(cursorPos.x - self.dragCursorStartPos.x, cursorPos.y - self.dragCursorStartPos.y);
CGPoint frameOrigin = self.view.window.frame.origin;
CGPoint actualFrameOrigin = CGPointMake(frameOrigin.x + cursorDisplacement.x, frameOrigin.y + cursorDisplacement.y);
NSLog(@"The frame's actual origin is (%f, %f)", actualFrameOrigin.x, actualFrameOrigin.y);
}
The actualFrameOrigin
point in myMethod
will report where the frame actually is, even though the self.view.window.frame.origin
point only updates when you stop dragging the window.
This approach lets you get notified while the window is being dragged and tells you its actual position, so you're all set!
The only issue I've found is that pressing the title bar quickly without moving the cursor will fire NSWindowWillMoveNotification
but not NSWindowDidMoveNotification
, which causes the timer to incorrectly keep repeating. To handle this case, we can check if the left mouse button is being held down in myMethod
by checking if (pressedButtons & (1 << 0)) == (1 << 0)
. If the button is not held down, we just cancel the timer.
I'm absolutely no Cocoa expert, but AFAIK windowDidMove
gives a notification even when you are still dragging and just take a little break (left mouse button still pressed, mouse is not moved for half a second or so).
What about watching two things: You know that the window drag starts and you know when it is finished. The time between watch the mouse movement, then you got your moving window position.
I would recommend looking at this link: http://www.cocoabuilder.com/archive/cocoa/31183-nswindow-not-updating-position-when-being-dragged.html
The answerer is saying that one can use the windowWillMove
event to start a timer that has to call updateWindow
(which seems to be the critical thing here) then you can periodically read the frame property which should be updated, and then stop your timer at windowDidMove
.