0

I'm trying to draw a shape that is created with NSBezierPath on an NSView canvas. I've created a subclass of NSView.

// NSView //
import Cocoa

class DisplayView: NSView {
    var path: NSBezierPath
    var fillColor: NSColor
    var strokeColor: NSColor
    var weight: CGFloat
    init(frame: CGRect, path: NSBezierPath, fillColor: NSColor, strokeColor: NSColor, weight: CGFloat){
        self.path = path
        self.fillColor = fillColor
        self.strokeColor = strokeColor
        self.weight = weight
        super.init(frame: frame)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func draw(_ dirtyRect: NSRect) {
        path.lineWidth = weight
        fillColor.set()
        path.fill()
        strokeColor.set()
        path.stroke()
    }
}

// NSViewController //
import Cocoa

class ViewController: NSViewController {
    // MARK: - IBOutlet
    @IBOutlet weak var canvasView: NSView!

    override func viewDidLoad() {
        super.viewDidLoad()

        override func viewDidLoad() {
        super.viewDidLoad()

        let path = Shapes.circle(maxSize: 100) // a path from a separate class
        let rect = CGRect(x: 20, y: 20, width: 200, height: 200)
        let pathView = DisplayView(frame: rect, path: path, fillColor: NSColor.green, strokeColor: NSColor.white, weight: 6.0)
        canvasView.addSubview(pathView)
    }
}

And I get the following result. How come the edges are broken by half the line weight on two sides? The path object is only a size of 100 pts x 100 pts. Thanks.

enter image description here

UPDATE

The following is the code for making a path object.

class Shapes {
    static func circle(maxSize: CGFloat) -> NSBezierPath {
        let oval = NSBezierPath.init(ovalIn: CGRect(x: 0.0 * maxSize, y: 0.0 * maxSize, width: 1.0 * maxSize, height: 1.0 * maxSize))
        oval.close()
        return oval
    }
}
El Tomato
  • 6,479
  • 6
  • 46
  • 75

1 Answers1

3

You have created your bezier path with an origin of 0,0. As a result, the thicker border gets clipped by the view it is rendered in (your DisplayView class).

You need to create your path so the origin is (weight / 2 + 1) instead of 0.

Or you can apply a translate transform to the graphics context and shift the origin by (weight / 2 + 1.

override func draw(_ dirtyRect: NSRect) {
    let ctx = NSGraphicsContext.current!.cgContext
    ctx.saveGState()
    let offset = weight / 2 + 1
    ctx.translateBy(x: offset, y: offset)

    path.lineWidth = weight
    fillColor.set()
    path.fill()
    strokeColor.set()
    path.stroke()
    ctx.restoreGState()
}
rmaddy
  • 314,917
  • 42
  • 532
  • 579
  • Thanks. I've added 'override var wantsDefaultClipping: Bool { return false }' to DisplayView, but I have no luck. – El Tomato Jun 02 '18 at 05:12
  • I wasn't sure that would work. Removed from the answer. But I did add another option. – rmaddy Jun 02 '18 at 05:16
  • Thanks. I see your point. It would be very difficult for me to edit complex shape objects. Is there a simple way of moving an entire NSBezierPath object, say, by 10 pts to the right and 10 pts up with just a few lines of code? – El Tomato Jun 02 '18 at 05:21
  • Yes, that's the 2nd option in my answer. Apply a translation to the context in your `draw` method before stroking and filling the path. – rmaddy Jun 02 '18 at 05:23
  • I added the implementation for the translation. – rmaddy Jun 02 '18 at 05:32
  • That's very nice of you. I don't think I could have written it although I've understood the issue. – El Tomato Jun 02 '18 at 05:33