Last active
April 21, 2017 11:59
-
-
Save elm4ward/ee3337919d3d261cb180bee88f61b128 to your computer and use it in GitHub Desktop.
This file contains hidden or 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
import UIKit | |
import PlaygroundSupport | |
import Foundation | |
import CoreGraphics | |
// ---------------------------------------------------------------------------------------------------- | |
// MARK: - Interpolations | |
// | |
// http://elm4ward.github.io/swift/generator/protocol/2016/04/08/Interpolations.html | |
// | |
// Interpolating images for animations on the watch. | |
// 1. Interpolation Protocol | |
// 2. Interpolation Operators | |
// 3. Interpolation Implementation | |
// 4. ImplicitDefault | |
// 5. ZipWithFillUp | |
// 6. BarSegment | |
// 7. Drawing | |
// 8. The actual Example | |
// ---------------------------------------------------------------------------------------------------- | |
// ---------------------------------------------------------------------------------------------------- | |
// MARK: - 1. Interpolation Protocol | |
// | |
// The main protocol used to interpolate whatever you want. | |
// ---------------------------------------------------------------------------------------------------- | |
protocol Interpolation { | |
associatedtype Result | |
func interpolate(upTo: Self) -> (Double) -> Result | |
} | |
struct Interpolate<T: Interpolation> { | |
let from: T | |
let to: T | |
/// keeping the interpolationValue between 0 and 1 | |
func atFraction(fraction: Double) -> T.Result { | |
let p = max(0, min(fraction, 1)) | |
return from.interpolate(upTo: to)(p) | |
} | |
} | |
// ---------------------------------------------------------------------------------------------------- | |
// MARK: - 2. Interpolation Operators | |
// | |
// Why? Because i like to provide a DSL. | |
// Totally ok to avoid them and use functions. | |
// ---------------------------------------------------------------------------------------------------- | |
precedencegroup Interpolate { | |
associativity: left | |
higherThan: Extract | |
} | |
precedencegroup Extract { | |
associativity: left | |
} | |
infix operator |~| : Interpolate | |
infix operator |~> : Extract | |
func |~| <T: Interpolation>(lhs: T, rhs: T) -> Interpolate<T> { | |
return Interpolate(from: lhs, to: rhs) | |
} | |
func |~> <T:Interpolation>(lhs: Interpolate<T>, rhs: Double) -> T.Result { | |
return lhs.atFraction(fraction: rhs) | |
} | |
func |~> <T:Interpolation>(lhs: [Interpolate<T>], rhs: Double) -> [T.Result] { | |
return lhs.map{ $0.atFraction(fraction: rhs) } | |
} | |
// ---------------------------------------------------------------------------------------------------- | |
// MARK: - 3. Interpolation Implementation | |
// ---------------------------------------------------------------------------------------------------- | |
extension Double: Interpolation { | |
func interpolate(upTo: Double) -> (Double) -> Double { | |
return { percent in (upTo - self) * percent + self } | |
} | |
} | |
extension Float: Interpolation { | |
func interpolate(upTo: Float) -> (Double) -> Float { | |
return { percent in (upTo - self) * Float(percent) + self } | |
} | |
} | |
extension CGFloat: Interpolation { | |
func interpolate(upTo: CGFloat) -> (Double) -> CGFloat { | |
return { percent in (upTo - self) * CGFloat(percent) + self } | |
} | |
} | |
extension UIColor: Interpolation { | |
func interpolate(upTo: UIColor) -> (Double) -> UIColor { | |
func valueFromComponents(components: [CGFloat]?) -> (Int) -> CGFloat { | |
return { position in | |
guard let components = components, components.count > position else { return 0 } | |
return components[position] | |
} | |
} | |
let components1 = valueFromComponents(components: self.cgColor.components) | |
let components2 = valueFromComponents(components: upTo.cgColor.components) | |
let componentAt: (Int, Double) -> CGFloat = { pos, percent in | |
let v = components1(pos) |~| components2(pos) |~> percent | |
return v | |
} | |
return { percent in | |
return UIColor( | |
red: componentAt(0, percent), | |
green: componentAt(1, percent), | |
blue: componentAt(2, percent), | |
alpha: componentAt(3, percent) | |
) | |
} | |
} | |
} | |
// ---------------------------------------------------------------------------------------------------- | |
// MARK: - 4. ImplicitDefault | |
// | |
// Hello ImplicitDefault, good old Friend. | |
// ---------------------------------------------------------------------------------------------------- | |
protocol ImplicitDefault { | |
static var implicitDefault: Self { get} | |
} | |
// ---------------------------------------------------------------------------------------------------- | |
// MARK: - 5. ZipWithFillUp | |
// | |
// Fill up with the Default. | |
// ---------------------------------------------------------------------------------------------------- | |
struct ZipWithFillUp<T,U>: Sequence where | |
T: Sequence, | |
U: Sequence, | |
T.Iterator.Element: ImplicitDefault, | |
U.Iterator.Element: ImplicitDefault | |
{ | |
let first: T | |
let second: U | |
init(_ first: T, _ second: U) { | |
self.first = first | |
self.second = second | |
} | |
func makeIterator() -> AnyIterator<(T.Iterator.Element, U.Iterator.Element)> { | |
var generator1 = first.makeIterator() | |
var generator2 = second.makeIterator() | |
return AnyIterator { | |
let element1 = generator1.next() | |
let element2 = generator2.next() | |
switch (element1, element2) { | |
case let (e1?, e2?): | |
return (e1, e2) | |
case let (.none, e2?): | |
return (T.Iterator.Element.implicitDefault, e2) | |
case let (e1?, .none): | |
return (e1, U.Iterator.Element.implicitDefault) | |
case (.none, .none): | |
return nil | |
} | |
} | |
} | |
} | |
extension Sequence where Self.Iterator.Element: ImplicitDefault { | |
func zipWithFillUp<T: Sequence>(sequence: T) -> ZipWithFillUp<Self, T> where T.Iterator.Element: ImplicitDefault{ | |
return ZipWithFillUp(self, sequence) | |
} | |
} | |
// ---------------------------------------------------------------------------------------------------- | |
// MARK: - 6. BarSegment | |
// | |
// We are coming closer to the actual example. | |
// ---------------------------------------------------------------------------------------------------- | |
struct BarSegment { | |
let color: UIColor | |
let percent: CGFloat | |
} | |
extension BarSegment: Interpolation { | |
func interpolate(upTo: BarSegment) -> (Double) -> BarSegment { | |
let color = self.color |~| upTo.color | |
let percent = self.percent |~| upTo.percent | |
return { atPercent in | |
BarSegment(color: color |~> atPercent, percent: percent |~> atPercent) | |
} | |
} | |
} | |
extension BarSegment: ImplicitDefault { | |
static let implicitDefault = BarSegment(color: .black, percent: 0) | |
} | |
// ---------------------------------------------------------------------------------------------------- | |
// MARK: - 7. Drawing | |
// | |
// Easy Cheesy | |
// ---------------------------------------------------------------------------------------------------- | |
func drawBarImage(size: CGSize) -> ([BarSegment]) -> UIImage? { | |
return { bars in | |
drawInUIImage(size: size) { context, bounds in | |
context.setFillColor(UIColor.clear.cgColor) | |
context.fill(bounds) | |
drawBarSegments(bars: bars, rect:CGRect(x: 0, y:0, width: size.width, height: size.height), context:context) | |
} | |
} | |
} | |
func drawBarSegments(bars: [BarSegment], rect: CGRect, context: CGContext){ | |
var sum = CGFloat(0) | |
for barData in bars { | |
let width = rect.width * barData.percent / 100 | |
barData.color.setFill() | |
context.saveGState() | |
context.fill(CGRect(x: sum, y: rect.origin.y, width: width, height: rect.height)) | |
context.restoreGState() | |
sum = sum + width | |
} | |
} | |
func drawInUIImage(size: CGSize, callback: ((CGContext, CGRect) -> ())) -> UIImage? { | |
UIGraphicsBeginImageContextWithOptions(size, false, 0) | |
guard let context = UIGraphicsGetCurrentContext() else { return nil } | |
callback(context, CGRect(origin: CGPoint.zero, size: size)) | |
let image = UIGraphicsGetImageFromCurrentImageContext() | |
UIGraphicsEndImageContext() | |
return image | |
} | |
// ---------------------------------------------------------------------------------------------------- | |
// MARK: - 8. The actual Example | |
// | |
// Let`s pretend the code above is in your framework. | |
// ---------------------------------------------------------------------------------------------------- | |
/// our mini DSL | |
let middle = 0 |~| 100 |~> 0.5 | |
// -> 50 | |
let before = [ | |
BarSegment(color: .red, percent: 10), | |
BarSegment(color: .red, percent: 10), | |
BarSegment(color: .orange, percent: 30), | |
BarSegment(color: .green, percent: 50) | |
] | |
let after = [ | |
BarSegment(color: .green, percent: 50), | |
BarSegment(color: .red, percent: 30), | |
BarSegment(color: .green, percent: 20) | |
] | |
// align your interpolations | |
let interpolations = before.zipWithFillUp(sequence: after).map(|~|) | |
// function to get the interpolations at a certain point | |
let barsAtFraction = { value in interpolations |~> value } | |
// flat map straight to your image | |
let bars = stride(from:0, through: 1, by: 0.01).map(barsAtFraction).flatMap(drawBarImage(size: CGSize(width: 200, height: 30))) | |
// lets show it in the playground | |
let image = UIImage.animatedImage(with: bars, duration: 2) | |
PlaygroundPage.current.liveView = UIImageView(image: image) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
swift 3 update