Dev notebook: Converting scale & position of circular NSSlider for degrees

The standard Cocoa control NSSlider comes in a circular variety that resembles a rotating knob with an indicator point. Among the ideal applications for such a control is representing a circular angle.

In the application I’m developing I wanted to use this to control the angle of the brush image used for drawing. I began by setting the range of the control from 100 and using the value as a percentage (a pretty common approach for sliders), converting to the angle (which I’m using in radians in my drawing code)

Functionally it was okay, but there were some user-feedback issues. For one, the circular slider by default increases in the clockwise direction, but in my mathematics-centered application I felt it made much more sense for angle to be treated as it is on the unit circle, increasing in the counter-clockwise direction. (Since my drawing code was written this way, it also meant that the position of the knob did not visually follow the rotation of the brush image). A related issue: the unit circle typically puts the angle zero at “3 o’clock”, whereas the NSSlider defaults to position 0 at noon.

One other usability issue involved precise setting, which is somewhat a limitation of the size of the control. A text field displaying the control setting could help the user a bit; however displaying values in radians would require a lot of decimal spaces and perhaps not be as intuitive, so I decided at this point to convert the control to work with degrees, and display this value in a text field.

To sum up, the needs are: 1) make the control increase counter-clockwise instead of clockwise, 2) make the control set 0 to the 3 o’clock knob position instead of noon; 3) map the range from 0 to 360 degrees and display that value in a text field.

In the .xib I set the control to have a value range from 0 to 359. I created a subclass of NSSlider, initially thinking I may have to get into the drawing code represent the value the way I wanted, but then realized that it’s just a simple a translation of values, from the control’s internal understanding to my counter-clockwise model of degrees. The solution is as simple as a Swift computed property:

import AppKit

class MPDegreeCircleSlider : NSSlider {

var degrees : CGFloat {
get {
// normal circular slider makes 0 at 12 o'clock
// and values increase clockwise; we just translate
// so that our degree values are zero at 3 o'clock
// increasing counter-clockwise

var adjVal = 0.0 - self.doubleValue + 90.0
if (adjVal < 0.0) {
adjVal += 360.0
}
return CGFloat(adjVal)
}

set (deg) {
var adjVal = 90.0 - Double(deg)
if adjVal < 0.0 {
adjVal += 360.0
}
self.doubleValue = adjVal
}
}
}

In my controller code, I cast the control to the subclass type, using the degrees computed property to set and get the value (rather than directly accessing an NSControl value property).

This approach works great! Below are screenshots of the app, with the brush angle at a few different settings – 0°, 41°, 124°. You can see the brush stroke in the drawing area, matching not only the angular value but also visually matching the position of the NSSlider knob.

Maybe some developer out there will find this little trick useful!