Last active
March 21, 2018 21:35
-
-
Save superlopuh/9e59e88d65d295a0fdfc345f6944491e to your computer and use it in GitHub Desktop.
Sugar for populating a UIView with a bunch of CAShapeLayers from models.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
extension CAShapeLayer { | |
struct Model { | |
let path: UIBezierPath | |
let strokeColor: UIColor? | |
let fillColor: UIColor | |
let lineWidth: CGFloat | |
let lineDashPattern: [NSNumber]? | |
init(path: UIBezierPath, strokeColor: UIColor? = nil, fillColor: UIColor = .clear, lineWidth: CGFloat = 1.0, lineDashPattern: [NSNumber]? = nil) { | |
self.path = path | |
self.strokeColor = strokeColor | |
self.fillColor = fillColor | |
self.lineWidth = lineWidth | |
self.lineDashPattern = lineDashPattern | |
} | |
} | |
func setModel(_ model: Model?) { | |
guard let model = model else { | |
self.path = nil | |
return | |
} | |
self.path = model.path.cgPath | |
self.strokeColor = model.strokeColor?.cgColor | |
self.fillColor = model.fillColor.cgColor | |
self.lineWidth = model.lineWidth | |
self.lineDashPattern = model.lineDashPattern | |
} | |
} | |
struct ReplenishingStack<Value> { | |
let make: () -> Value | |
// Called when taken from queue and into action | |
let dequeueWithPrev: (Value, Value) -> () | |
// Called when taken from action and into queue | |
let enqueueWithPrev: (Value, Value) -> () | |
// never greater than values.count | |
private(set) var activeCount: Int | |
private var values: [Value] | |
init(initial: Value, make: @escaping () -> Value, dequeueWithPrev: @escaping (Value, Value) -> (), enqueueWithPrev: @escaping (Value, Value) -> ()) { | |
activeCount = 0 | |
self.values = [initial] | |
self.make = make | |
self.dequeueWithPrev = dequeueWithPrev | |
self.enqueueWithPrev = enqueueWithPrev | |
} | |
/// Do not capture the array slice in work | |
private mutating func gimme(_ number: Int, work: (ArraySlice<Value>) -> ()) { | |
if values.count <= number { | |
for _ in values.count ... number { | |
values.append(make()) | |
} | |
} | |
if number < activeCount { | |
let prevs = values[number ..< activeCount] | |
let currs = values[number + 1 ..< activeCount + 1] | |
for (prev, curr) in zip(prevs, currs) { | |
enqueueWithPrev(prev, curr) | |
} | |
} else if activeCount < number { | |
let prevs = values[activeCount ..< number] | |
let currs = values[activeCount + 1 ..< number + 1] | |
for (prev, curr) in zip(prevs, currs) { | |
dequeueWithPrev(prev, curr) | |
} | |
} | |
activeCount = number | |
work(values.dropFirst()[...activeCount]) | |
} | |
mutating func zipForEach<Elements: Collection>(_ elements: Elements, work: (Value, Elements.Element) -> ()) where Elements.IndexDistance == Int { | |
gimme(elements.count) { values in | |
zip(values, elements).forEach(work) | |
} | |
} | |
} | |
final class ShapesView: UIView { | |
private lazy var shapeLayers: ReplenishingStack<CALayer> = { | |
return ReplenishingStack<CALayer>( | |
initial: self.layer, | |
make: { CAShapeLayer() }, | |
dequeueWithPrev: { prev, curr in | |
prev.addSublayer(curr) | |
}, | |
enqueueWithPrev: { _, curr in | |
curr.removeFromSuperlayer() | |
} | |
) | |
}() | |
func setShapes(_ shapes: [CAShapeLayer.Model]) { | |
shapeLayers.zipForEach(shapes) { layer, shape in | |
layer.frame = self.bounds | |
(layer as! CAShapeLayer).setModel(shape) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment