Allow click and dragging a view to drag the window itself?

12,894

Solution 1

I found this here:

-(void)mouseDown:(NSEvent *)theEvent {    
    NSRect  windowFrame = [[self window] frame];

    initialLocation = [NSEvent mouseLocation];

    initialLocation.x -= windowFrame.origin.x;
    initialLocation.y -= windowFrame.origin.y;
}

- (void)mouseDragged:(NSEvent *)theEvent {
    NSPoint currentLocation;
    NSPoint newOrigin;

    NSRect  screenFrame = [[NSScreen mainScreen] frame];
    NSRect  windowFrame = [self frame];

    currentLocation = [NSEvent mouseLocation];
    newOrigin.x = currentLocation.x - initialLocation.x;
    newOrigin.y = currentLocation.y - initialLocation.y;

    // Don't let window get dragged up under the menu bar
    if( (newOrigin.y+windowFrame.size.height) > (screenFrame.origin.y+screenFrame.size.height) ){
        newOrigin.y=screenFrame.origin.y + (screenFrame.size.height-windowFrame.size.height);
    }

    //go ahead and move the window to the new location
    [[self window] setFrameOrigin:newOrigin];
}

It works fine, though I'm not 100% sure I'm doing it correctly. There's one bug I've found so far, and that's if the drag begins inside a subview (a tab itself) and then enters the superview (the tab bar). The window jumps around. Some -hitTest: magic, or possibly even just invalidating initialLocation on mouseUp should probably fix that.

Solution 2

I tried the mouseDownCanMoveWindow solution (https://stackoverflow.com/a/4564146/901641) but it didn't work for me. I got rid of that method and instead added this to my window subclass:

- (BOOL)isMovableByWindowBackground {
    return YES;
}

which worked like a charm.

Solution 3

It works for me after TWO steps:

  1. Subclass NSView, override the mouseDownCanMoveWindow to return YES.
  2. Subclass NSWindow, override the isMovableByWindowBackground to return YES.

Solution 4

As of macOS 10.11, the simplest way to do this is to utilize the new -[NSWindow performWindowDragWithEvent:] method:

@interface MyView () {
    BOOL movingWindow;
}
@end

@implementation MyView

...

- (BOOL)mouseDownCanMoveWindow
{
    return NO;
}

- (void)mouseDown:(NSEvent *)event
{
    movingWindow = NO;

    CGPoint point = [self convertPoint:event.locationInWindow
                              fromView:nil];

    // The area in your view where you want the window to move:
    CGRect movableRect = CGRectMake(0, 0, 100, 100);

    if (self.window.movableByWindowBackground &&
        CGRectContainsPoint(movableRect, point)) {

        [self.window performWindowDragWithEvent:event];
        movingWindow = YES;
        return;
    }

    // Handle the -mouseDown: as usual
}

- (void)mouseDragged:(NSEvent *)event
{
    if (movingWindow) return;

    // Handle the -mouseDragged: as usual
}

@end

Here, -performWindowDragWithEvent: will handle the correct behavior of not overlapping the menu bar, and will also snap to edges on macOS 10.12 and later. Be sure to include a BOOL movingWindow instance variable with your view's private interface so you can avoid -mouseDragged: events once you determined you don't want to process them.

Here, we are also checking that -[NSWindow movableByWindowBackground] is set to YES so that this view can be used in non-movable-by-window-background windows, but that is optional.

Solution 5

Have you tried overriding the NSView method mouseDownCanMoveWindow to return YES?

Share:
12,894
d11wtq
Author by

d11wtq

About me I'm a developer in various languages, but most notably Ruby and PHP, shifting more and more in the Ruby direction every day. I also enjoy playing around with Cocoa on OS X and iOS, but I don't pretend to be an expert. I have a half-written multi-language text editor in it. One day I may finish it, though I'm considering writing an Emacs-inspired terminal-based editor in Ruby instead. Employment Flippa.com Pty. Ltd, Melbourne (scroll down for opportunities) Main technologies: PHP, quickly & iteratively moving to Ruby, Rails, DataMapper. I'm the development manager at Flippa.com and spend a lot of time working on stuff for them in my own time, mostly because I love it. We are currently in a long-term transitional period, migrating our codebase from a bespoke PHP framework to Ruby on Rails. Seamlessly running the two applications side-by-side poses some interesting problems. Our biggest ongoing technical challenges revolve around search infrastructure and a fairly complex and ever-evolving bidding system, along with tight integration with many external services. We spend a large amount of time re-thinking and improving our server infrastructure. We love exploring new technologies, where they actually make good sense as far as our needs are concerned. Job Opportunities If you're based in Australia and looking to work in a fun startup environment (we're about 3 years old) in Melbourne, drop me a note either on Twitter (@d11wtq), or via my github profile and we can arrange an interview if we think you're a good fit. We're always looking for technically-capable junior backend-developers/graduates, experienced backend-developers, and (at the current time) client-side developers. People who work with us love the atmosphere. We work in a lively office, shared with our sibling companies (SitePoint.com, 99designs.com, wavedigital.com.au and learnable.com). We have flexible working hours, a fridge full of beer and a foosball table. At the end of every year we all go away together and celebrate in style. Developers are also given the last 3 days in each month to work on projects of their own choosing. Open Source I have a selection of open source projects up on github (@d11wtq). Flippa also likes to share (@flippa).

Updated on June 10, 2022

Comments

  • d11wtq
    d11wtq almost 2 years

    I'm using a textured window that has a tab bar along the top of it, just below the title bar.

    I've used -setContentBorderThickness:forEdge: on the window to make the gradient look right, and to make sure sheets slide out from the right position.

    What's not working however, is dragging the window around. It works if I click and drag the area that's actually the title bar, but since the title bar gradient spills into a (potentially/often empty) tab bar, it's really easy to click too low and it feels really frustrating when you try to drag and realise the window is not moving.

    I notice NSToolbar, while occupying roughly the same amount of space below the title bar, allows the window to be dragged around when the cursor is over it. How does one implement this?

    Thanks.

    Tab Bar

  • d11wtq
    d11wtq over 13 years
    I hadn't, since I didn't know such a method existed, thanks. However, it doesn't appear to have worked. The tab bar is a subview of a larger (almost entire window) view, if it makes a difference.
  • Richard
    Richard over 13 years
    My next thought was to override mouseDown: (NSControl method) to pass the mouse event to [self window], and ensure that the window isMovableByWindowBackground, but that still doesn't work for me. Sorry.
  • d11wtq
    d11wtq over 13 years
    No dramas. You were on the right track. Thanks for investigating! :)
  • d11wtq
    d11wtq over 13 years
    As suspected, invalidating the "initialLocation" variable in the mouseUp: event (just by setting the y position to a negative number), then adding a guard clause to mouseDragged: fixes the bug.
  • twe4ked
    twe4ked almost 10 years
    In Swift: window.movableByWindowBackground = true.
  • ArtOfWarfare
    ArtOfWarfare almost 10 years
    @Odin - I'd imagine window.movableByWindowBackground = YES; works in Obj-C, but I think I went the method overriding route because I was specifically looking to make a subclass of NSWindow that exhibited this trait always.
  • twe4ked
    twe4ked almost 10 years
    You're probably right, I just pasted what I ended up with for anyone else that visits this page using Swift. Thanks for the answer :)
  • Keith Smiley
    Keith Smiley over 9 years
    This is often useful but it seems like it's only called a few times when the view is loaded and then never again. Because of this you can't conditionally return based on some logic.
  • Sam Soffes
    Sam Soffes over 9 years
    You can skip step two if you make your window "textured". (There's a checkbox in IB.)
  • VinceFior
    VinceFior almost 9 years
    You don't need to subclass NSWindow, since you can call [self.view.window setMovableByWindowBackground:YES] in the view controller. It appears NSWindow's isMovableByWindowBackground is mutable while NSView's mouseDownCanMoveWindow is read-only.
  • Clifton Labrum
    Clifton Labrum over 7 years
    Swift 3: window.isMovableByWindowBackground = true
  • Lax
    Lax over 6 years
    This method works for my project. except that any mouse events in movableRect will perform moving, even if there are subviews above the view.
  • Dimitri Bouniol
    Dimitri Bouniol over 6 years
    @Lax You may want to check that your view that appears above the dragging view is hit testing correctly - are both of them getting mouse down events?
  • NickSpag
    NickSpag about 6 years
    Are you sure you need the event code? Simply subclassing NSTableView and overriding 'mouseDownCanMoveWindow' works for me, even with selection enabled.
  • Paulo Mattos
    Paulo Mattos about 6 years
    Hey @NickSpag, as far as I remember, doing as you suggested would mess up the table selection behaviour (when trying to drag the window by clicking over a given selectable table row).
  • ervinbosenbacher
    ervinbosenbacher over 2 years
    Thanks for the help, solved my problem but I needed it in Swift 5. Here is what I came up with