NSBezierPath with roundedRect works Northeast but not southWest

I have a very simple app that draws rectangles, ovals or rounderrect. I use NSBezierPath to draw the figures. I draw a rectangle and swap between shapes by typing O for oval / not oval, R for roundedRect / not rounded


When drawing roundedRect, I get a strange behavior. If I draw northEast (from lower left to top right), drawing goes OK, I get a blue rounded rectangle.


But drawing in any other direction (such as SouthWest, from top right to bottom left or southEast or NorthWest) leads to no drawing at all.

If I change roundedRect by a rect in the Bezizer call, drawing works in all directions.


XCode Version 7.1.1 (7B1005), OSX 10.11.1

Here is the code (in drawRect)


// DrawingView.swift


import Cocoa


class DrawingView: NSView {

var startPoint: NSPoint

var endPoint: NSPoint

var isOval: Bool // draw an oval

var isRoundedRect : Bool // draw a roundedrect


override init(frame frameRect: NSRect) {

self.startPoint = NSPoint()

self.endPoint = NSPoint()

isOval = false

isRoundedRect = false

super.init(frame: frameRect)

}


required init?(coder: NSCoder) {

self.startPoint = NSPoint()

self.endPoint = NSPoint()

isOval = false

isRoundedRect = false

super.init(coder: coder)

}


override func drawRect(dirtyRect: NSRect) {

super.drawRect(dirtyRect)

NSColor.redColor().set()

NSRectFill(dirtyRect)

NSColor.blueColor().set()

if isOval {

NSBezierPath(ovalInRect: NSMakeRect(startPoint.x, startPoint.y, (endPoint.x - startPoint.x), (endPoint.y - startPoint.y))).fill()

} else {

if isRoundedRect {

NSBezierPath(roundedRect: NSMakeRect(startPoint.x, startPoint.y, (endPoint.x - startPoint.x), (endPoint.y - startPoint.y)), xRadius: 30.0, yRadius: 30.0).fill()

// If changed to NSBezierPath(rect: NSMakeRect(startPoint.x, startPoint.y, (endPoint.x - startPoint.x), (endPoint.y - startPoint.y))).fill() , drawing OK

} else {

NSBezierPath(rect: NSMakeRect(startPoint.x, startPoint.y, (endPoint.x - startPoint.x), (endPoint.y - startPoint.y))).fill()

}

}

}


override var acceptsFirstResponder: Bool {

return true // inutile, sauf si des TextFields peuvent intercepter ces keyDown events.

}


override func keyDown(theEvent: NSEvent) {

if ((theEvent.characters == "o") || (theEvent.characters == "O")) {

isOval = !isOval

self.needsDisplay = true

}

if ((theEvent.characters == "r") || (theEvent.characters == "R")) {

isRoundedRect = !isRoundedRect

isOval = false

self.needsDisplay = true

}

}


override func mouseDown(theEvent: NSEvent) {

let point = theEvent.locationInWindow

startPoint = self.convertPoint(point, fromView: nil) // nil: converts from window coordinates

}


override func mouseDragged(theEvent: NSEvent) {

let point = theEvent.locationInWindow

endPoint = self.convertPoint(point, fromView: nil) // nil: converts from window coordinates

self.needsDisplay = true


}

}

For me, it's astonishing your code works for Ovals and non-rounded Rects.


Generally, NSRects containing negative size-values are considered to be invalid and treated as an empty rect.

NSIsEmptyRect

You can see NSContainsRect always returns false for such `empty` rects.

let enclosingRect = NSMakeRect(0, 0, 200, 200)
let rect1 = NSMakeRect(100, 100, 50, 50)
print(NSContainsRect(enclosingRect, rect1)) //->true
let rect2 = NSMakeRect(100, 100, -50, -50) //Size negative, treated as empty
print(NSContainsRect(enclosingRect, rect2)) //->false


You can write a Bug Report issuing this topic, but I'm not sure how Apple would fix this issue (even if we assumed Apple would):

- make NSBezierPath unable to construct a valid path for `empty` rects, including Ovals or non-rounded Rects

- make NSBezierPath able to construct a valid path for size-negative rects, including RoundedRects


Anyway, you better have in mind that many NSRect (or CGRect) handling functions or methods treat size-negative rects as empty.


EDIT: corrected class name to NSBezierPath.

Thanks. Bug report filed 23681651

Hi, what do you mean by "corrected class name to NSBezierPath." ?


Thanks for all your help in this forum.

Hi, what do you mean by "corrected class name to NSBezierPath." ?

Not a big issue, I first wrote my reply referring to UIBezierPath, so I corrected them to NSBezierPath.

Work around is pretty obvious


var newStart = NSPoint()

var newEnd = NSPoint()

newStart.x = (startPoint.x <= endPoint.x) ? startPoint.x : endPoint.x

newStart.y = (startPoint.y <= endPoint.y) ? startPoint.y : endPoint.y

newEnd.x = (startPoint.x <= endPoint.x) ? endPoint.x : startPoint.x

newEnd.y = (startPoint.y <= endPoint.y) ? endPoint.y : startPoint.y

NSBezierPath(roundedRect: NSMakeRect(newStart.x, newStart.y, (newEnd.x - newStart.x), (newEnd.y - newStart.y)), xRadius: 30.0, yRadius: 30.0).fill()


But having a different behavior between rect and roundRect is really surprising.

We definitely need 2 brains ; a UI brain and an NS brain, with fast switch 😁

NSBezierPath with roundedRect works Northeast but not southWest
 
 
Q