Yuma Technical Inc.
Yuma Technical Inc.

Reputation: 723

How store a tuple for NSGradient(colorsAndLocations:) in swift

To apply a gradient to a NSView, it seems best to make a class that inherits from NSView and override the draw method that sets a path and draws the gradient.

class GradientView: NSView {

var startColor = NSColor.red
var startPosition: CGFloat = 0
var endColor = NSColor.white
var endPosition: CGFloat = 1
var rotation: CGFloat = 0


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

    let bgGradient = NSGradient(colorsAndLocations: (startColor, startPosition), (endColor, endPosition))
    let path = NSBezierPath.init(rect: self.bounds)
    bgGradient?.draw(in: path, angle: rotation)
}
}

(Let me know if there's a better/easier way...)

It works great, but now I want to send a custom collection of colors & color-stops. Note: *With this above method, I am limited to defining two (the startColor & startPosition and endColor & endPosition) only.

I'd like to use (an array ? of) tuples (containing several colors and their stop values). The API to set a color and location is NSGradient(colorsAndLocations:) and uses the tuple (NSColor, CGFloat).

I can add a parameter like var colorStopArray: [(color: NSColor, stop: CGFloat)] = [] that creates an array of (such) tuples. Also I can append values with: colorStopArray.append((startColor, startPosition)).

But how can I assign this to the API?

I've tried many methods and they all cause errors (eg. tried NSGradient(colorsAndLocations: colorStopArray.flatMap{return ($0.color, $0.stop)}) )

So how can send many custom colors+locations to the above class (or hopefully a better suggestion) to create a custom gradient (which will then be drawn on my NSView)?

Upvotes: 0

Views: 188

Answers (2)

Yuma Technical Inc.
Yuma Technical Inc.

Reputation: 723

Using matt's clues, I replaced the let line (using the more appropriate API init(colors:atLocations:colorSpace:)) :

let bgGradient = NSGradient(colorsAndLocations: (startColor, startPosition), (endColor, endPosition))

with

let bgGradient = NSGradient(colors: colorStopArray.map { $0.color }, atLocations: colorStopArray.map { $0.stop }, colorSpace: NSColorSpace.genericRGB)

Now it functions as I desired.

Upvotes: 0

matt
matt

Reputation: 535925

With this above method, I am limited to defining two (the startColor & startPosition and endColor & endPosition) only.

No, you're not. This is a variadic; you can add as many tuples as you wish. This works fine:

let startColor = NSColor.red
let startPosition: CGFloat = 0
let middleColor = NSColor.blue
let middlePosition: CGFloat = 0.5
var endColor = NSColor.white
var endPosition: CGFloat = 1
let bgGradient = NSGradient(colorsAndLocations: 
    (startColor, startPosition), 
    (middleColor, middlePosition), 
    (endColor, endPosition))

If the question is "can I turn an array into a variadic?", then the answer is, unfortunately no. This feature ("splatting") is missing from the Swift language. The only way to call a variadic in Swift is as a variadic.

The initializer that lets you supply an array of colors and stop locations is init(colors:atLocations:colorSpace:). So if that's what you want to supply, call that initializer instead.

Upvotes: 0

Related Questions