Skip to content

Instantly share code, notes, and snippets.

@xcadaverx
Created October 7, 2020 18:10
Show Gist options
  • Save xcadaverx/aa9e3dc811834bb88f99400817fcfa72 to your computer and use it in GitHub Desktop.
Save xcadaverx/aa9e3dc811834bb88f99400817fcfa72 to your computer and use it in GitHub Desktop.
[@interpolated] A property wrapper to interpolate between values #swift #ios #propertyWrapper
// MARK: - Interpolatable+Common
extension Double: Interpolatable {
static func makeWithInterpolationValues(interpolationValues: [Double]) -> Double {
interpolationValues[0]
}
var interpolationValues: [Double] { [self] }
}
extension CGFloat: Interpolatable {
static func makeWithInterpolationValues(interpolationValues: [Double]) -> CGFloat {
CGFloat(interpolationValues[0])
}
var interpolationValues: [Double] { [Double(self)] }
}
extension CGPoint: Interpolatable {
static func makeWithInterpolationValues(interpolationValues: [Double]) -> CGPoint {
CGPoint(x: interpolationValues[0], y: interpolationValues[1])
}
var interpolationValues: [Double] { [Double(x), Double(y)] }
}
extension CGSize: Interpolatable {
static func makeWithInterpolationValues(interpolationValues: [Double]) -> CGSize {
let interpolationValues = interpolationValues.map { CGFloat($0) }
return CGSize(width: interpolationValues[0], height: interpolationValues[1])
}
var interpolationValues: [Double] { [Double(width), Double(height)] }
}
extension UIColor: Interpolatable {
static func makeWithInterpolationValues(interpolationValues: [Double]) -> Self {
let interpolationValues = interpolationValues.map { CGFloat($0) }
return UIColor(hue: interpolationValues[0], saturation: interpolationValues[1], brightness: interpolationValues[2], alpha: interpolationValues[3]) as! Self
}
var interpolationValues: [Double] {
var hue: CGFloat = 0, saturation: CGFloat = 0, brightness: CGFloat = 0, alpha: CGFloat = 0
getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: &alpha)
return [hue, saturation, brightness, alpha]
.map(Double.init)
}
}
extension Interpolatable {
static func ...(lhs: Self, rhs: Self) -> InterpolatableRange<Self> {
(lhs, rhs)
}
}
extension Interpolatable {
func scaled<NewValue: Interpolatable>(from originalRange: InterpolatableRange<Self>, to newRange: InterpolatableRange<NewValue>) -> NewValue {
let originalLowerValues = originalRange.lowerBound.interpolationValues
let originalUpperValues = originalRange.upperBound.interpolationValues
let percentage: Double = zip(interpolationValues, zip(originalLowerValues, originalUpperValues))
.map { ($0, $1.0, $1.1) }
.map { (value: Double, min: Double, max: Double) -> Double in
(((value - min) * 100.0) / (max - min))
}
.reduce(0.0, +)
let newLowerValues = newRange.lowerBound.interpolationValues
let newUpperValues = newRange.upperBound.interpolationValues
let newInterpolatedValues = zip(newUpperValues, newLowerValues)
.map {
((percentage / Double(interpolationValues.count)) * ($0 - $1) / 100.0) + $1
}
return NewValue.makeWithInterpolationValues(interpolationValues: newInterpolatedValues)
}
}
// MARK: Interpolatable
protocol Interpolatable {
static func makeWithInterpolationValues(interpolationValues: [Double]) -> Self
var interpolationValues: [Double] { get }
}
// MARK: InterpolatableRange
typealias InterpolatableRange<Value> = (lowerBound: Value, upperBound: Value)
// MARK: Interpolated Property Wrapper
@propertyWrapper
struct Interpolated<T: Interpolatable, U: Interpolatable>: DynamicProperty {
private let originalRange: InterpolatableRange<T>
private let newRange: InterpolatableRange<U>
@State var wrappedValue: T
var projectedValue: U {
wrappedValue.scaled(from: originalRange, to: newRange)
}
init(wrappedValue: T, from originalRange: InterpolatableRange<T>, to newRange: InterpolatableRange<U>) {
self._wrappedValue = State(wrappedValue: wrappedValue)
self.originalRange = originalRange
self.newRange = newRange
}
func binding() -> Binding<T> {
Binding(get: { wrappedValue }, set: { wrappedValue = $0 })
}
}
// MARK: - Usage
struct ContentView: View {
@Interpolated(
from: 0...100,
to: UIColor.red...UIColor.green
) var percentage = 0
var body: some View {
VStack {
Slider(value: _percentage.binding(), in: 0...100)
Rectangle()
.fill(Color($percentage))
Button("Reset to 50%") {
self.percentage = 50
}
}
.padding()
.frame(width: 300, height: 300)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment