Skip to content

Instantly share code, notes, and snippets.

@superlopuh
Last active March 21, 2018 21:35
Show Gist options
  • Save superlopuh/9e59e88d65d295a0fdfc345f6944491e to your computer and use it in GitHub Desktop.
Save superlopuh/9e59e88d65d295a0fdfc345f6944491e to your computer and use it in GitHub Desktop.
Sugar for populating a UIView with a bunch of CAShapeLayers from models.
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