Skip to content

Instantly share code, notes, and snippets.

@elm4ward
Last active April 21, 2017 11:59
Show Gist options
  • Save elm4ward/ee3337919d3d261cb180bee88f61b128 to your computer and use it in GitHub Desktop.
Save elm4ward/ee3337919d3d261cb180bee88f61b128 to your computer and use it in GitHub Desktop.
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)
@elm4ward
Copy link
Author

swift 3 update

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment