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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With