skip to Main Content

I’m trying to make a simple drawing app (OSX Mac app) and I’m trying to figure out how the user can draw a line by two mouse clicks, for example, the first mouse click (mouseDown then mouseUP) would mark the origin point of the line, and the second mouse click (mouseDown then mouseUP) would mark the end point of the line. Before the user makes the second click of the end point, I’d like the line (before anchoring the end point) to be shown live, kind of like in photoshop. Both Objective-C and Swift are fine.

So far I’ve got…

var newLinear = NSBezierPath()

override func mouseDown(theEvent: NSEvent) {
        super.mouseDown(theEvent)
        var lastPoint = theEvent.locationInWindow
        lastPoint.x -= frame.origin.x
        lastPoint.y -= frame.origin.y
        newLinear.moveToPoint(lastPoint)
    }

override func mouseUp(theEvent: NSEvent) {
        var newPoint = theEvent.locationInWindow
        newPoint.x -= frame.origin.x
        newPoint.y -= frame.origin.y
        newLinear.lineToPoint(newPoint)
        needsDisplay = true
    }

Cheers!

2

Answers


  1. enums with associated values are great for this as your application scales up and possibly adds other tools and states.

    enum State {
        case normal
        case drawingLine(from: CGPoint, to: CGPoint)
    }
    var state = State.normal
    
    override func mouseDown(theEvent: NSEvent) {
        super.mouseDown(theEvent)
        var lastPoint = theEvent.locationInWindow
        lastPoint.x -= frame.origin.x
        lastPoint.y -= frame.origin.y
        state = .drawingLine(from: lastPoint, to: lastPoint)
    }
    
    override func mouseUp(theEvent: NSEvent) {
        if case .drawingLine(let firstPoint, _) = state {
            var newPoint = theEvent.locationInWindow
            newPoint.x -= frame.origin.x
            newPoint.y -= frame.origin.y
            //finalize line from `firstPoint` to `newPoint`
        }
    }
    
    override func mouseMoved(theEvent: NSEvent) {
        if case .drawingLine(let firstPoint, _) = state {
            needsDisplay = true
            var newPoint = theEvent.locationInWindow
            newPoint.x -= frame.origin.x
            newPoint.y -= frame.origin.y
            state = .drawingLine(from: firstPoint, to: newPoint)
        }
    }
    
    override func draw(_ dirtyRect: NSRect) {
        if case .drawingLine(let firstPoint, let secondPoint) = state {
            //draw your line from `firstPoint` to `secondPoint`
        }
    }
    
    Login or Signup to reply.
  2. I understand what you are attempting to achieve here! So I have added lines of code to the andyvn22’s solution that should assist your endeavours, please note: for simplicity purposes start a new ‘Xcode Project’ and we can make sure everything is covered together.

    In your new project Right Click ‘ViewController.swift’ and add New File… Select ‘Cocoa Class’ and Click Next, name this file ‘DrawingView’ assure Subclass of: is ‘NSView’ and select Next.

    Now your finished that lets setup your interface, enter ‘Main.storyboard’ and drag/drop a ‘Custom View’ make sure to add constrains according to your preferences. Now enter your ‘Identity Inspector’ while ‘Custom View’ is selected and add ‘DrawingView’ to Class at the top.

    I hope this is making sense soo far! Ok open ‘Assistant Editor’ so you can view ‘ViewController.swift’ and ‘Main.storyboard’ simultaneously, Right Click and drag from ‘Custom View’ to ‘ViewController.swift’, name this ‘draw’ and select connect.

    You will notice Xcode has automatically updated @IBOutlet to your Subclass, and your ‘ViewController.swift’ looks like the example below.

    import Cocoa
    
    class ViewController: NSViewController {
    
        @IBOutlet var draw: DrawingView!
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            // Do any additional setup after loading the view.
        }
    
        override var representedObject: Any? {
            didSet {
            // Update the view, if already loaded.
            }
        }
    }
    

    Now select the ‘DrawingView.swift’ file and clear all content on page, highlight the code below, copy and paste into your project.

    import Cocoa
    
    class DrawingView: NSView {
    
        // >>>CODE ADDED BY LC<<<
        private var path: NSBezierPath = {
            let path = NSBezierPath()
            path.lineWidth = 50.0
            path.lineJoinStyle = .roundLineJoinStyle
            path.lineCapStyle = .roundLineCapStyle
            return path
        }()
    
        enum State {
            case normal
            case drawingLine(from: CGPoint, to: CGPoint)
        }
        var state = State.normal
    
        override func mouseDown(with event: NSEvent) {
            super.mouseDown(with: event)
            var lastPoint = event.locationInWindow
            lastPoint.x -= frame.origin.x
            lastPoint.y -= frame.origin.y
            state = .drawingLine(from: lastPoint, to: lastPoint)
        }
    
        override func mouseUp(with event: NSEvent) {
            if case .drawingLine(let firstPoint, _) = state {
                var newPoint = event.locationInWindow
                newPoint.x -= frame.origin.x
                newPoint.y -= frame.origin.y
    
                // >>>CODE ADDED BY LC<<<
                path.move(to: convert(event.locationInWindow, from: nil))
                path.line(to: firstPoint)
                needsDisplay = true
            }
        }
    
        override func draw(_ dirtyRect: NSRect) {
            if case .drawingLine(let firstPoint, let secondPoint) = state {
    
                // >>>CODE ADDED BY LC<<<
                NSColor.orange.set()
                path.lineWidth = 5.0
                path.stroke()
                path.line(to: firstPoint)
                path.line(to: secondPoint)
            }
        }
    }
    

    Everything should be running as intended, you will be incapable of drawing a crocked line. I hope this has helped and feel free to reply for additional information.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search