├── .gitignore ├── LICENSE ├── LNInterpolation ├── Color+Interpolation.h ├── Color+Interpolation.m ├── Info.plist ├── LNInterpolable.h ├── LNInterpolable.m ├── LNInterpolation.h ├── LNInterpolation.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ └── LNInterpolation.xcscheme ├── NSValue+Interpolation.h ├── NSValue+Interpolation.mm └── include │ └── LNInterpolation │ ├── Color+Interpolation.h │ ├── LNInterpolable.h │ ├── LNInterpolation.h │ └── NSValue+Interpolation.h ├── LNInterpolationExample ├── LNInterpolationExample.playground │ ├── Pages │ │ ├── Colors.xcplaygroundpage │ │ │ ├── Contents.swift │ │ │ └── timeline.xctimeline │ │ ├── Numbers.xcplaygroundpage │ │ │ └── Contents.swift │ │ └── Values.xcplaygroundpage │ │ │ ├── Contents.swift │ │ │ └── timeline.xctimeline │ ├── contents.xcplayground │ └── playground.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── LNInterpolationExample.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── Package.swift ├── README.md ├── Supplements ├── Colors.png ├── Numbers.png └── Values.png └── releaseVersion.sh /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xcuserstate 23 | 24 | ## Obj-C/Swift specific 25 | *.hmap 26 | *.ipa 27 | *.dSYM.zip 28 | *.dSYM 29 | 30 | # CocoaPods 31 | # 32 | # We recommend against adding the Pods directory to your .gitignore. However 33 | # you should judge for yourself, the pros and cons are mentioned at: 34 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 35 | # 36 | # Pods/ 37 | 38 | # Carthage 39 | # 40 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 41 | # Carthage/Checkouts 42 | 43 | Carthage/Build 44 | 45 | # fastlane 46 | # 47 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 48 | # screenshots whenever they are needed. 49 | # For more information about the recommended setup visit: 50 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md 51 | 52 | fastlane/report.xml 53 | fastlane/screenshots 54 | 55 | #Code Injection 56 | # 57 | # After new code Injection tools there's a generated folder /iOSInjectionProject 58 | # https://github.com/johnno1962/injectionforxcode 59 | 60 | iOSInjectionProject/ 61 | _tmp_release_notes.md 62 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016-2021 Leo Natan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LNInterpolation/Color+Interpolation.h: -------------------------------------------------------------------------------- 1 | // 2 | // Color+Interpolation.h 3 | // 4 | // Created by Leo Natan on 01/10/2016. 5 | // Copyright © 2016 Leo Natan. All rights reserved. 6 | // 7 | 8 | #if __has_include() || __has_include() 9 | 10 | #import 11 | 12 | #if __has_include() 13 | #import 14 | #else 15 | #import 16 | #endif 17 | 18 | /** 19 | Interpolate using the LAB color space for optimal quality. This constant is equal to @c LNUseDefaultInterpolationBehavior. 20 | */ 21 | extern LNInterpolationBehavior const LNInterpolationBehaviorUseLABColorSpace; 22 | 23 | /** 24 | Interpolate using the RGB color space. 25 | */ 26 | extern LNInterpolationBehavior const LNInterpolationBehaviorUseRGBColorSpace; 27 | 28 | /** 29 | Interpolates between colors. 30 | 31 | By default, colors are interpolated in the Lab color space for optimal quality at the expense of some performance. Use @c LNUseRGBInterpolationBehavior for better performance but suboptimal quality. 32 | */ 33 | #if __has_include() 34 | @interface UIColor (LNInterpolation) @end 35 | #else 36 | @interface NSColor (LNInterpolation) @end 37 | #endif 38 | 39 | #endif 40 | -------------------------------------------------------------------------------- /LNInterpolation/Color+Interpolation.m: -------------------------------------------------------------------------------- 1 | // 2 | // Color+Interpolation.m 3 | // 4 | // Created by Leo Natan on 01/10/2016. 5 | // Copyright © 2016 Leo Natan. All rights reserved. 6 | // 7 | 8 | #if __has_include() || __has_include() 9 | 10 | #import "Color+Interpolation.h" 11 | 12 | #if __has_include() 13 | #define Color UIColor 14 | #else 15 | #define Color NSColor 16 | #endif 17 | 18 | #define SWAP(x, y) do { __typeof(x) __ZZZZ__SWAP = x; x = y; y = __ZZZZ__SWAP; } while(0) 19 | 20 | //Same value as LNInterpolationBehaviorUseDefault 21 | LNInterpolationBehavior const LNInterpolationBehaviorUseLABColorSpace = @"LNInterpolationBehaviorUseDefault"; 22 | LNInterpolationBehavior const LNInterpolationBehaviorUseRGBColorSpace = @"LNInterpolationBehaviorUseRGB"; 23 | 24 | extern double LNLinearInterpolate(double from, double to, double p); 25 | 26 | static NSArray* LNRGBComponentsFromColor(Color* color) 27 | { 28 | size_t numberOfComponents = CGColorGetNumberOfComponents(color.CGColor); 29 | const CGFloat* components = CGColorGetComponents(color.CGColor); 30 | 31 | return numberOfComponents == 2 ? @[@(components[0]), @(components[0]), @(components[0]), @(components[1])] : @[@(components[0]), @(components[1]), @(components[2]), @(components[3])]; 32 | } 33 | 34 | static Color* LNColorFromRGBComponents(NSArray* components) 35 | { 36 | return [Color colorWithRed:components[0].doubleValue green:components[1].doubleValue blue:components[2].doubleValue alpha:components[3].doubleValue]; 37 | } 38 | 39 | static NSArray* LNLabComponentsFromColor(Color* color) 40 | { 41 | NSArray* rgbComponents = LNRGBComponentsFromColor(color); 42 | CGFloat r = rgbComponents[0].doubleValue; 43 | CGFloat g = rgbComponents[1].doubleValue; 44 | CGFloat b = rgbComponents[2].doubleValue; 45 | 46 | //RGB -> XYZ 47 | //http://www.brucelindbloom.com/index.html?Eqn_RGB_to_XYZ.html 48 | 49 | r = (r > 0.04045) ? pow((r + 0.055) / 1.055, 2.4) : (r / 12.92); 50 | g = (g > 0.04045) ? pow((g + 0.055) / 1.055, 2.4) : (g / 12.92); 51 | b = (b > 0.04045) ? pow((b + 0.055) / 1.055, 2.4) : (b / 12.92); 52 | 53 | //http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html (sRGB -> XYZ) 54 | CGFloat x = r * 41.24564 + g * 35.75761 + b * 18.04375; 55 | CGFloat y = r * 21.26729 + g * 71.51522 + b * 07.21750; 56 | CGFloat z = r * 01.93339 + g * 11.91920 + b * 95.03041; 57 | 58 | //XYZ -> Lab 59 | //http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_Lab.html 60 | 61 | static const CGFloat eps = 216.0 / 24389.0; 62 | static const CGFloat k = 24389.0 / 27.0; 63 | 64 | x = x / 100.0;//95.047; 65 | y = y / 100.0;//100.000; 66 | z = z / 100.0;//108.883; 67 | 68 | x = x > eps ? pow(x, 1.0 / 3.0) : (k * x + 16.0) / 116.0; 69 | y = y > eps ? pow(y, 1.0 / 3.0) : (k * y + 16.0) / 116.0; 70 | z = z > eps ? pow(z, 1.0 / 3.0) : (k * z + 16.0) / 116.0; 71 | 72 | CGFloat l = 116 * y - 16; 73 | CGFloat a = 500 * (x - y); 74 | b = 200 * (y - z); 75 | 76 | return @[@(l), @(a), @(b), rgbComponents[3]]; 77 | } 78 | 79 | static Color* LNColorFromLabComponents(NSArray* components) 80 | { 81 | CGFloat l = components[0].doubleValue; 82 | CGFloat a = components[1].doubleValue; 83 | CGFloat b = components[2].doubleValue; 84 | 85 | //Lab -> XYZ 86 | //http://www.brucelindbloom.com/index.html?Eqn_Lab_to_XYZ.html 87 | 88 | static const CGFloat eps = 216.0 / 24389.0; 89 | static const CGFloat k = 24389.0 / 27.0; 90 | 91 | CGFloat y = (l + 16.0) / 116.0; 92 | CGFloat x = a / 500 + y; 93 | CGFloat z = y - b / 200; 94 | 95 | x = pow(x, 3.0) > eps ? pow(x, 3.0) : (116 * x - 16) / k; 96 | y = l > k * eps ? pow((l + 16) / 116, 3.0) : l / k; 97 | z = pow(z, 3.0) > eps ? pow(z, 3.0) : (116 * z - 16) / k; 98 | 99 | x = x * 1.0;//.95047; 100 | y = y * 1.0;//1.00000; 101 | z = z * 1.0;//1.08883; 102 | 103 | //XYZ -> RGB 104 | 105 | //http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html (XYZ -> sRGB) 106 | CGFloat r = x * 3.2404542 + y * -1.5371385 + z * -0.4985314; 107 | CGFloat g = x * -0.9692660 + y * 1.8760108 + z * 0.0415560; 108 | b = x * 0.0556434 + y * -0.2040259 + z * 1.0572252; 109 | 110 | r = r <= 0.0031308 ? 12.92 * r : 1.055 * pow(r, 1.0 / 2.4) - 0.055; 111 | g = g <= 0.0031308 ? 12.92 * g : 1.055 * pow(g, 1.0 / 2.4) - 0.055; 112 | b = b <= 0.0031308 ? 12.92 * b : 1.055 * pow(b, 1.0 / 2.4) - 0.055; 113 | 114 | // return Color 115 | return LNColorFromRGBComponents(@[@(r), @(g), @(b), components[3]]); 116 | } 117 | 118 | static inline __attribute__((__always_inline__)) Color* LNInterpolateColor(Color* fromValue, Color* toValue, CGFloat p, NSArray* (*compConverter)(Color*), Color* (*colorConverter)(NSArray*)) 119 | { 120 | NSArray* arrayC1 = compConverter(fromValue); 121 | NSArray* arrayC2 = compConverter(toValue); 122 | 123 | NSMutableArray* arrayOutput = [NSMutableArray new]; 124 | [arrayC1 enumerateObjectsUsingBlock:^(NSNumber * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 125 | arrayOutput[idx] = @(LNLinearInterpolate(obj.doubleValue, arrayC2[idx].doubleValue, p)); 126 | }]; 127 | 128 | return colorConverter(arrayOutput); 129 | } 130 | 131 | @implementation Color (Interpolation) 132 | 133 | - (instancetype)interpolateToValue:(Color*)toValue progress:(double)p 134 | { 135 | return [self interpolateToValue:toValue progress:p behavior:LNInterpolationBehaviorUseDefault]; 136 | } 137 | 138 | - (instancetype)interpolateToValue:(id)toValue progress:(double)p behavior:(LNInterpolationBehavior)behavior 139 | { 140 | NSParameterAssert([toValue isKindOfClass:[Color class]]); 141 | 142 | if(p <= 0) 143 | { 144 | return self; 145 | } 146 | 147 | if(p >= 1) 148 | { 149 | return toValue; 150 | } 151 | 152 | return LNInterpolateColor(self, toValue, p, behavior == LNInterpolationBehaviorUseRGBColorSpace ? LNRGBComponentsFromColor : LNLabComponentsFromColor, behavior == LNInterpolationBehaviorUseRGBColorSpace ? LNColorFromRGBComponents : LNColorFromLabComponents); 153 | } 154 | 155 | @end 156 | 157 | #endif 158 | -------------------------------------------------------------------------------- /LNInterpolation/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.1 19 | CFBundleVersion 20 | 1 21 | NSHumanReadableCopyright 22 | Copyright © 2016-2021 Leo Natan. All rights reserved. 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /LNInterpolation/LNInterpolable.h: -------------------------------------------------------------------------------- 1 | // 2 | // LNInterpolable.h 3 | // 4 | // Created by Leo Natan on 01/10/2016. 5 | // Copyright © 2016 Leo Natan. All rights reserved. 6 | // 7 | 8 | #import 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | NS_SWIFT_NAME(InterpolationBehavior) 13 | typedef const NSString* LNInterpolationBehavior NS_TYPED_EXTENSIBLE_ENUM; 14 | 15 | /** 16 | Interpolate using the default behavor of each implementation. 17 | */ 18 | extern LNInterpolationBehavior const LNInterpolationBehaviorUseDefault; 19 | 20 | 21 | /** 22 | Classes implementing this protocol support interpolation. 23 | */ 24 | NS_SWIFT_NAME(Interpolable) 25 | @protocol LNInterpolable 26 | 27 | /** 28 | Interpolates between @c self and @c toValue accodring to @c progress using the default behavior. 29 | 30 | @param toValue The value to interpolate to 31 | @param progress The progress of the interpolation 32 | @return An object representing the interpolated value at the requested progress 33 | */ 34 | - (instancetype)interpolateToValue:(id)toValue progress:(double)progress NS_SWIFT_NAME(interpolate(to:progress:)); 35 | 36 | /** 37 | Interpolates between @c self and @c toValue according to @c progress using @c behavior. 38 | 39 | @param toValue The value to interpolate to 40 | @param behavior The bahvior to use for interpolation 41 | @param progress The progress of the interpolation 42 | @return An object representing the interpolated value at the requested progress 43 | */ 44 | - (instancetype)interpolateToValue:(id)toValue progress:(double)progress behavior:(LNInterpolationBehavior)behavior NS_SWIFT_NAME(interpolate(to:progress:behavior:)); 45 | 46 | NS_ASSUME_NONNULL_END 47 | 48 | @end 49 | -------------------------------------------------------------------------------- /LNInterpolation/LNInterpolable.m: -------------------------------------------------------------------------------- 1 | // 2 | // LNInterpolable.c 3 | // 4 | // Created by Leo Natan on 04/10/2016. 5 | // Copyright © 2016 Leo Natan. All rights reserved. 6 | // 7 | 8 | #import "LNInterpolable.h" 9 | 10 | LNInterpolationBehavior const LNInterpolationBehaviorUseDefault = @"LNInterpolationBehaviorUseDefault"; 11 | 12 | double LNLinearInterpolate(double from, double to, double p) 13 | { 14 | return from + p * (to - from); 15 | } 16 | -------------------------------------------------------------------------------- /LNInterpolation/LNInterpolation.h: -------------------------------------------------------------------------------- 1 | // 2 | // LNInterpolation.h 3 | // 4 | // Created by Leo Natan on 05/10/2016. 5 | // Copyright © 2016 Leo Natan. All rights reserved. 6 | // 7 | 8 | #import 9 | #import 10 | #import 11 | 12 | //! Project version number for LNInterpolationFramework. 13 | FOUNDATION_EXPORT double LNInterpolationFrameworkVersionNumber; 14 | 15 | //! Project version string for LNInterpolationFramework. 16 | FOUNDATION_EXPORT const unsigned char LNInterpolationFrameworkVersionString[]; 17 | -------------------------------------------------------------------------------- /LNInterpolation/LNInterpolation.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 392B81EA1DAD575B00EF31DD /* Color+Interpolation.h in Headers */ = {isa = PBXBuildFile; fileRef = 392B81E21DAD575B00EF31DD /* Color+Interpolation.h */; settings = {ATTRIBUTES = (Public, ); }; }; 11 | 392B81EB1DAD575B00EF31DD /* Color+Interpolation.m in Sources */ = {isa = PBXBuildFile; fileRef = 392B81E31DAD575B00EF31DD /* Color+Interpolation.m */; }; 12 | 392B81EC1DAD575B00EF31DD /* LNInterpolable.h in Headers */ = {isa = PBXBuildFile; fileRef = 392B81E41DAD575B00EF31DD /* LNInterpolable.h */; settings = {ATTRIBUTES = (Public, ); }; }; 13 | 392B81ED1DAD575B00EF31DD /* LNInterpolable.m in Sources */ = {isa = PBXBuildFile; fileRef = 392B81E51DAD575B00EF31DD /* LNInterpolable.m */; }; 14 | 392B81EE1DAD575B00EF31DD /* LNInterpolation.h in Headers */ = {isa = PBXBuildFile; fileRef = 392B81E61DAD575B00EF31DD /* LNInterpolation.h */; settings = {ATTRIBUTES = (Public, ); }; }; 15 | 392B81F01DAD575B00EF31DD /* NSValue+Interpolation.h in Headers */ = {isa = PBXBuildFile; fileRef = 392B81E81DAD575B00EF31DD /* NSValue+Interpolation.h */; settings = {ATTRIBUTES = (Public, ); }; }; 16 | 392B81F11DAD575B00EF31DD /* NSValue+Interpolation.mm in Sources */ = {isa = PBXBuildFile; fileRef = 392B81E91DAD575B00EF31DD /* NSValue+Interpolation.mm */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXFileReference section */ 20 | 392B81E21DAD575B00EF31DD /* Color+Interpolation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "Color+Interpolation.h"; sourceTree = SOURCE_ROOT; }; 21 | 392B81E31DAD575B00EF31DD /* Color+Interpolation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "Color+Interpolation.m"; sourceTree = SOURCE_ROOT; }; 22 | 392B81E41DAD575B00EF31DD /* LNInterpolable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LNInterpolable.h; sourceTree = SOURCE_ROOT; }; 23 | 392B81E51DAD575B00EF31DD /* LNInterpolable.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LNInterpolable.m; sourceTree = SOURCE_ROOT; }; 24 | 392B81E61DAD575B00EF31DD /* LNInterpolation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LNInterpolation.h; sourceTree = SOURCE_ROOT; }; 25 | 392B81E81DAD575B00EF31DD /* NSValue+Interpolation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSValue+Interpolation.h"; sourceTree = SOURCE_ROOT; }; 26 | 392B81E91DAD575B00EF31DD /* NSValue+Interpolation.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "NSValue+Interpolation.mm"; sourceTree = SOURCE_ROOT; }; 27 | 39EE692B1DA4E1B100F19FA9 /* LNInterpolation.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = LNInterpolation.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 28 | /* End PBXFileReference section */ 29 | 30 | /* Begin PBXFrameworksBuildPhase section */ 31 | 39EE69271DA4E1B100F19FA9 /* Frameworks */ = { 32 | isa = PBXFrameworksBuildPhase; 33 | buildActionMask = 2147483647; 34 | files = ( 35 | ); 36 | runOnlyForDeploymentPostprocessing = 0; 37 | }; 38 | /* End PBXFrameworksBuildPhase section */ 39 | 40 | /* Begin PBXGroup section */ 41 | 39EE69211DA4E1B100F19FA9 = { 42 | isa = PBXGroup; 43 | children = ( 44 | 39EE692D1DA4E1B100F19FA9 /* LNInterpolation */, 45 | 39EE692C1DA4E1B100F19FA9 /* Products */, 46 | ); 47 | sourceTree = ""; 48 | }; 49 | 39EE692C1DA4E1B100F19FA9 /* Products */ = { 50 | isa = PBXGroup; 51 | children = ( 52 | 39EE692B1DA4E1B100F19FA9 /* LNInterpolation.framework */, 53 | ); 54 | name = Products; 55 | sourceTree = ""; 56 | }; 57 | 39EE692D1DA4E1B100F19FA9 /* LNInterpolation */ = { 58 | isa = PBXGroup; 59 | children = ( 60 | 392B81E61DAD575B00EF31DD /* LNInterpolation.h */, 61 | 392B81E41DAD575B00EF31DD /* LNInterpolable.h */, 62 | 392B81E51DAD575B00EF31DD /* LNInterpolable.m */, 63 | 392B81E81DAD575B00EF31DD /* NSValue+Interpolation.h */, 64 | 392B81E91DAD575B00EF31DD /* NSValue+Interpolation.mm */, 65 | 392B81E21DAD575B00EF31DD /* Color+Interpolation.h */, 66 | 392B81E31DAD575B00EF31DD /* Color+Interpolation.m */, 67 | ); 68 | name = LNInterpolation; 69 | sourceTree = ""; 70 | }; 71 | /* End PBXGroup section */ 72 | 73 | /* Begin PBXHeadersBuildPhase section */ 74 | 39EE69281DA4E1B100F19FA9 /* Headers */ = { 75 | isa = PBXHeadersBuildPhase; 76 | buildActionMask = 2147483647; 77 | files = ( 78 | 392B81EA1DAD575B00EF31DD /* Color+Interpolation.h in Headers */, 79 | 392B81EE1DAD575B00EF31DD /* LNInterpolation.h in Headers */, 80 | 392B81F01DAD575B00EF31DD /* NSValue+Interpolation.h in Headers */, 81 | 392B81EC1DAD575B00EF31DD /* LNInterpolable.h in Headers */, 82 | ); 83 | runOnlyForDeploymentPostprocessing = 0; 84 | }; 85 | /* End PBXHeadersBuildPhase section */ 86 | 87 | /* Begin PBXNativeTarget section */ 88 | 39EE692A1DA4E1B100F19FA9 /* LNInterpolation */ = { 89 | isa = PBXNativeTarget; 90 | buildConfigurationList = 39EE69331DA4E1B100F19FA9 /* Build configuration list for PBXNativeTarget "LNInterpolation" */; 91 | buildPhases = ( 92 | 39EE69281DA4E1B100F19FA9 /* Headers */, 93 | 39EE69271DA4E1B100F19FA9 /* Frameworks */, 94 | 39EE69291DA4E1B100F19FA9 /* Resources */, 95 | 39EE69261DA4E1B100F19FA9 /* Sources */, 96 | ); 97 | buildRules = ( 98 | ); 99 | dependencies = ( 100 | ); 101 | name = LNInterpolation; 102 | productName = LNInterpolation; 103 | productReference = 39EE692B1DA4E1B100F19FA9 /* LNInterpolation.framework */; 104 | productType = "com.apple.product-type.framework"; 105 | }; 106 | /* End PBXNativeTarget section */ 107 | 108 | /* Begin PBXProject section */ 109 | 39EE69221DA4E1B100F19FA9 /* Project object */ = { 110 | isa = PBXProject; 111 | attributes = { 112 | LastUpgradeCheck = 1200; 113 | ORGANIZATIONNAME = "Leo Natan"; 114 | TargetAttributes = { 115 | 39EE692A1DA4E1B100F19FA9 = { 116 | CreatedOnToolsVersion = 8.1; 117 | LastSwiftMigration = 0810; 118 | ProvisioningStyle = Automatic; 119 | }; 120 | }; 121 | }; 122 | buildConfigurationList = 39EE69251DA4E1B100F19FA9 /* Build configuration list for PBXProject "LNInterpolation" */; 123 | compatibilityVersion = "Xcode 3.2"; 124 | developmentRegion = en; 125 | hasScannedForEncodings = 0; 126 | knownRegions = ( 127 | en, 128 | Base, 129 | ); 130 | mainGroup = 39EE69211DA4E1B100F19FA9; 131 | productRefGroup = 39EE692C1DA4E1B100F19FA9 /* Products */; 132 | projectDirPath = ""; 133 | projectRoot = ""; 134 | targets = ( 135 | 39EE692A1DA4E1B100F19FA9 /* LNInterpolation */, 136 | ); 137 | }; 138 | /* End PBXProject section */ 139 | 140 | /* Begin PBXResourcesBuildPhase section */ 141 | 39EE69291DA4E1B100F19FA9 /* Resources */ = { 142 | isa = PBXResourcesBuildPhase; 143 | buildActionMask = 2147483647; 144 | files = ( 145 | ); 146 | runOnlyForDeploymentPostprocessing = 0; 147 | }; 148 | /* End PBXResourcesBuildPhase section */ 149 | 150 | /* Begin PBXSourcesBuildPhase section */ 151 | 39EE69261DA4E1B100F19FA9 /* Sources */ = { 152 | isa = PBXSourcesBuildPhase; 153 | buildActionMask = 2147483647; 154 | files = ( 155 | 392B81F11DAD575B00EF31DD /* NSValue+Interpolation.mm in Sources */, 156 | 392B81ED1DAD575B00EF31DD /* LNInterpolable.m in Sources */, 157 | 392B81EB1DAD575B00EF31DD /* Color+Interpolation.m in Sources */, 158 | ); 159 | runOnlyForDeploymentPostprocessing = 0; 160 | }; 161 | /* End PBXSourcesBuildPhase section */ 162 | 163 | /* Begin XCBuildConfiguration section */ 164 | 39EE69311DA4E1B100F19FA9 /* Debug */ = { 165 | isa = XCBuildConfiguration; 166 | buildSettings = { 167 | ALWAYS_SEARCH_USER_PATHS = NO; 168 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 169 | CLANG_ANALYZER_NONNULL = YES; 170 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 171 | CLANG_CXX_LIBRARY = "libc++"; 172 | CLANG_ENABLE_MODULES = YES; 173 | CLANG_ENABLE_OBJC_ARC = YES; 174 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 175 | CLANG_WARN_BOOL_CONVERSION = YES; 176 | CLANG_WARN_COMMA = YES; 177 | CLANG_WARN_CONSTANT_CONVERSION = YES; 178 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 179 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 180 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 181 | CLANG_WARN_EMPTY_BODY = YES; 182 | CLANG_WARN_ENUM_CONVERSION = YES; 183 | CLANG_WARN_INFINITE_RECURSION = YES; 184 | CLANG_WARN_INT_CONVERSION = YES; 185 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 186 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 187 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 188 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 189 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 190 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 191 | CLANG_WARN_STRICT_PROTOTYPES = YES; 192 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 193 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 194 | CLANG_WARN_UNREACHABLE_CODE = YES; 195 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 196 | CODE_SIGN_IDENTITY = "-"; 197 | COPY_PHASE_STRIP = NO; 198 | CURRENT_PROJECT_VERSION = 1; 199 | DEBUG_INFORMATION_FORMAT = dwarf; 200 | ENABLE_STRICT_OBJC_MSGSEND = YES; 201 | ENABLE_TESTABILITY = YES; 202 | GCC_C_LANGUAGE_STANDARD = gnu99; 203 | GCC_DYNAMIC_NO_PIC = NO; 204 | GCC_NO_COMMON_BLOCKS = YES; 205 | GCC_OPTIMIZATION_LEVEL = 0; 206 | GCC_PREPROCESSOR_DEFINITIONS = ( 207 | "DEBUG=1", 208 | "$(inherited)", 209 | ); 210 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 211 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 212 | GCC_WARN_UNDECLARED_SELECTOR = YES; 213 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 214 | GCC_WARN_UNUSED_FUNCTION = YES; 215 | GCC_WARN_UNUSED_VARIABLE = YES; 216 | MACOSX_DEPLOYMENT_TARGET = 10.10; 217 | MTL_ENABLE_DEBUG_INFO = YES; 218 | ONLY_ACTIVE_ARCH = YES; 219 | SDKROOT = macosx; 220 | SUPPORTED_PLATFORMS = "macosx iphoneos iphonesimulator watchos watchsimulator appletvos appletvsimulator"; 221 | VALID_ARCHS = "i386 x86_64 armv7 armv7s arm64"; 222 | VERSIONING_SYSTEM = "apple-generic"; 223 | VERSION_INFO_PREFIX = ""; 224 | WATCHOS_DEPLOYMENT_TARGET = 2.0; 225 | }; 226 | name = Debug; 227 | }; 228 | 39EE69321DA4E1B100F19FA9 /* Release */ = { 229 | isa = XCBuildConfiguration; 230 | buildSettings = { 231 | ALWAYS_SEARCH_USER_PATHS = NO; 232 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 233 | CLANG_ANALYZER_NONNULL = YES; 234 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 235 | CLANG_CXX_LIBRARY = "libc++"; 236 | CLANG_ENABLE_MODULES = YES; 237 | CLANG_ENABLE_OBJC_ARC = YES; 238 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 239 | CLANG_WARN_BOOL_CONVERSION = YES; 240 | CLANG_WARN_COMMA = YES; 241 | CLANG_WARN_CONSTANT_CONVERSION = YES; 242 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 243 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 244 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 245 | CLANG_WARN_EMPTY_BODY = YES; 246 | CLANG_WARN_ENUM_CONVERSION = YES; 247 | CLANG_WARN_INFINITE_RECURSION = YES; 248 | CLANG_WARN_INT_CONVERSION = YES; 249 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 250 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 251 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 252 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 253 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 254 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 255 | CLANG_WARN_STRICT_PROTOTYPES = YES; 256 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 257 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 258 | CLANG_WARN_UNREACHABLE_CODE = YES; 259 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 260 | CODE_SIGN_IDENTITY = "-"; 261 | COPY_PHASE_STRIP = NO; 262 | CURRENT_PROJECT_VERSION = 1; 263 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 264 | ENABLE_NS_ASSERTIONS = NO; 265 | ENABLE_STRICT_OBJC_MSGSEND = YES; 266 | GCC_C_LANGUAGE_STANDARD = gnu99; 267 | GCC_NO_COMMON_BLOCKS = YES; 268 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 269 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 270 | GCC_WARN_UNDECLARED_SELECTOR = YES; 271 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 272 | GCC_WARN_UNUSED_FUNCTION = YES; 273 | GCC_WARN_UNUSED_VARIABLE = YES; 274 | MACOSX_DEPLOYMENT_TARGET = 10.10; 275 | MTL_ENABLE_DEBUG_INFO = NO; 276 | SDKROOT = macosx; 277 | SUPPORTED_PLATFORMS = "macosx iphoneos iphonesimulator watchos watchsimulator appletvos appletvsimulator"; 278 | VALID_ARCHS = "i386 x86_64 armv7 armv7s arm64"; 279 | VERSIONING_SYSTEM = "apple-generic"; 280 | VERSION_INFO_PREFIX = ""; 281 | WATCHOS_DEPLOYMENT_TARGET = 2.0; 282 | }; 283 | name = Release; 284 | }; 285 | 39EE69341DA4E1B100F19FA9 /* Debug */ = { 286 | isa = XCBuildConfiguration; 287 | buildSettings = { 288 | APPLICATION_EXTENSION_API_ONLY = YES; 289 | CLANG_ENABLE_MODULES = YES; 290 | CODE_SIGN_IDENTITY = ""; 291 | COMBINE_HIDPI_IMAGES = YES; 292 | DEFINES_MODULE = YES; 293 | DYLIB_COMPATIBILITY_VERSION = 1; 294 | DYLIB_CURRENT_VERSION = 1; 295 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 296 | FRAMEWORK_VERSION = A; 297 | INFOPLIST_FILE = Info.plist; 298 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 299 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 300 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 301 | MACOSX_DEPLOYMENT_TARGET = 10.13; 302 | PRODUCT_BUNDLE_IDENTIFIER = com.leonatan.LNInterpolation; 303 | PRODUCT_NAME = "$(TARGET_NAME)"; 304 | SKIP_INSTALL = YES; 305 | TVOS_DEPLOYMENT_TARGET = 12.0; 306 | WATCHOS_DEPLOYMENT_TARGET = 4.0; 307 | }; 308 | name = Debug; 309 | }; 310 | 39EE69351DA4E1B100F19FA9 /* Release */ = { 311 | isa = XCBuildConfiguration; 312 | buildSettings = { 313 | APPLICATION_EXTENSION_API_ONLY = YES; 314 | CLANG_ENABLE_MODULES = YES; 315 | CODE_SIGN_IDENTITY = ""; 316 | COMBINE_HIDPI_IMAGES = YES; 317 | DEFINES_MODULE = YES; 318 | DYLIB_COMPATIBILITY_VERSION = 1; 319 | DYLIB_CURRENT_VERSION = 1; 320 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 321 | FRAMEWORK_VERSION = A; 322 | INFOPLIST_FILE = Info.plist; 323 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 324 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 325 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 326 | MACOSX_DEPLOYMENT_TARGET = 10.13; 327 | PRODUCT_BUNDLE_IDENTIFIER = com.leonatan.LNInterpolation; 328 | PRODUCT_NAME = "$(TARGET_NAME)"; 329 | SKIP_INSTALL = YES; 330 | TVOS_DEPLOYMENT_TARGET = 12.0; 331 | WATCHOS_DEPLOYMENT_TARGET = 4.0; 332 | }; 333 | name = Release; 334 | }; 335 | /* End XCBuildConfiguration section */ 336 | 337 | /* Begin XCConfigurationList section */ 338 | 39EE69251DA4E1B100F19FA9 /* Build configuration list for PBXProject "LNInterpolation" */ = { 339 | isa = XCConfigurationList; 340 | buildConfigurations = ( 341 | 39EE69311DA4E1B100F19FA9 /* Debug */, 342 | 39EE69321DA4E1B100F19FA9 /* Release */, 343 | ); 344 | defaultConfigurationIsVisible = 0; 345 | defaultConfigurationName = Release; 346 | }; 347 | 39EE69331DA4E1B100F19FA9 /* Build configuration list for PBXNativeTarget "LNInterpolation" */ = { 348 | isa = XCConfigurationList; 349 | buildConfigurations = ( 350 | 39EE69341DA4E1B100F19FA9 /* Debug */, 351 | 39EE69351DA4E1B100F19FA9 /* Release */, 352 | ); 353 | defaultConfigurationIsVisible = 0; 354 | defaultConfigurationName = Release; 355 | }; 356 | /* End XCConfigurationList section */ 357 | }; 358 | rootObject = 39EE69221DA4E1B100F19FA9 /* Project object */; 359 | } 360 | -------------------------------------------------------------------------------- /LNInterpolation/LNInterpolation.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /LNInterpolation/LNInterpolation.xcodeproj/xcshareddata/xcschemes/LNInterpolation.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 52 | 53 | 59 | 60 | 66 | 67 | 68 | 69 | 71 | 72 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /LNInterpolation/NSValue+Interpolation.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSValue+Interpolation.h 3 | // 4 | // Created by Leo Natan on 01/10/2016. 5 | // Copyright © 2016 Leo Natan. All rights reserved. 6 | // 7 | 8 | #import 9 | #import 10 | 11 | /** 12 | Interpolates between @c NSValue objects. 13 | Currently, the following value types are supported: 14 | Numbers (@c NSNumber), decimal numbers (@c NSDecimalNumber) 15 | Core Graphics: CGPoint, CGSize, CGVector, CGRect, CGAffineTransform (@c NSValue) 16 | UIKit: UIOffset, UIEdgeInsets (@c NSValue) 17 | AppKit: NSEdgeInsets (@c NSValue) 18 | */ 19 | @interface NSValue (Interpolation) @end 20 | -------------------------------------------------------------------------------- /LNInterpolation/NSValue+Interpolation.mm: -------------------------------------------------------------------------------- 1 | // 2 | // NSValue+Interpolation.mm 3 | // 4 | // Created by Leo Natan on 01/10/2016. 5 | // Copyright © 2016 Leo Natan. All rights reserved. 6 | // 7 | 8 | #import "NSValue+Interpolation.h" 9 | 10 | #if __has_include() 11 | #import 12 | #endif 13 | 14 | #if __has_include() 15 | #import 16 | #endif 17 | 18 | #if __has_include() 19 | #import 20 | 21 | #define CGPointValue pointValue 22 | #define CGRectValue rectValue 23 | #define valueWithCGPoint valueWithPoint 24 | #define UIEdgeInsets NSEdgeInsets 25 | #define valueWithCGRect valueWithRect 26 | 27 | #endif 28 | 29 | extern "C" double LNLinearInterpolate(double from, double to, double p); 30 | 31 | #if __has_include() 32 | static inline CGAffineTransform _LNInterpolateCGAffineTransform(const CGAffineTransform& fromTransform, const CGAffineTransform& toTransform, CGFloat p) 33 | { 34 | CGAffineTransform rv; 35 | rv.a = LNLinearInterpolate(fromTransform.a, toTransform.a, p); 36 | rv.b = LNLinearInterpolate(fromTransform.b, toTransform.b, p); 37 | rv.c = LNLinearInterpolate(fromTransform.c, toTransform.c, p); 38 | rv.d = LNLinearInterpolate(fromTransform.d, toTransform.d, p); 39 | rv.tx = LNLinearInterpolate(fromTransform.tx, toTransform.tx, p); 40 | rv.ty = LNLinearInterpolate(fromTransform.ty, toTransform.ty, p); 41 | 42 | return rv; 43 | } 44 | #endif 45 | 46 | @implementation NSValue (Interpolation) 47 | 48 | - (instancetype)interpolateToValue:(id)toValue progress:(double)p 49 | { 50 | return [self interpolateToValue:toValue progress:p behavior:LNInterpolationBehaviorUseDefault]; 51 | } 52 | 53 | - (instancetype)interpolateToValue:(id)toValue progress:(double)p behavior:(LNInterpolationBehavior)behavior 54 | { 55 | if(p <= 0) 56 | { 57 | return self; 58 | } 59 | 60 | if(p >= 1) 61 | { 62 | return toValue; 63 | } 64 | 65 | if([self isKindOfClass:[NSNumber class]]) 66 | { 67 | if([self isKindOfClass:[NSDecimalNumber class]]) 68 | { 69 | //Special case for decimal numbers. 70 | NSDecimalNumber* from = (id)self; 71 | NSDecimalNumber* to = (id)toValue; 72 | 73 | return [[[to decimalNumberBySubtracting:from] decimalNumberByMultiplyingBy:[[NSDecimalNumber alloc] initWithDouble:p]] decimalNumberByAdding:from]; 74 | } 75 | 76 | double f = [(NSNumber*)self doubleValue]; 77 | double f2 = [(NSNumber*)toValue doubleValue]; 78 | 79 | return [NSNumber numberWithDouble:LNLinearInterpolate(f, f2, p)]; 80 | } 81 | 82 | #if __has_include() || __has_include() 83 | if((strcmp(self.objCType, @encode(CGPoint)) == 0) || (strcmp(self.objCType, @encode(CGSize)) == 0) || (strcmp(self.objCType, @encode(CGVector)) == 0) 84 | #if __has_include() 85 | || (strcmp(self.objCType, @encode(UIOffset)) == 0) 86 | #endif 87 | ) 88 | { 89 | CGPoint v = [self CGPointValue]; 90 | CGPoint v2 = [self CGPointValue]; 91 | v.x = LNLinearInterpolate(v.x, v2.y, p); 92 | v.y = LNLinearInterpolate(v.x, v2.y, p); 93 | 94 | return [NSValue valueWithCGPoint:v]; 95 | } 96 | 97 | if((strcmp(self.objCType, @encode(CGRect)) == 0) 98 | || (strcmp(self.objCType, @encode(UIEdgeInsets)) == 0)) 99 | { 100 | CGRect v = [self CGRectValue]; 101 | CGRect v2 = [toValue CGRectValue]; 102 | v.origin.x = LNLinearInterpolate(v.origin.x, v2.origin.x, p); 103 | v.origin.y = LNLinearInterpolate(v.origin.y, v2.origin.y, p); 104 | v.size.width = LNLinearInterpolate(v.size.width, v2.size.width, p); 105 | v.size.height = LNLinearInterpolate(v.size.height, v2.size.height, p); 106 | 107 | return [NSValue valueWithCGRect:v]; 108 | } 109 | 110 | #if __has_include() 111 | if(strcmp(self.objCType, @encode(CGAffineTransform)) == 0) 112 | { 113 | return [NSValue valueWithCGAffineTransform:_LNInterpolateCGAffineTransform([self CGAffineTransformValue], [toValue CGAffineTransformValue], p)]; 114 | } 115 | #endif 116 | #endif 117 | 118 | //Unsupported value type. 119 | 120 | return self; 121 | } 122 | 123 | @end 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | -------------------------------------------------------------------------------- /LNInterpolation/include/LNInterpolation/Color+Interpolation.h: -------------------------------------------------------------------------------- 1 | ../../Color+Interpolation.h -------------------------------------------------------------------------------- /LNInterpolation/include/LNInterpolation/LNInterpolable.h: -------------------------------------------------------------------------------- 1 | ../../LNInterpolable.h -------------------------------------------------------------------------------- /LNInterpolation/include/LNInterpolation/LNInterpolation.h: -------------------------------------------------------------------------------- 1 | ../../LNInterpolation.h -------------------------------------------------------------------------------- /LNInterpolation/include/LNInterpolation/NSValue+Interpolation.h: -------------------------------------------------------------------------------- 1 | ../../NSValue+Interpolation.h -------------------------------------------------------------------------------- /LNInterpolationExample/LNInterpolationExample.playground/Pages/Colors.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | import AppKit 2 | import CoreGraphics 3 | import LNInterpolation 4 | import PlaygroundSupport 5 | 6 | let fromColor = #colorLiteral(red: 1, green: 1, blue: 0, alpha: 1) 7 | let toColor = #colorLiteral(red: 0, green: 0, blue: 1, alpha: 1) 8 | 9 | fromColor.interpolate(to: toColor, progress: 0.0) 10 | fromColor.interpolate(to: toColor, progress: 0.1) 11 | fromColor.interpolate(to: toColor, progress: 0.2) 12 | fromColor.interpolate(to: toColor, progress: 0.3) 13 | fromColor.interpolate(to: toColor, progress: 0.4) 14 | fromColor.interpolate(to: toColor, progress: 0.5) 15 | fromColor.interpolate(to: toColor, progress: 0.6) 16 | fromColor.interpolate(to: toColor, progress: 0.7) 17 | fromColor.interpolate(to: toColor, progress: 0.8) 18 | fromColor.interpolate(to: toColor, progress: 0.9) 19 | fromColor.interpolate(to: toColor, progress: 1.0) 20 | 21 | let width = 512 22 | let height = 300 23 | 24 | func imageWith(behavior: InterpolationBehavior) -> NSImage { 25 | let ctx = CGContext(data: nil, width: width, height: height, bitsPerComponent: 8, 26 | bytesPerRow: 0, space: NSColorSpace.sRGB.cgColorSpace!, 27 | bitmapInfo: CGImageAlphaInfo.premultipliedFirst.rawValue)! 28 | 29 | ctx.setLineWidth(1.0) 30 | 31 | for p in stride(from: 0.0, through: 1.0, by: 1.0 / Double(width - 1)) { 32 | let interpolatedColor = fromColor.interpolate(to: toColor, progress: p, behavior: behavior) 33 | ctx.setStrokeColor(interpolatedColor.cgColor) 34 | ctx.move(to: CGPoint(x: p * Double(ctx.width - 1), y: 0.0)) 35 | ctx.addLine(to: CGPoint(x: p * Double(ctx.width - 1), y: Double(height))) 36 | ctx.strokePath() 37 | } 38 | 39 | return NSImage(cgImage: ctx.makeImage()!, size: NSSize(width: width, height: height)) 40 | } 41 | 42 | imageWith(behavior: .useDefault) 43 | 44 | imageWith(behavior: .useRGBColorSpace) 45 | -------------------------------------------------------------------------------- /LNInterpolationExample/LNInterpolationExample.playground/Pages/Colors.xcplaygroundpage/timeline.xctimeline: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | 24 | 25 | 29 | 30 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /LNInterpolationExample/LNInterpolationExample.playground/Pages/Numbers.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import LNInterpolation 3 | 4 | let a : NSNumber = 20 5 | let b : NSNumber = 100 6 | 7 | a.interpolate(to: b, progress: 0.5) 8 | 9 | let c : NSNumber = 1.1 10 | let d : NSNumber = 3.3 11 | 12 | c.interpolate(to: d, progress: 0.2) 13 | 14 | let e = NSDecimalNumber(string: "10e10") 15 | let f = NSDecimalNumber(string: "10e20") 16 | 17 | e.interpolate(to: f, progress: 0.7) 18 | -------------------------------------------------------------------------------- /LNInterpolationExample/LNInterpolationExample.playground/Pages/Values.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import LNInterpolation 3 | 4 | let smallRect = NSRect(x: 0.0, y: 0.0, width: 20.0, height: 20.0) 5 | let largeRect = NSRect(x: 20.0, y: 20.0, width: 100.0, height: 100.0) 6 | 7 | (smallRect as NSValue).interpolate(to: largeRect, progress: 0.1) 8 | (smallRect as NSValue).interpolate(to: largeRect, progress: 0.2) 9 | (smallRect as NSValue).interpolate(to: largeRect, progress: 0.3) 10 | (smallRect as NSValue).interpolate(to: largeRect, progress: 0.4) 11 | (smallRect as NSValue).interpolate(to: largeRect, progress: 0.5) 12 | (smallRect as NSValue).interpolate(to: largeRect, progress: 0.6) 13 | (smallRect as NSValue).interpolate(to: largeRect, progress: 0.7) 14 | (smallRect as NSValue).interpolate(to: largeRect, progress: 0.8) 15 | (smallRect as NSValue).interpolate(to: largeRect, progress: 0.9) 16 | (smallRect as NSValue).interpolate(to: largeRect, progress: 1.0) 17 | -------------------------------------------------------------------------------- /LNInterpolationExample/LNInterpolationExample.playground/Pages/Values.xcplaygroundpage/timeline.xctimeline: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /LNInterpolationExample/LNInterpolationExample.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /LNInterpolationExample/LNInterpolationExample.playground/playground.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /LNInterpolationExample/LNInterpolationExample.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /LNInterpolationExample/LNInterpolationExample.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /LNInterpolationExample/LNInterpolationExample.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.1 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "LNInterpolation", 7 | platforms: [ 8 | .iOS(.v13) 9 | ], 10 | products: [ 11 | .library( 12 | name: "LNInterpolation", 13 | type: .dynamic, 14 | targets: ["LNInterpolation"]), 15 | .library( 16 | name: "LNInterpolation-Static", 17 | type: .static, 18 | targets: ["LNInterpolation"]), 19 | ], 20 | dependencies: [], 21 | targets: [ 22 | .target( 23 | name: "LNInterpolation", 24 | dependencies: [], 25 | path: "LNInterpolation", 26 | exclude: [ 27 | "Info.plist", 28 | "LNInterpolation.xcodeproj" 29 | ], 30 | publicHeadersPath: "include", 31 | cSettings: [ 32 | .headerSearchPath("."), 33 | ]), 34 | ] 35 | ) 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LNInterpolation 2 | 3 | An interpolation framework for Cocoa and Cocoa Touch. 4 | 5 | [![GitHub release](https://img.shields.io/github/release/LeoNatan/LNInterpolation.svg)](https://github.com/LeoNatan/LNInterpolation/releases) [![GitHub stars](https://img.shields.io/github/stars/LeoNatan/LNInterpolation.svg)](https://github.com/LeoNatan/LNInterpolation/stargazers) [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/LeoNatan/LNInterpolation/master/LICENSE) PayPal Donation Button 6 | 7 | [![GitHub issues](https://img.shields.io/github/issues-raw/LeoNatan/LNInterpolation.svg)](https://github.com/LeoNatan/LNInterpolation/issues) [![GitHub contributors](https://img.shields.io/github/contributors/LeoNatan/LNInterpolation.svg)](https://github.com/LeoNatan/LNInterpolation/graphs/contributors) [![Swift Package Manager compatible](https://img.shields.io/badge/swift%20package%20manager-compatible-green)](https://swift.org/package-manager/) [![Carthage compatible](https://img.shields.io/badge/carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) 8 | 9 | ## Protocol 10 | 11 | The framework defines a protocol, `LNInterpolable` (`Interpolable` in Swift), defining a common API for interpolating between two values. 12 | 13 | The protocol defines two methods. 14 | 15 | ### Objective C 16 | 17 | ```objective-c 18 | - (instancetype)interpolateToValue:(id)toValue progress:(double)progress; 19 | - (instancetype)interpolateToValue:(id)toValue progress:(double)progress behavior:(LNInterpolationBehavior)behavior; 20 | ``` 21 | 22 | ### Swift 23 | 24 | ``` 25 | public func interpolate(to toValue: Any, progress: Double) -> Self 26 | public func interpolate(to toValue: Any, progress: Double, behavior: InterpolationBehavior) -> Self 27 | ``` 28 | 29 | The first variant returns an interpolated value between the original and `toValue` according to progress, using the default behavior. 30 | The second is similar, but receives a behavior modifier. Each implementation can use this to choose how to implement the interpolation between the values. A default value of `LNInterpolationBehaviorUseDefault` (`.useDefault` in Swift) is always available. 31 | 32 | ## Provided Implementations 33 | 34 | The framework provides implementation for the most common cases where interpolation may be needed. 35 | 36 | ### `NSValue` and `NSNumber` 37 | 38 | An implementation is provided for `NSValue`, which provides support for the following values: 39 | 40 | * Numbers (`NSNumber`) 41 | * Decimal numbers (`NSDecimalNumber`) 42 | * Core Graphics: `CGPoint`, `CGSize`, `CGVector`, `CGRect`, `CGAffineTransform` (`NSValue`) 43 | * UIKit: `UIOffset`, `UIEdgeInsets` (`NSValue`) 44 | * AppKit: `NSEdgeInsets` (`NSValue`) 45 | 46 | 47 | 48 | 49 | ### `UIColor` and `NSColor` 50 | 51 | An implementation is provided for colors (both for iOS and macOS). By default, color interpolation is performed in Lab colorspace for best possible interpolation quality. If performance is an issue, you may use `LNInterpolationBehaviorUseRGBColorSpace` (`.useRGBColorSpace` in Swift) to specify a behavior where RGB colorspace is used. 52 | 53 | 54 | 55 | ## Demo Playground 56 | 57 | A demo playground is provided. Open LNInterpolationExample/LNInterpolationExample.xcworkspace and build the framework for macOS. Open the playground and explore the available pages. 58 | 59 | ## Adding to Your Project 60 | 61 | ### Swift Package Manager 62 | 63 | Swift Package Manager is the recommended way to integrate `LNInterpolation` in your project. 64 | 65 | `LNInterpolation` supports SPM versions 5.1.0 and above. To use SPM, you should use Xcode 11 to open your project. Click `File` -> `Swift Packages` -> `Add Package Dependency`, enter `https://github.com/LeoNatan/LNInterpolation`. Select the version you’d like to use. 66 | 67 | You can also manually add the package to your `Package.swift` file: 68 | 69 | ```swift 70 | .package(url: "https://github.com/LeoNatan/LNInterpolation.git", from: "1.0") 71 | ``` 72 | 73 | And the dependency in your target: 74 | 75 | ```swift 76 | .target(name: "BestExampleApp", dependencies: ["LNInterpolation"]), 77 | ``` 78 | 79 | ### Carthage 80 | 81 | Add the following to your Cartfile: 82 | 83 | ```github "LeoNatan/LNInterpolation"``` 84 | 85 | ### Manual 86 | 87 | Drag the `LNInterpolation.xcodeproj` project to your project, and add `LNInterpolation.framework` to **Embedded Binaries** in your project target's **General** tab. Xcode should sort everything else on its own. 88 | -------------------------------------------------------------------------------- /Supplements/Colors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeoNatan/LNInterpolation/0c536276c537da00ec14c8f6f86ed607a030eba6/Supplements/Colors.png -------------------------------------------------------------------------------- /Supplements/Numbers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeoNatan/LNInterpolation/0c536276c537da00ec14c8f6f86ed607a030eba6/Supplements/Numbers.png -------------------------------------------------------------------------------- /Supplements/Values.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeoNatan/LNInterpolation/0c536276c537da00ec14c8f6f86ed607a030eba6/Supplements/Values.png -------------------------------------------------------------------------------- /releaseVersion.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | if [[ -n $(git status --porcelain) ]]; then 5 | echo -e >&2 "\033[1;31mCannot release version because there are unstaged changes:\033[0m" 6 | git status --short 7 | exit -2 8 | fi 9 | 10 | if [[ -n $(git tag --contains $(git rev-parse --verify HEAD)) ]]; then 11 | echo -e >&2 "\033[1;31mThe latest commit is already contained in the following releases:\033[0m" 12 | git tag --contains $(git rev-parse --verify HEAD) 13 | exit -3 14 | fi 15 | 16 | # if [[ -n $(git log --branches --not --remotes) ]]; then 17 | # echo -e "\033[1;34mPushing pending commits to git\033[0m" 18 | # git push 19 | # fi 20 | 21 | CURRENT_VERSION=$(/usr/libexec/PlistBuddy LNInterpolation/Info.plist -c "Print CFBundleShortVersionString") 22 | NEXT_VERSION=$(echo "$CURRENT_VERSION" | perl -pe 's/^((\d+\.)*)(-?\d+)(.*)$/$1.($3+1).$4/e') 23 | 24 | echo -e "\033[1;34mUsing $NEXT_VERSION as release version\033[0m" 25 | 26 | echo -e "\033[1;34mCreating release notes\033[0m" 27 | 28 | RELEASE_NOTES_FILE=_tmp_release_notes.md 29 | 30 | touch "${RELEASE_NOTES_FILE}" 31 | open -Wn "${RELEASE_NOTES_FILE}" 32 | 33 | if ! [ -s "${RELEASE_NOTES_FILE}" ]; then 34 | echo >&2 "\033[1;31mNo release notes provided, aborting.\033[0m" 35 | rm -f "${RELEASE_NOTES_FILE}" 36 | exit -1 37 | fi 38 | 39 | echo -e "\033[1;34mUpdating framework version\033[0m" 40 | 41 | /usr/libexec/PlistBuddy LNInterpolation/Info.plist -c "Set CFBundleShortVersionString $NEXT_VERSION" -c "Set CFBundleVersion 1" 42 | 43 | echo -e "\033[1;34mCommitting all changes to Git for release $NEXT_VERSION\033[0m" 44 | 45 | git add -A 46 | git commit -m "$NEXT_VERSION" 47 | git tag "$NEXT_VERSION" 48 | 49 | git push 50 | git push --tags 51 | 52 | echo -e "\033[1;34mCreating a GitHub release\033[0m" 53 | 54 | gh release create --repo LeoNatan/LNInterpolation "$NEXT_VERSION" --title "v$NEXT_VERSION" --notes-file "${RELEASE_NOTES_FILE}" 55 | 56 | rm -f "${RELEASE_NOTES_FILE}" --------------------------------------------------------------------------------