5
votes

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
Did you get any further with it? I'd also like to know the windows frame DURING the drag....Georg
@Georg, see my answer below. :)VinceFior

4 Answers

4
votes

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.

0
votes

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.

0
votes

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.

0
votes

Swift 5 solution ⤵︎

// Drop into `applicationDidFinishLaunching`
NotificationCenter.default.addObserver(
    self,
    selector: #selector(didChange),
    name: NSWindow.didChangeScreenNotification,
    object: nil
)

// Drop into same class
@objc func didChange() {
    print("Changed")
}