Created
October 7, 2020 18:10
-
-
Save xcadaverx/aa9e3dc811834bb88f99400817fcfa72 to your computer and use it in GitHub Desktop.
[@interpolated] A property wrapper to interpolate between values #swift #ios #propertyWrapper
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
// 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) | |
} | |
} |
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 Interpolatable { | |
static func ...(lhs: Self, rhs: Self) -> InterpolatableRange<Self> { | |
(lhs, rhs) | |
} | |
} |
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 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) | |
} | |
} |
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
// MARK: Interpolatable | |
protocol Interpolatable { | |
static func makeWithInterpolationValues(interpolationValues: [Double]) -> Self | |
var interpolationValues: [Double] { get } | |
} |
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
// MARK: InterpolatableRange | |
typealias InterpolatableRange<Value> = (lowerBound: Value, upperBound: Value) |
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
// 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 }) | |
} | |
} |
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
// 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