Search code examples
swiftcocoansviewnsimageviewnsevent

How to move NSImageView inside NSView in Cocoa?


In my Mac OS app I need a functionality to be able to move NSImageView around inside an NSView after mouseDown event (triggered by user) happen in this NSImageView. When the user triggers mouse Up event, this view must move to the last mouseDrag event direction and stay there. During the move I want the NSImageView to be visible on the screen (it should move along with the mouse cursor).

I've read a Handling Mouse Events guide by Apple, https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/EventOverview/HandlingMouseEvents/HandlingMouseEvents.html Also I've downloaded this sample code: https://developer.apple.com/library/content/samplecode/DragItemAround/Introduction/Intro.html

Both links contain code in Objective C. Code for DragItemAround is outdated. I've tried to search for solutions on GitHub and other StackOverflow threads, but have not found any working solutions.

Would be glad to hear the answers on this question. I'm using Swift 3.

I've created a custom MovableImageView which is a subclass of NSImageView with this code:

import Cocoa

class MovableImageView: NSImageView {

    override func draw(_ dirtyRect: NSRect) {
        super.draw(dirtyRect)

        // Drawing code here.
        // setup the starting location of the
        // draggable item

    }

    override func mouseDown(with event: NSEvent) {
        Swift.print("mouseDown")

        //let window = self.window!
        //let startingPoint = event.locationInWindow


    }


    override func mouseDragged(with event: NSEvent) {
        Swift.print("mouseDragged")

        self.backgroundColor = NSColor.black

    }

    override func mouseUp(with event: NSEvent) {
        Swift.print("mouseUp")
    }

}

After that in Interface Builder i've set this class to NSImageView. I've also set constraints in Interface Builder for this NSImageView.

Now i'm trying to figure out how to move NSImageView inside NSView? (Swift 3, XCode 8)


Solution

  • You need to save the mouseDown point and use it for offset later. Please check the following code:

    class MovableImageView: NSImageView {
    
    var firstMouseDownPoint: NSPoint = NSZeroPoint
    
    init() {
        super.init(frame: NSZeroRect)
        self.wantsLayer = true
        self.layer?.backgroundColor = NSColor.red.cgColor
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func draw(_ dirtyRect: NSRect) {
        super.draw(dirtyRect)
    
        // Drawing code here.
    }
    
    override func mouseDown(with event: NSEvent) {
        Swift.print("mouseDown")
        firstMouseDownPoint = (self.window?.contentView?.convert(event.locationInWindow, to: self))!
    }
    
    override func mouseDragged(with event: NSEvent) {
        Swift.print("mouseDragged")
        let newPoint = (self.window?.contentView?.convert(event.locationInWindow, to: self))!
        let offset = NSPoint(x: newPoint.x - firstMouseDownPoint.x, y: newPoint.y - firstMouseDownPoint.y)
        let origin = self.frame.origin
        let size = self.frame.size
        self.frame = NSRect(x: origin.x + offset.x, y: origin.y + offset.y, width: size.width, height: size.height)
    }
    
    override func mouseUp(with event: NSEvent) {
        Swift.print("mouseUp")
    
        }
    }
    

    In the parent view, just add this MovableImageView as subview like this:

    let view = MovableImageView()
    view.frame = NSRect(x: 0, y: 0, width: 100, height: 100)
    self.view.addSubview(view) //Self is your parent view
    

    enter image description here