├── .DS_Store ├── Readme ├── demo.gif ├── demo.png └── logo.png ├── Sources ├── .DS_Store ├── Interpolatable │ ├── Interpolatable.swift │ ├── UIColor+Interpolatable.swift │ ├── CGPrimitives+Interpolatable.swift │ └── UIBezierPath+Interpolatable.swift ├── Easing │ ├── Easing+CAMediaTimeFunction.swift │ └── Easing.swift └── Utils │ ├── CGPath+PathElements.swift │ └── CubicBezierInterpolator.swift ├── Demo ├── Demo │ ├── Assets.xcassets │ │ ├── Contents.json │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Info.plist │ ├── EasingDemoCell.swift │ ├── EasingListViewController.swift │ ├── AppDelegate.swift │ ├── Base.lproj │ │ └── LaunchScreen.storyboard │ ├── AdaptiveContentView.swift │ ├── SceneDelegate.swift │ ├── EasingDemo.swift │ ├── EasingChartView.swift │ └── EasingViewController.swift ├── Ref │ └── ReferenceImages_64 │ │ └── DemoTests.EasingDemoTests │ │ ├── test_linear@3x.png │ │ ├── test_caEaseIn@3x.png │ │ ├── test_caEaseOut@3x.png │ │ ├── test_backEaseIn@3x.png │ │ ├── test_backEaseOut@3x.png │ │ ├── test_bounceEaseIn@3x.png │ │ ├── test_cubicEaseIn@3x.png │ │ ├── test_cubicEaseOut@3x.png │ │ ├── test_sineEaseIn@3x.png │ │ ├── test_sineEaseOut@3x.png │ │ ├── test_smoothStep@3x.png │ │ ├── test_smootherStep@3x.png │ │ ├── test_backEaseInOut@3x.png │ │ ├── test_bounceEaseOut@3x.png │ │ ├── test_circularEaseIn@3x.png │ │ ├── test_cubicEaseInOut@3x.png │ │ ├── test_elasticEaseIn@3x.png │ │ ├── test_elasticEaseOut@3x.png │ │ ├── test_quarticEaseIn@3x.png │ │ ├── test_quarticEaseOut@3x.png │ │ ├── test_quinticEaseIn@3x.png │ │ ├── test_quinticEaseOut@3x.png │ │ ├── test_sineEaseInOut@3x.png │ │ ├── test_bounceEaseInOut@3x.png │ │ ├── test_caEaseInEaseOut@3x.png │ │ ├── test_circularEaseInOut@3x.png │ │ ├── test_circularEaseOut@3x.png │ │ ├── test_elasticEaseInOut@3x.png │ │ ├── test_exponentialEaseIn@3x.png │ │ ├── test_quadraticEaseIn@3x.png │ │ ├── test_quadraticEaseOut@3x.png │ │ ├── test_quarticEaseInOut@3x.png │ │ ├── test_quinticEaseInOut@3x.png │ │ ├── test_exponentialEaseOut@3x.png │ │ ├── test_quadraticEaseInOut@3x.png │ │ ├── test_exponentialEaseInOut@3x.png │ │ └── test_cubicBezier_0_11__0_87__0_21__0_88_@3x.png ├── Demo.xcodeproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── swiftpm │ │ │ └── Package.resolved │ ├── xcshareddata │ │ └── xcschemes │ │ │ └── Demo.xcscheme │ └── project.pbxproj └── DemoTests │ └── DemoTests.swift ├── Package.swift ├── Tests └── EasingTests.swift ├── LICENSE ├── .gitignore └── README.md /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psharanda/Easing/HEAD/.DS_Store -------------------------------------------------------------------------------- /Readme/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psharanda/Easing/HEAD/Readme/demo.gif -------------------------------------------------------------------------------- /Readme/demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psharanda/Easing/HEAD/Readme/demo.png -------------------------------------------------------------------------------- /Readme/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psharanda/Easing/HEAD/Readme/logo.png -------------------------------------------------------------------------------- /Sources/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psharanda/Easing/HEAD/Sources/.DS_Store -------------------------------------------------------------------------------- /Demo/Demo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Demo/Ref/ReferenceImages_64/DemoTests.EasingDemoTests/test_linear@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psharanda/Easing/HEAD/Demo/Ref/ReferenceImages_64/DemoTests.EasingDemoTests/test_linear@3x.png -------------------------------------------------------------------------------- /Demo/Ref/ReferenceImages_64/DemoTests.EasingDemoTests/test_caEaseIn@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psharanda/Easing/HEAD/Demo/Ref/ReferenceImages_64/DemoTests.EasingDemoTests/test_caEaseIn@3x.png -------------------------------------------------------------------------------- /Demo/Ref/ReferenceImages_64/DemoTests.EasingDemoTests/test_caEaseOut@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psharanda/Easing/HEAD/Demo/Ref/ReferenceImages_64/DemoTests.EasingDemoTests/test_caEaseOut@3x.png -------------------------------------------------------------------------------- /Demo/Ref/ReferenceImages_64/DemoTests.EasingDemoTests/test_backEaseIn@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psharanda/Easing/HEAD/Demo/Ref/ReferenceImages_64/DemoTests.EasingDemoTests/test_backEaseIn@3x.png -------------------------------------------------------------------------------- /Demo/Ref/ReferenceImages_64/DemoTests.EasingDemoTests/test_backEaseOut@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psharanda/Easing/HEAD/Demo/Ref/ReferenceImages_64/DemoTests.EasingDemoTests/test_backEaseOut@3x.png -------------------------------------------------------------------------------- /Demo/Ref/ReferenceImages_64/DemoTests.EasingDemoTests/test_bounceEaseIn@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psharanda/Easing/HEAD/Demo/Ref/ReferenceImages_64/DemoTests.EasingDemoTests/test_bounceEaseIn@3x.png -------------------------------------------------------------------------------- /Demo/Ref/ReferenceImages_64/DemoTests.EasingDemoTests/test_cubicEaseIn@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psharanda/Easing/HEAD/Demo/Ref/ReferenceImages_64/DemoTests.EasingDemoTests/test_cubicEaseIn@3x.png -------------------------------------------------------------------------------- /Demo/Ref/ReferenceImages_64/DemoTests.EasingDemoTests/test_cubicEaseOut@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psharanda/Easing/HEAD/Demo/Ref/ReferenceImages_64/DemoTests.EasingDemoTests/test_cubicEaseOut@3x.png -------------------------------------------------------------------------------- /Demo/Ref/ReferenceImages_64/DemoTests.EasingDemoTests/test_sineEaseIn@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psharanda/Easing/HEAD/Demo/Ref/ReferenceImages_64/DemoTests.EasingDemoTests/test_sineEaseIn@3x.png -------------------------------------------------------------------------------- /Demo/Ref/ReferenceImages_64/DemoTests.EasingDemoTests/test_sineEaseOut@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psharanda/Easing/HEAD/Demo/Ref/ReferenceImages_64/DemoTests.EasingDemoTests/test_sineEaseOut@3x.png -------------------------------------------------------------------------------- /Demo/Ref/ReferenceImages_64/DemoTests.EasingDemoTests/test_smoothStep@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psharanda/Easing/HEAD/Demo/Ref/ReferenceImages_64/DemoTests.EasingDemoTests/test_smoothStep@3x.png -------------------------------------------------------------------------------- /Demo/Ref/ReferenceImages_64/DemoTests.EasingDemoTests/test_smootherStep@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psharanda/Easing/HEAD/Demo/Ref/ReferenceImages_64/DemoTests.EasingDemoTests/test_smootherStep@3x.png -------------------------------------------------------------------------------- /Demo/Ref/ReferenceImages_64/DemoTests.EasingDemoTests/test_backEaseInOut@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psharanda/Easing/HEAD/Demo/Ref/ReferenceImages_64/DemoTests.EasingDemoTests/test_backEaseInOut@3x.png -------------------------------------------------------------------------------- /Demo/Ref/ReferenceImages_64/DemoTests.EasingDemoTests/test_bounceEaseOut@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psharanda/Easing/HEAD/Demo/Ref/ReferenceImages_64/DemoTests.EasingDemoTests/test_bounceEaseOut@3x.png -------------------------------------------------------------------------------- /Demo/Ref/ReferenceImages_64/DemoTests.EasingDemoTests/test_circularEaseIn@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psharanda/Easing/HEAD/Demo/Ref/ReferenceImages_64/DemoTests.EasingDemoTests/test_circularEaseIn@3x.png -------------------------------------------------------------------------------- /Demo/Ref/ReferenceImages_64/DemoTests.EasingDemoTests/test_cubicEaseInOut@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psharanda/Easing/HEAD/Demo/Ref/ReferenceImages_64/DemoTests.EasingDemoTests/test_cubicEaseInOut@3x.png -------------------------------------------------------------------------------- /Demo/Ref/ReferenceImages_64/DemoTests.EasingDemoTests/test_elasticEaseIn@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psharanda/Easing/HEAD/Demo/Ref/ReferenceImages_64/DemoTests.EasingDemoTests/test_elasticEaseIn@3x.png -------------------------------------------------------------------------------- /Demo/Ref/ReferenceImages_64/DemoTests.EasingDemoTests/test_elasticEaseOut@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psharanda/Easing/HEAD/Demo/Ref/ReferenceImages_64/DemoTests.EasingDemoTests/test_elasticEaseOut@3x.png -------------------------------------------------------------------------------- /Demo/Ref/ReferenceImages_64/DemoTests.EasingDemoTests/test_quarticEaseIn@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psharanda/Easing/HEAD/Demo/Ref/ReferenceImages_64/DemoTests.EasingDemoTests/test_quarticEaseIn@3x.png -------------------------------------------------------------------------------- /Demo/Ref/ReferenceImages_64/DemoTests.EasingDemoTests/test_quarticEaseOut@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psharanda/Easing/HEAD/Demo/Ref/ReferenceImages_64/DemoTests.EasingDemoTests/test_quarticEaseOut@3x.png -------------------------------------------------------------------------------- /Demo/Ref/ReferenceImages_64/DemoTests.EasingDemoTests/test_quinticEaseIn@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psharanda/Easing/HEAD/Demo/Ref/ReferenceImages_64/DemoTests.EasingDemoTests/test_quinticEaseIn@3x.png -------------------------------------------------------------------------------- /Demo/Ref/ReferenceImages_64/DemoTests.EasingDemoTests/test_quinticEaseOut@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psharanda/Easing/HEAD/Demo/Ref/ReferenceImages_64/DemoTests.EasingDemoTests/test_quinticEaseOut@3x.png -------------------------------------------------------------------------------- /Demo/Ref/ReferenceImages_64/DemoTests.EasingDemoTests/test_sineEaseInOut@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psharanda/Easing/HEAD/Demo/Ref/ReferenceImages_64/DemoTests.EasingDemoTests/test_sineEaseInOut@3x.png -------------------------------------------------------------------------------- /Demo/Ref/ReferenceImages_64/DemoTests.EasingDemoTests/test_bounceEaseInOut@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psharanda/Easing/HEAD/Demo/Ref/ReferenceImages_64/DemoTests.EasingDemoTests/test_bounceEaseInOut@3x.png -------------------------------------------------------------------------------- /Demo/Ref/ReferenceImages_64/DemoTests.EasingDemoTests/test_caEaseInEaseOut@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psharanda/Easing/HEAD/Demo/Ref/ReferenceImages_64/DemoTests.EasingDemoTests/test_caEaseInEaseOut@3x.png -------------------------------------------------------------------------------- /Demo/Ref/ReferenceImages_64/DemoTests.EasingDemoTests/test_circularEaseInOut@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psharanda/Easing/HEAD/Demo/Ref/ReferenceImages_64/DemoTests.EasingDemoTests/test_circularEaseInOut@3x.png -------------------------------------------------------------------------------- /Demo/Ref/ReferenceImages_64/DemoTests.EasingDemoTests/test_circularEaseOut@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psharanda/Easing/HEAD/Demo/Ref/ReferenceImages_64/DemoTests.EasingDemoTests/test_circularEaseOut@3x.png -------------------------------------------------------------------------------- /Demo/Ref/ReferenceImages_64/DemoTests.EasingDemoTests/test_elasticEaseInOut@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psharanda/Easing/HEAD/Demo/Ref/ReferenceImages_64/DemoTests.EasingDemoTests/test_elasticEaseInOut@3x.png -------------------------------------------------------------------------------- /Demo/Ref/ReferenceImages_64/DemoTests.EasingDemoTests/test_exponentialEaseIn@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psharanda/Easing/HEAD/Demo/Ref/ReferenceImages_64/DemoTests.EasingDemoTests/test_exponentialEaseIn@3x.png -------------------------------------------------------------------------------- /Demo/Ref/ReferenceImages_64/DemoTests.EasingDemoTests/test_quadraticEaseIn@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psharanda/Easing/HEAD/Demo/Ref/ReferenceImages_64/DemoTests.EasingDemoTests/test_quadraticEaseIn@3x.png -------------------------------------------------------------------------------- /Demo/Ref/ReferenceImages_64/DemoTests.EasingDemoTests/test_quadraticEaseOut@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psharanda/Easing/HEAD/Demo/Ref/ReferenceImages_64/DemoTests.EasingDemoTests/test_quadraticEaseOut@3x.png -------------------------------------------------------------------------------- /Demo/Ref/ReferenceImages_64/DemoTests.EasingDemoTests/test_quarticEaseInOut@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psharanda/Easing/HEAD/Demo/Ref/ReferenceImages_64/DemoTests.EasingDemoTests/test_quarticEaseInOut@3x.png -------------------------------------------------------------------------------- /Demo/Ref/ReferenceImages_64/DemoTests.EasingDemoTests/test_quinticEaseInOut@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psharanda/Easing/HEAD/Demo/Ref/ReferenceImages_64/DemoTests.EasingDemoTests/test_quinticEaseInOut@3x.png -------------------------------------------------------------------------------- /Demo/Ref/ReferenceImages_64/DemoTests.EasingDemoTests/test_exponentialEaseOut@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psharanda/Easing/HEAD/Demo/Ref/ReferenceImages_64/DemoTests.EasingDemoTests/test_exponentialEaseOut@3x.png -------------------------------------------------------------------------------- /Demo/Ref/ReferenceImages_64/DemoTests.EasingDemoTests/test_quadraticEaseInOut@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psharanda/Easing/HEAD/Demo/Ref/ReferenceImages_64/DemoTests.EasingDemoTests/test_quadraticEaseInOut@3x.png -------------------------------------------------------------------------------- /Demo/Demo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Demo/Ref/ReferenceImages_64/DemoTests.EasingDemoTests/test_exponentialEaseInOut@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psharanda/Easing/HEAD/Demo/Ref/ReferenceImages_64/DemoTests.EasingDemoTests/test_exponentialEaseInOut@3x.png -------------------------------------------------------------------------------- /Demo/Demo/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Demo/Ref/ReferenceImages_64/DemoTests.EasingDemoTests/test_cubicBezier_0_11__0_87__0_21__0_88_@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psharanda/Easing/HEAD/Demo/Ref/ReferenceImages_64/DemoTests.EasingDemoTests/test_cubicBezier_0_11__0_87__0_21__0_88_@3x.png -------------------------------------------------------------------------------- /Demo/Demo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "platform" : "ios", 6 | "size" : "1024x1024" 7 | } 8 | ], 9 | "info" : { 10 | "author" : "xcode", 11 | "version" : 1 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Demo/Demo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Sources/Interpolatable/Interpolatable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2016-present, Pavel Sharanda. All rights reserved. 3 | // 4 | 5 | import Foundation 6 | 7 | public protocol Interpolatable { 8 | func interpolate(to: Self, progress: Double, easing: Easing) -> Self 9 | } 10 | 11 | 12 | extension Interpolatable { 13 | public func interpolate(to: Self, progress: Double) -> Self { 14 | return interpolate(to: to, progress: progress, easing: .linear) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.9 2 | 3 | import PackageDescription 4 | 5 | let packageName = "Easing" 6 | 7 | let package = Package( 8 | name: "Easing", 9 | platforms: [.iOS(.v12), .macOS(.v10_13), .watchOS(.v4), .tvOS(.v12), .visionOS(.v1)], 10 | products: [.library(name: "Easing", targets: ["Easing"])], 11 | dependencies: [], 12 | targets: [ 13 | .target(name: "Easing", dependencies: [], path: "Sources"), 14 | .testTarget(name: "EasingTests", dependencies: ["Easing"], path: "Tests"), 15 | ] 16 | ) 17 | -------------------------------------------------------------------------------- /Tests/EasingTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2016-present, Pavel Sharanda. All rights reserved. 3 | // 4 | 5 | import Easing 6 | import Foundation 7 | import XCTest 8 | 9 | class EasingTests: XCTestCase { 10 | func test1() { 11 | let val = Easing.linear.calculate(0.5) 12 | XCTAssertEqual(val, 0.5) 13 | } 14 | 15 | func test2() { 16 | let val = Easing.linear.calculate(d1: 0, d2: 1, g: 0.5) 17 | XCTAssertEqual(val, 0.5) 18 | } 19 | 20 | func test3() { 21 | let val = Easing.linear.calculate(g1: 0, d1: 0, g2: 1, d2: 1, g: 0.5) 22 | XCTAssertEqual(val, 0.5) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Demo/Demo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | UIApplicationSceneManifest 6 | 7 | UIApplicationSupportsMultipleScenes 8 | 9 | UISceneConfigurations 10 | 11 | UIWindowSceneSessionRoleApplication 12 | 13 | 14 | UISceneConfigurationName 15 | Default Configuration 16 | UISceneDelegateClassName 17 | $(PRODUCT_MODULE_NAME).SceneDelegate 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Demo/Demo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "originHash" : "ed3e487f7ed5771e7714735368878af1d0daa01bc8d176d1560f16d899872633", 3 | "pins" : [ 4 | { 5 | "identity" : "fixflex", 6 | "kind" : "remoteSourceControl", 7 | "location" : "https://github.com/psharanda/FixFlex", 8 | "state" : { 9 | "revision" : "83a7d0f58fa5b98e86712d26c179122ae10c1445", 10 | "version" : "1.2.1" 11 | } 12 | }, 13 | { 14 | "identity" : "ios-snapshot-test-case", 15 | "kind" : "remoteSourceControl", 16 | "location" : "https://github.com/uber/ios-snapshot-test-case", 17 | "state" : { 18 | "revision" : "7b10770333a961be6e5a41c9ce04b8c6d3990126", 19 | "version" : "8.0.0" 20 | } 21 | } 22 | ], 23 | "version" : 3 24 | } 25 | -------------------------------------------------------------------------------- /Demo/DemoTests/DemoTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Pavel Sharanda on 02/12/2023. 3 | // 4 | 5 | @testable import Demo 6 | import iOSSnapshotTestCase 7 | import XCTest 8 | 9 | final class EasingDemoTests: FBSnapshotTestCase { 10 | 11 | override func setUp() { 12 | super.setUp() 13 | usesDrawViewHierarchyInRect = true 14 | } 15 | 16 | func test() { 17 | //recordMode = true 18 | 19 | for item in EasingDemoItem.allItems { 20 | let view = EasingChartView() 21 | view.easing = item.easing 22 | let size = view.sizeThatFits(CGSize(width: 100, height: CGFloat.greatestFiniteMagnitude)) 23 | view.frame = CGRect(origin: .zero, size: size) 24 | FBSnapshotVerifyView(view, identifier: item.name) 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016-present Pavel Sharanda 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 | 23 | -------------------------------------------------------------------------------- /Demo/Demo/EasingDemoCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2024-present, Pavel Sharanda. All rights reserved. 3 | // 4 | 5 | import FixFlex 6 | import UIKit 7 | 8 | class EasingDemoCell: UITableViewCell { 9 | private let demoView = EasingChartView() 10 | private let titleLabel = UILabel() 11 | 12 | override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { 13 | super.init(style: style, reuseIdentifier: reuseIdentifier) 14 | commonInit() 15 | } 16 | 17 | required init?(coder: NSCoder) { 18 | super.init(coder: coder) 19 | commonInit() 20 | } 21 | 22 | private func commonInit() { 23 | contentView.addSubview(demoView) 24 | contentView.addSubview(titleLabel) 25 | 26 | contentView.fx.hstack(Fix(20), Flex(titleLabel), Flex(), Fix(demoView, 60), Fix(20)) 27 | 28 | contentView.fx.vstack(Fill(), Flex(titleLabel), Fill()) 29 | contentView.fx.vstack(Fix(10), Flex(demoView), Fix(10)) 30 | } 31 | 32 | var demoItem: EasingDemoItem? { 33 | didSet { 34 | titleLabel.text = demoItem?.name 35 | demoView.easing = demoItem?.easing 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Demo/Demo/EasingListViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2024-present, Pavel Sharanda. All rights reserved. 3 | // 4 | 5 | import FixFlex 6 | import UIKit 7 | 8 | class EasingListViewController: UITableViewController { 9 | 10 | 11 | override func viewDidLoad() { 12 | super.viewDidLoad() 13 | title = "Easing Demo" 14 | 15 | tableView.register(EasingDemoCell.self, forCellReuseIdentifier: "CellId") 16 | tableView.rowHeight = UITableView.automaticDimension 17 | tableView.estimatedRowHeight = 32 18 | } 19 | 20 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 21 | let cell = tableView.dequeueReusableCell(withIdentifier: "CellId", for: indexPath) as! EasingDemoCell 22 | cell.demoItem = EasingDemoItem.allItems[indexPath.row] 23 | return cell 24 | } 25 | 26 | override func tableView(_: UITableView, numberOfRowsInSection _: Int) -> Int { 27 | EasingDemoItem.allItems.count 28 | } 29 | 30 | override func tableView(_: UITableView, didSelectRowAt indexPath: IndexPath) { 31 | let vc = EasingViewController(demoItem: EasingDemoItem.allItems[indexPath.row]) 32 | navigationController?.pushViewController(vc, animated: true) 33 | } 34 | } 35 | 36 | -------------------------------------------------------------------------------- /Demo/Demo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2024-present, Pavel Sharanda. All rights reserved. 3 | // 4 | 5 | import UIKit 6 | 7 | @main 8 | class AppDelegate: UIResponder, UIApplicationDelegate { 9 | func application(_: UIApplication, didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 10 | // Override point for customization after application launch. 11 | return true 12 | } 13 | 14 | // MARK: UISceneSession Lifecycle 15 | 16 | func application(_: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options _: UIScene.ConnectionOptions) -> UISceneConfiguration { 17 | // Called when a new scene session is being created. 18 | // Use this method to select a configuration to create the new scene with. 19 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 20 | } 21 | 22 | func application(_: UIApplication, didDiscardSceneSessions _: Set) { 23 | // Called when the user discards a scene session. 24 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 25 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Sources/Easing/Easing+CAMediaTimeFunction.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2016-present, Pavel Sharanda. All rights reserved. 3 | // 4 | 5 | #if !os(watchOS) 6 | 7 | import QuartzCore 8 | 9 | public extension Easing { 10 | static let caEaseIn: Easing = { 11 | let f = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeIn) 12 | return Easing.cubicBezierEasingForMediaFunction(f) 13 | }() 14 | 15 | static let caEaseOut: Easing = { 16 | let f = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut) 17 | return Easing.cubicBezierEasingForMediaFunction(f) 18 | }() 19 | 20 | static let caEaseInEaseOut: Easing = { 21 | let f = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut) 22 | return Easing.cubicBezierEasingForMediaFunction(f) 23 | }() 24 | 25 | private static func cubicBezierEasingForMediaFunction(_ m: CAMediaTimingFunction) -> Easing { 26 | var controlPoint1: [Float] = [0, 0] 27 | var controlPoint2: [Float] = [0, 0] 28 | 29 | m.getControlPoint(at: 1, values: &controlPoint1) 30 | m.getControlPoint(at: 2, values: &controlPoint2) 31 | 32 | return Easing.cubicBezier( 33 | Double(controlPoint1[0]), 34 | Double(controlPoint1[1]), 35 | Double(controlPoint2[0]), 36 | Double(controlPoint2[1]) 37 | ) 38 | } 39 | } 40 | 41 | #endif 42 | -------------------------------------------------------------------------------- /Demo/Demo/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Demo/Demo/AdaptiveContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2024-present, Pavel Sharanda. All rights reserved. 3 | // 4 | 5 | import UIKit 6 | 7 | open class AdaptiveContentView: UIView { 8 | private class WorkerLabel: UILabel { 9 | var contentRectForBounds: ((CGRect) -> CGRect)? 10 | 11 | override init(frame: CGRect) { 12 | super.init(frame: frame) 13 | numberOfLines = 0 14 | isHidden = true 15 | } 16 | 17 | @available(*, unavailable) 18 | required init?(coder _: NSCoder) { 19 | fatalError("init(coder:) has not been implemented") 20 | } 21 | 22 | override func textRect(forBounds bounds: CGRect, limitedToNumberOfLines _: Int) -> CGRect { 23 | return contentRectForBounds?(bounds) ?? .zero 24 | } 25 | } 26 | 27 | private let workerLabel = WorkerLabel() 28 | 29 | override public init(frame: CGRect) { 30 | super.init(frame: frame) 31 | commonInit() 32 | } 33 | 34 | public required init?(coder: NSCoder) { 35 | super.init(coder: coder) 36 | commonInit() 37 | } 38 | 39 | private func commonInit() { 40 | workerLabel.translatesAutoresizingMaskIntoConstraints = false 41 | workerLabel.contentRectForBounds = { [weak self] bounds in 42 | self?.contentRect(forBounds: bounds) ?? .zero 43 | } 44 | addSubview(workerLabel) 45 | 46 | NSLayoutConstraint.activate([ 47 | workerLabel.leadingAnchor.constraint(equalTo: leadingAnchor), 48 | workerLabel.trailingAnchor.constraint(equalTo: trailingAnchor), 49 | workerLabel.topAnchor.constraint(equalTo: topAnchor), 50 | workerLabel.bottomAnchor.constraint(equalTo: bottomAnchor), 51 | ]) 52 | } 53 | 54 | open func invalidateContentRect() { 55 | workerLabel.text = workerLabel.text == "a" ? "b" : "a" 56 | } 57 | 58 | open func contentRect(forBounds bounds: CGRect) -> CGRect { 59 | return bounds 60 | } 61 | 62 | open override func sizeThatFits(_ size: CGSize) -> CGSize { 63 | return workerLabel.sizeThatFits(size) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Sources/Interpolatable/UIColor+Interpolatable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2016-present, Pavel Sharanda. All rights reserved. 3 | // 4 | 5 | #if !os(macOS) 6 | 7 | import Foundation 8 | import UIKit 9 | 10 | extension UIColor: Interpolatable { 11 | // TODO: check alternative interpolations https://raphlinus.github.io/color/2021/01/18/oklab-critique.html 12 | 13 | public func interpolateHSB(to: UIColor, progress: Double, easing: Easing) -> Self { 14 | var h1: CGFloat = 0 15 | var s1: CGFloat = 0 16 | var b1: CGFloat = 0 17 | var a1: CGFloat = 0 18 | getHue(&h1, saturation: &s1, brightness: &b1, alpha: &a1) 19 | var h2: CGFloat = 0 20 | var s2: CGFloat = 0 21 | var b2: CGFloat = 0 22 | var a2: CGFloat = 0 23 | to.getHue(&h2, saturation: &s2, brightness: &b2, alpha: &a2) 24 | 25 | return Self( 26 | hue: h1.interpolate(to: h2, progress: progress, easing: easing), 27 | saturation: s1.interpolate(to: s2, progress: progress, easing: easing), 28 | brightness: b1.interpolate(to: b2, progress: progress, easing: easing), 29 | alpha: a1.interpolate(to: a2, progress: progress, easing: easing) 30 | ) 31 | } 32 | 33 | public func interpolate(to: UIColor, progress: Double, easing: Easing) -> Self { 34 | var r1: CGFloat = 0 35 | var g1: CGFloat = 0 36 | var b1: CGFloat = 0 37 | var a1: CGFloat = 0 38 | getRed(&r1, green: &g1, blue: &b1, alpha: &a1) 39 | var r2: CGFloat = 0 40 | var g2: CGFloat = 0 41 | var b2: CGFloat = 0 42 | var a2: CGFloat = 0 43 | to.getRed(&r2, green: &g2, blue: &b2, alpha: &a2) 44 | 45 | return Self( 46 | red: r1.interpolate(to: r2, progress: progress, easing: easing), 47 | green: g1.interpolate(to: g2, progress: progress, easing: easing), 48 | blue: b1.interpolate(to: b2, progress: progress, easing: easing), 49 | alpha: a1.interpolate(to: a2, progress: progress, easing: easing) 50 | ) 51 | } 52 | } 53 | 54 | #endif 55 | -------------------------------------------------------------------------------- /Demo/Demo/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2024-present, Pavel Sharanda. All rights reserved. 3 | // 4 | 5 | import UIKit 6 | 7 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 8 | var window: UIWindow? 9 | 10 | func scene(_ scene: UIScene, willConnectTo _: UISceneSession, options _: UIScene.ConnectionOptions) { 11 | guard let windowScene = (scene as? UIWindowScene) else { return } 12 | 13 | let window = UIWindow(windowScene: windowScene) 14 | 15 | let nc = UINavigationController() 16 | let vc = EasingListViewController() 17 | nc.viewControllers = [vc] 18 | window.rootViewController = nc 19 | 20 | self.window = window 21 | window.makeKeyAndVisible() 22 | } 23 | 24 | func sceneDidDisconnect(_: UIScene) { 25 | // Called as the scene is being released by the system. 26 | // This occurs shortly after the scene enters the background, or when its session is discarded. 27 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 28 | // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). 29 | } 30 | 31 | func sceneDidBecomeActive(_: UIScene) { 32 | // Called when the scene has moved from an inactive state to an active state. 33 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 34 | } 35 | 36 | func sceneWillResignActive(_: UIScene) { 37 | // Called when the scene will move from an active state to an inactive state. 38 | // This may occur due to temporary interruptions (ex. an incoming phone call). 39 | } 40 | 41 | func sceneWillEnterForeground(_: UIScene) { 42 | // Called as the scene transitions from the background to the foreground. 43 | // Use this method to undo the changes made on entering the background. 44 | } 45 | 46 | func sceneDidEnterBackground(_: UIScene) { 47 | // Called as the scene transitions from the foreground to the background. 48 | // Use this method to save data, release shared resources, and store enough scene-specific state information 49 | // to restore the scene back to its current state. 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## User settings 6 | xcuserdata/ 7 | 8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 9 | *.xcscmblueprint 10 | *.xccheckout 11 | 12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 13 | build/ 14 | DerivedData/ 15 | *.moved-aside 16 | *.pbxuser 17 | !default.pbxuser 18 | *.mode1v3 19 | !default.mode1v3 20 | *.mode2v3 21 | !default.mode2v3 22 | *.perspectivev3 23 | !default.perspectivev3 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | 28 | ## App packaging 29 | *.ipa 30 | *.dSYM.zip 31 | *.dSYM 32 | 33 | ## Playgrounds 34 | timeline.xctimeline 35 | playground.xcworkspace 36 | 37 | # Swift Package Manager 38 | # 39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 40 | # Packages/ 41 | # Package.pins 42 | # Package.resolved 43 | # *.xcodeproj 44 | # 45 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 46 | # hence it is not needed unless you have added a package configuration file to your project 47 | .swiftpm 48 | 49 | .build/ 50 | 51 | # CocoaPods 52 | # 53 | # We recommend against adding the Pods directory to your .gitignore. However 54 | # you should judge for yourself, the pros and cons are mentioned at: 55 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 56 | # 57 | Pods/ 58 | # 59 | # Add this line if you want to avoid checking in source code from the Xcode workspace 60 | # *.xcworkspace 61 | 62 | # Carthage 63 | # 64 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 65 | Carthage/Checkouts 66 | 67 | Carthage/Build/ 68 | 69 | # Accio dependency management 70 | Dependencies/ 71 | .accio/ 72 | 73 | # fastlane 74 | # 75 | # It is recommended to not store the screenshots in the git repo. 76 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 77 | # For more information about the recommended setup visit: 78 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 79 | 80 | fastlane/report.xml 81 | fastlane/Preview.html 82 | fastlane/screenshots/**/*.png 83 | fastlane/test_output 84 | 85 | # Code Injection 86 | # 87 | # After new code Injection tools there's a generated folder /iOSInjectionProject 88 | # https://github.com/johnno1962/injectionforxcode 89 | 90 | iOSInjectionProject/ 91 | .DS_Store 92 | -------------------------------------------------------------------------------- /Sources/Utils/CGPath+PathElements.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2016-present, Pavel Sharanda. All rights reserved. 3 | // 4 | 5 | import CoreGraphics 6 | import Foundation 7 | 8 | enum PathElement { 9 | case moveToPoint(CGPoint) 10 | case addLineToPoint(CGPoint) 11 | case addQuadCurveToPoint(controlPoint: CGPoint, endPoint: CGPoint) 12 | case addCurveToPoint(controlPoint1: CGPoint, controlPoint2: CGPoint, endPoint: CGPoint) 13 | case closeSubpath 14 | } 15 | 16 | extension CGPath { 17 | static func pathFromPathElements(_ pathElements: [PathElement]) -> CGPath { 18 | let path = CGMutablePath() 19 | 20 | for pathElement in pathElements { 21 | switch pathElement { 22 | case let .moveToPoint(point): 23 | path.move(to: point) 24 | case let .addLineToPoint(point): 25 | path.addLine(to: point) 26 | case let .addQuadCurveToPoint(controlPoint, endPoint): 27 | path.addQuadCurve(to: endPoint, control: controlPoint) 28 | case let .addCurveToPoint(controlPoint1, controlPoint2, endPoint): 29 | path.addCurve(to: endPoint, control1: controlPoint1, control2: controlPoint2) 30 | case .closeSubpath: 31 | path.closeSubpath() 32 | } 33 | } 34 | return path 35 | } 36 | 37 | func pathElements() -> [PathElement] { 38 | var result = [PathElement]() 39 | 40 | applyWithBlock { elementPointer in 41 | let element = elementPointer.pointee 42 | switch element.type { 43 | case .moveToPoint: 44 | let points = Array(UnsafeBufferPointer(start: element.points, count: 1)) 45 | let el = PathElement.moveToPoint(points[0]) 46 | result.append(el) 47 | case .addLineToPoint: 48 | let points = Array(UnsafeBufferPointer(start: element.points, count: 1)) 49 | let el = PathElement.addLineToPoint(points[0]) 50 | result.append(el) 51 | case .addQuadCurveToPoint: 52 | let points = Array(UnsafeBufferPointer(start: element.points, count: 2)) 53 | let el = PathElement.addQuadCurveToPoint( 54 | controlPoint: points[0], endPoint: points[1] 55 | ) 56 | result.append(el) 57 | case .addCurveToPoint: 58 | let points = Array(UnsafeBufferPointer(start: element.points, count: 3)) 59 | let el = PathElement.addCurveToPoint( 60 | controlPoint1: points[0], controlPoint2: points[1], endPoint: points[2] 61 | ) 62 | result.append(el) 63 | case .closeSubpath: 64 | result.append(.closeSubpath) 65 | @unknown default: 66 | break 67 | } 68 | } 69 | 70 | return result 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Sources/Interpolatable/CGPrimitives+Interpolatable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2016-present, Pavel Sharanda. All rights reserved. 3 | // 4 | 5 | import CoreGraphics 6 | import Foundation 7 | 8 | extension CGFloat: Interpolatable { 9 | public func interpolate(to: CGFloat, progress: Double, easing: Easing) -> Self { 10 | return easing.calculate(d1: self, d2: to, g: progress) 11 | } 12 | } 13 | 14 | extension CGPoint: Interpolatable { 15 | public func interpolate(to: CGPoint, progress: Double, easing: Easing) -> Self { 16 | return CGPoint( 17 | x: x.interpolate(to: to.x, progress: progress, easing: easing), 18 | y: y.interpolate(to: to.y, progress: progress, easing: easing) 19 | ) 20 | } 21 | } 22 | 23 | extension CGRect: Interpolatable { 24 | public func interpolate(to: CGRect, progress: Double, easing: Easing) -> Self { 25 | return CGRect( 26 | x: origin.x.interpolate(to: to.origin.x, progress: progress, easing: easing), 27 | y: origin.y.interpolate(to: to.origin.y, progress: progress, easing: easing), 28 | width: size.width.interpolate(to: to.size.width, progress: progress, easing: easing), 29 | height: size.height.interpolate(to: to.size.height, progress: progress, easing: easing) 30 | ) 31 | } 32 | } 33 | 34 | extension CGSize: Interpolatable { 35 | public func interpolate(to: CGSize, progress: Double, easing: Easing) -> Self { 36 | return CGSize( 37 | width: width.interpolate(to: to.width, progress: progress, easing: easing), 38 | height: height.interpolate(to: to.height, progress: progress, easing: easing) 39 | ) 40 | } 41 | } 42 | 43 | extension CGAffineTransform: Interpolatable { 44 | public func interpolate(to: CGAffineTransform, progress: Double, easing: Easing) -> Self { 45 | return CGAffineTransform(tx: tx.interpolate(to: to.tx, progress: progress, easing: easing), 46 | ty: ty.interpolate(to: to.ty, progress: progress, easing: easing), 47 | scaleX: scaleX.interpolate(to: to.scaleX, progress: progress, easing: easing), 48 | scaleY: scaleY.interpolate(to: to.scaleY, progress: progress, easing: easing), 49 | rotation: rotation.interpolate(to: to.rotation, progress: progress, easing: easing)) 50 | } 51 | 52 | var scaleX: CGFloat { sqrt(a * a + c * c) } 53 | var scaleY: CGFloat { sqrt(b * b + d * d) } 54 | var rotation: CGFloat { atan2(b, a) } 55 | 56 | init(tx: CGFloat, ty: CGFloat, scaleX: CGFloat, scaleY: CGFloat, rotation: CGFloat) { 57 | let translationTransform = CGAffineTransform(translationX: tx, y: ty) 58 | let scaleTransform = CGAffineTransform(scaleX: scaleX, y: scaleY) 59 | let rotationTransform = CGAffineTransform(rotationAngle: rotation) 60 | self = rotationTransform.concatenating(scaleTransform).concatenating(translationTransform) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Demo/Demo/EasingDemo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2024-present, Pavel Sharanda. All rights reserved. 3 | // 4 | 5 | import Foundation 6 | 7 | struct EasingDemoItem { 8 | let name: String 9 | let easing: Easing 10 | 11 | static let allItems = [ 12 | EasingDemoItem(name: "linear", easing: .linear), 13 | 14 | EasingDemoItem(name: "smoothStep", easing: .smoothStep), 15 | EasingDemoItem(name: "smootherStep", easing: .smootherStep), 16 | 17 | EasingDemoItem(name: "quadraticEaseIn", easing: .quadraticEaseIn), 18 | EasingDemoItem(name: "quadraticEaseOut", easing: .quadraticEaseOut), 19 | EasingDemoItem(name: "quadraticEaseInOut", easing: .quadraticEaseInOut), 20 | 21 | EasingDemoItem(name: "cubicEaseIn", easing: .cubicEaseIn), 22 | EasingDemoItem(name: "cubicEaseOut", easing: .cubicEaseOut), 23 | EasingDemoItem(name: "cubicEaseInOut", easing: .cubicEaseInOut), 24 | 25 | EasingDemoItem(name: "quarticEaseIn", easing: .quarticEaseIn), 26 | EasingDemoItem(name: "quarticEaseOut", easing: .quarticEaseOut), 27 | EasingDemoItem(name: "quarticEaseInOut", easing: .quarticEaseInOut), 28 | 29 | EasingDemoItem(name: "quinticEaseIn", easing: .quinticEaseIn), 30 | EasingDemoItem(name: "quinticEaseOut", easing: .quinticEaseOut), 31 | EasingDemoItem(name: "quinticEaseInOut", easing: .quinticEaseInOut), 32 | 33 | EasingDemoItem(name: "sineEaseIn", easing: .sineEaseIn), 34 | EasingDemoItem(name: "sineEaseOut", easing: .sineEaseOut), 35 | EasingDemoItem(name: "sineEaseInOut", easing: .sineEaseInOut), 36 | 37 | EasingDemoItem(name: "circularEaseIn", easing: .circularEaseIn), 38 | EasingDemoItem(name: "circularEaseOut", easing: .circularEaseOut), 39 | EasingDemoItem(name: "circularEaseInOut", easing: .circularEaseInOut), 40 | 41 | EasingDemoItem(name: "exponentialEaseIn", easing: .exponentialEaseIn), 42 | EasingDemoItem(name: "exponentialEaseOut", easing: .exponentialEaseOut), 43 | EasingDemoItem(name: "exponentialEaseInOut", easing: .exponentialEaseInOut), 44 | 45 | EasingDemoItem(name: "elasticEaseIn", easing: .elasticEaseIn), 46 | EasingDemoItem(name: "elasticEaseOut", easing: .elasticEaseOut), 47 | EasingDemoItem(name: "elasticEaseInOut", easing: .elasticEaseInOut), 48 | 49 | EasingDemoItem(name: "backEaseIn", easing: .backEaseIn), 50 | EasingDemoItem(name: "backEaseOut", easing: .backEaseOut), 51 | EasingDemoItem(name: "backEaseInOut", easing: .backEaseInOut), 52 | 53 | EasingDemoItem(name: "bounceEaseIn", easing: .bounceEaseIn), 54 | EasingDemoItem(name: "bounceEaseOut", easing: .bounceEaseOut), 55 | EasingDemoItem(name: "bounceEaseInOut", easing: .bounceEaseInOut), 56 | 57 | EasingDemoItem(name: "caEaseIn", easing: .caEaseIn), 58 | EasingDemoItem(name: "caEaseOut", easing: .caEaseOut), 59 | EasingDemoItem(name: "caEaseInEaseOut", easing: .caEaseInEaseOut), 60 | 61 | EasingDemoItem(name: "cubicBezier(0.11, 0.87, 0.21,-0.88)", easing: .cubicBezier(0.11, 0.87, 0.21, -0.88)), 62 | ] 63 | } 64 | 65 | 66 | -------------------------------------------------------------------------------- /Demo/Demo/EasingChartView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2024-present, Pavel Sharanda. All rights reserved. 3 | // 4 | 5 | import FixFlex 6 | import UIKit 7 | 8 | class EasingChartView: AdaptiveContentView { 9 | private let easingLayer = CAShapeLayer() 10 | private let xAxisLayer = CAShapeLayer() 11 | private let yAxisLayer = CAShapeLayer() 12 | 13 | override init(frame: CGRect) { 14 | super.init(frame: frame) 15 | commonInit() 16 | } 17 | 18 | required init?(coder: NSCoder) { 19 | super.init(coder: coder) 20 | commonInit() 21 | } 22 | 23 | private func commonInit() { 24 | xAxisLayer.fillColor = nil 25 | layer.addSublayer(xAxisLayer) 26 | 27 | yAxisLayer.fillColor = nil 28 | layer.addSublayer(yAxisLayer) 29 | 30 | easingLayer.fillColor = nil 31 | layer.addSublayer(easingLayer) 32 | updateColors() 33 | } 34 | 35 | override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { 36 | super.traitCollectionDidChange(previousTraitCollection) 37 | updateColors() 38 | } 39 | 40 | private func updateColors() { 41 | xAxisLayer.strokeColor = UIColor.label.withAlphaComponent(0.2).cgColor 42 | yAxisLayer.strokeColor = UIColor.label.withAlphaComponent(0.2).cgColor 43 | easingLayer.strokeColor = UIColor.label.cgColor 44 | } 45 | 46 | var easing: Easing? { 47 | didSet { 48 | invalidateContentRect() 49 | } 50 | } 51 | 52 | override func contentRect(forBounds bounds: CGRect) -> CGRect { 53 | if bounds.width > UIView.layoutFittingExpandedSize.width { 54 | return bounds 55 | } 56 | 57 | let chartWidth = bounds.width - 2 58 | 59 | let xAxisPath = UIBezierPath() 60 | xAxisPath.move(to: CGPoint(x: 0, y: chartWidth)) 61 | xAxisPath.addLine(to: CGPoint(x: chartWidth, y: chartWidth)) 62 | xAxisLayer.path = xAxisPath.cgPath 63 | 64 | let yAxisPath = UIBezierPath() 65 | yAxisPath.move(to: .zero) 66 | yAxisPath.addLine(to: CGPoint(x: 0, y: chartWidth)) 67 | yAxisLayer.path = yAxisPath.cgPath 68 | 69 | guard let easing else { 70 | easingLayer.path = nil 71 | xAxisLayer.bounds = .zero 72 | yAxisLayer.bounds = .zero 73 | return .zero 74 | } 75 | 76 | let easingPath = UIBezierPath() 77 | easingPath.move(to: CGPoint(x: 0, y: chartWidth)) 78 | 79 | var x = 0.0 80 | while x <= chartWidth { 81 | let progress = x / chartWidth 82 | let y = chartWidth - easing.calculate(progress) * chartWidth 83 | 84 | easingPath.addLine(to: CGPoint(x: x, y: y)) 85 | x += 1 86 | } 87 | 88 | easingLayer.path = easingPath.cgPath 89 | 90 | let pathBounds = easingPath.bounds 91 | 92 | xAxisLayer.frame = CGRect(x: 1, y: -pathBounds.minY + 1, width: 0, height: 0) 93 | yAxisLayer.frame = CGRect(x: 1, y: -pathBounds.minY + 1, width: 0, height: 0) 94 | easingLayer.frame = CGRect(x: 1, y: -pathBounds.minY + 1, width: 0, height: 0) 95 | 96 | self.pathBounds = pathBounds 97 | 98 | return CGRect(x: 0, y: 0, width: bounds.width, height: pathBounds.height + 2) 99 | } 100 | 101 | var pathBounds: CGRect? 102 | } 103 | -------------------------------------------------------------------------------- /Demo/Demo.xcodeproj/xcshareddata/xcschemes/Demo.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 9 | 10 | 16 | 22 | 23 | 24 | 25 | 26 | 32 | 33 | 36 | 42 | 43 | 44 | 45 | 46 | 56 | 58 | 64 | 65 | 66 | 67 | 71 | 72 | 76 | 77 | 78 | 79 | 85 | 87 | 93 | 94 | 95 | 96 | 98 | 99 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /Demo/Demo/EasingViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2024-present, Pavel Sharanda. All rights reserved. 3 | // 4 | 5 | import FixFlex 6 | import UIKit 7 | 8 | private let PINK_BALL_SIZE: CGFloat = 30.0 9 | 10 | class EasingViewController: UIViewController { 11 | private let chartView = EasingChartView() 12 | let rectDemoContainer = UIView() 13 | 14 | private let rectDemoView = UIView() 15 | 16 | let transformDemoContainer = UIView() 17 | private let transformDemoView = UIView() 18 | 19 | private let demoItem: EasingDemoItem 20 | 21 | private var displaylink: CADisplayLink? 22 | private var startTime: CFTimeInterval = 0 23 | 24 | private var progress: Double = 0 { 25 | didSet { 26 | view.setNeedsLayout() 27 | } 28 | } 29 | 30 | init(demoItem: EasingDemoItem) { 31 | self.demoItem = demoItem 32 | super.init(nibName: nil, bundle: nil) 33 | } 34 | 35 | @available(*, unavailable) 36 | required init?(coder _: NSCoder) { 37 | fatalError("init(coder:) has not been implemented") 38 | } 39 | 40 | override func viewDidLoad() { 41 | super.viewDidLoad() 42 | 43 | view.backgroundColor = .systemBackground 44 | title = demoItem.name 45 | 46 | chartView.easing = demoItem.easing 47 | view.addSubview(chartView) 48 | 49 | view.addSubview(rectDemoContainer) 50 | 51 | view.addSubview(transformDemoContainer) 52 | 53 | view.fx.hstack(Fix(20), Flex(chartView), Fix(20), Fix(rectDemoContainer, PINK_BALL_SIZE), Fix(20)) 54 | view.fx.hstack(Fix(20), Flex(transformDemoContainer), Fix(20)) 55 | view.fx.vstack(startAnchor: view.safeAreaLayoutGuide.topAnchor, 56 | endAnchor: view.safeAreaLayoutGuide.bottomAnchor, 57 | Fix(20), 58 | Flex([chartView, rectDemoContainer]), 59 | Fix(20), 60 | Flex(transformDemoContainer), 61 | Fix(20)) 62 | 63 | rectDemoView.backgroundColor = UIColor.systemPink 64 | rectDemoView.layer.cornerRadius = PINK_BALL_SIZE / 2 65 | rectDemoContainer.addSubview(rectDemoView) 66 | 67 | transformDemoView.backgroundColor = UIColor.systemMint 68 | transformDemoContainer.addSubview(transformDemoView) 69 | } 70 | 71 | override func viewDidAppear(_ animated: Bool) { 72 | super.viewDidAppear(animated) 73 | startTime = CACurrentMediaTime() 74 | progress = 0 75 | let displaylink = CADisplayLink(target: self, selector: #selector(step)) 76 | displaylink.add(to: .current, forMode: .default) 77 | self.displaylink = displaylink 78 | } 79 | 80 | override func viewWillDisappear(_: Bool) { 81 | displaylink?.invalidate() 82 | displaylink = nil 83 | } 84 | 85 | override func viewDidLayoutSubviews() { 86 | super.viewDidLayoutSubviews() 87 | 88 | let pathBounds = chartView.pathBounds ?? .zero 89 | 90 | let startRect = CGRect(x: 0, y: -pathBounds.minY + chartView.bounds.width - PINK_BALL_SIZE / 2, width: PINK_BALL_SIZE, height: PINK_BALL_SIZE) 91 | let endRect = CGRect(x: 0, y: -pathBounds.minY - PINK_BALL_SIZE / 2, width: PINK_BALL_SIZE, height: PINK_BALL_SIZE) 92 | 93 | rectDemoView.frame = startRect.interpolate(to: endRect, progress: progress, easing: demoItem.easing) 94 | 95 | let startTransform = CGAffineTransform.identity 96 | let endTransform = CGAffineTransform(scaleX: 2, y: 2) 97 | 98 | transformDemoView.transform = startTransform.interpolate(to: endTransform, progress: progress, easing: demoItem.easing) 99 | 100 | let size = transformDemoContainer.bounds.height / 4 101 | transformDemoView.layer.cornerRadius = size / 2 102 | transformDemoView.bounds = CGRect(x: 0, y: 0, width: size, height: size) 103 | transformDemoView.center = CGPoint(x: transformDemoContainer.bounds.midX, y: transformDemoContainer.bounds.midY) 104 | } 105 | 106 | @objc private func step(_: CADisplayLink) { 107 | let newCurrentTime = CACurrentMediaTime() 108 | progress = (newCurrentTime - startTime).truncatingRemainder(dividingBy: 1) 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /Sources/Interpolatable/UIBezierPath+Interpolatable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2016-present, Pavel Sharanda. All rights reserved. 3 | // 4 | 5 | #if !os(macOS) 6 | 7 | import Foundation 8 | import UIKit 9 | 10 | extension UIBezierPath: Interpolatable { 11 | public func interpolate(to: UIBezierPath, progress: Double, easing: Easing) -> Self { 12 | let pathElements1 = cgPath.pathElements() 13 | let pathElements2 = to.cgPath.pathElements() 14 | 15 | assert( 16 | pathElements1.count == pathElements2.count, 17 | "UIBezierPath.interpolate: number of path elements should be the same" 18 | ) 19 | 20 | var interpolatedPathElements: [PathElement] = [] 21 | 22 | var foundMistmatch = false 23 | 24 | for (e1, e2) in zip(pathElements1, pathElements2) { 25 | switch e1 { 26 | case let .moveToPoint(p1): 27 | switch e2 { 28 | case let .moveToPoint(p2): 29 | interpolatedPathElements.append( 30 | .moveToPoint(p1.interpolate(to: p2, progress: progress, easing: easing))) 31 | default: 32 | foundMistmatch = true 33 | } 34 | case let .addLineToPoint(p1): 35 | switch e2 { 36 | case let .addLineToPoint(p2): 37 | interpolatedPathElements.append( 38 | .addLineToPoint(p1.interpolate(to: p2, progress: progress, easing: easing))) 39 | default: 40 | foundMistmatch = true 41 | } 42 | case let .addQuadCurveToPoint(c1, d1): 43 | switch e2 { 44 | case let .addQuadCurveToPoint(c2, d2): 45 | interpolatedPathElements.append( 46 | .addQuadCurveToPoint( 47 | controlPoint: c1.interpolate(to: c2, progress: progress, easing: easing), 48 | endPoint: d1.interpolate(to: d2, progress: progress, easing: easing) 49 | )) 50 | default: 51 | foundMistmatch = true 52 | } 53 | case let .addCurveToPoint(f1, s1, e1): 54 | switch e2 { 55 | case let .addCurveToPoint(l2, r2, e2): 56 | interpolatedPathElements.append( 57 | .addCurveToPoint( 58 | controlPoint1: f1.interpolate(to: l2, progress: progress, easing: easing), 59 | controlPoint2: s1.interpolate(to: r2, progress: progress, easing: easing), 60 | endPoint: e1.interpolate(to: e2, progress: progress, easing: easing) 61 | )) 62 | default: 63 | foundMistmatch = true 64 | } 65 | case .closeSubpath: 66 | switch e2 { 67 | case .closeSubpath: 68 | interpolatedPathElements.append(.closeSubpath) 69 | default: 70 | foundMistmatch = true 71 | } 72 | } 73 | } 74 | 75 | assert(!foundMistmatch, "UIBezierPath.interpolate: path elements type should be the same") 76 | 77 | let interpolatedPath = Self(cgPath: CGPath.pathFromPathElements(interpolatedPathElements)) 78 | interpolatedPath.lineWidth = lineWidth.interpolate(to: to.lineWidth, progress: progress, easing: easing) 79 | 80 | interpolatedPath.lineCapStyle = lineCapStyle 81 | assert( 82 | lineCapStyle == to.lineCapStyle, 83 | "UIBezierPath.interpolate: lineCapStyle should be the same" 84 | ) 85 | 86 | interpolatedPath.lineJoinStyle = lineJoinStyle 87 | assert( 88 | lineJoinStyle == to.lineJoinStyle, 89 | "UIBezierPath.interpolate: lineJoinStyle should be the same" 90 | ) 91 | 92 | interpolatedPath.miterLimit = miterLimit.interpolate(to: to.miterLimit, progress: progress, easing: easing) 93 | interpolatedPath.flatness = flatness.interpolate(to: to.flatness, progress: progress, easing: easing) 94 | 95 | // TODO: implement line dash pattern interpolation 96 | 97 | interpolatedPath.usesEvenOddFillRule = usesEvenOddFillRule 98 | assert( 99 | usesEvenOddFillRule == to.usesEvenOddFillRule, 100 | "UIBezierPath.interpolate: usesEvenOddFillRule should be the same" 101 | ) 102 | 103 | return interpolatedPath 104 | } 105 | } 106 | 107 | #endif 108 | -------------------------------------------------------------------------------- /Sources/Utils/CubicBezierInterpolator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2016-present, Pavel Sharanda. All rights reserved. 3 | // 4 | 5 | import Foundation 6 | 7 | // Based on nsSMILKeySpline from Mozilla https://github.com/mozilla-services/services-central-legacy/blob/master/content/smil/nsSMILKeySpline.cpp 8 | struct CubicBezierCalculator { 9 | let x1: Double 10 | let y1: Double 11 | let x2: Double 12 | let y2: Double 13 | 14 | private let sampleValues: [Double] 15 | 16 | init(x1: Double, y1: Double, x2: Double, y2: Double, samplesCount: Int = 10) { 17 | self.x1 = x1 18 | self.y1 = y1 19 | self.x2 = x2 20 | self.y2 = y2 21 | 22 | if x1 == y1, x2 == y2 { 23 | sampleValues = [] 24 | } else { 25 | sampleValues = CubicBezierCalculator.calculateSampleValues(x1, x2, samplesCount) 26 | } 27 | } 28 | 29 | func calculate(_ x: Double) -> Double { 30 | if x1 == y1 && x2 == y2 { 31 | return x 32 | } 33 | return CubicBezierCalculator.calculateBezier(calculateT(x), y1, y2) 34 | } 35 | 36 | private func calculateT(_ x: Double) -> Double { 37 | let stepSize = 1.0 / Double(sampleValues.count) 38 | 39 | // Find interval where t lies 40 | var intervalStart = 0.0 41 | 42 | var currentSample = 0 43 | 44 | for (idx, value) in sampleValues.enumerated() { 45 | if idx == sampleValues.count - 1 { 46 | break 47 | } 48 | 49 | if value >= x { 50 | break 51 | } 52 | 53 | currentSample = idx 54 | if idx > 0 { 55 | intervalStart += stepSize 56 | } 57 | } 58 | 59 | // Interpolate to provide an initial guess for t 60 | let startGuess = sampleValues[currentSample] 61 | let endGuess = sampleValues[currentSample + 1] 62 | let dist = (x - startGuess) / (endGuess - startGuess) 63 | 64 | let guessForT = intervalStart + dist * stepSize 65 | 66 | // Check the slope to see what strategy to use. If the slope is too small 67 | // Newton-Raphson iteration won't converge on a root so we use bisection 68 | // instead. 69 | 70 | let initialSlope = CubicBezierCalculator.getSlope(guessForT, x1, x2) 71 | if initialSlope >= 0.02 { 72 | return CubicBezierCalculator.newtonRaphsonIterate(x, guessForT, x1, x2) 73 | } else if initialSlope == 0 { 74 | return guessForT 75 | } else { 76 | return CubicBezierCalculator.binarySubdivide( 77 | x, intervalStart, intervalStart + stepSize, x1, x2 78 | ) 79 | } 80 | } 81 | 82 | private static func calculateSampleValues(_ x1: Double, _ x2: Double, _ samplesCount: Int) 83 | -> [Double] 84 | { 85 | enum Cache { 86 | static var dict: [SampleKey: [Double]] = [:] 87 | } 88 | 89 | let key = SampleKey(x1: x1, x2: x2, number: samplesCount) 90 | 91 | if let cached = Cache.dict[key] { 92 | return cached 93 | } 94 | 95 | let stepSize = 1.0 / Double(samplesCount) 96 | 97 | var sampleValues: [Double] = [] 98 | for i in 0 ... samplesCount { 99 | sampleValues.append(calculateBezier(Double(i) * stepSize, x1, x2)) 100 | } 101 | 102 | Cache.dict[key] = sampleValues 103 | 104 | return sampleValues 105 | } 106 | 107 | private static func calculateBezier(_ t: Double, _ a1: Double, _ a2: Double) -> Double { 108 | // use Horner's scheme to evaluate the Bezier polynomial 109 | return ((calculateA(a1, a2) * t + calculateB(a1, a2)) * t + calculateC(a1)) * t 110 | } 111 | 112 | private static func calculateA(_ a1: Double, _ a2: Double) -> Double { 113 | return 1.0 - 3.0 * a2 + 3.0 * a1 114 | } 115 | 116 | private static func calculateB(_ a1: Double, _ a2: Double) -> Double { 117 | return 3.0 * a2 - 6.0 * a1 118 | } 119 | 120 | private static func calculateC(_ a1: Double) -> Double { 121 | return 3.0 * a1 122 | } 123 | 124 | private static func getSlope(_ aT: Double, _ a1: Double, _ a2: Double) -> Double { 125 | return 3.0 * calculateA(a1, a2) * aT * aT + 2.0 * calculateB(a1, a2) * aT + calculateC(a1) 126 | } 127 | 128 | private static func newtonRaphsonIterate( 129 | _ x: Double, _ guessT: Double, _ x1: Double, _ x2: Double 130 | ) -> Double { 131 | var guessT = guessT 132 | // Refine guess with Newton-Raphson iteration 133 | for _ in 0 ..< 4 { 134 | // We're trying to find where f(t) = x, 135 | // so we're actually looking for a root for: CalcBezier(t) - x 136 | let currentX = calculateBezier(guessT, x1, x2) - x 137 | let currentSlope = getSlope(guessT, x1, x2) 138 | if currentSlope == 0.0 { 139 | return guessT 140 | } 141 | guessT -= currentX / currentSlope 142 | } 143 | return guessT 144 | } 145 | 146 | private static func binarySubdivide( 147 | _ x: Double, _ a: Double, _ b: Double, _ x1: Double, _ x2: Double 148 | ) -> Double { 149 | var a = a 150 | var b = b 151 | var currentX: Double = 0 152 | var currentT: Double = 0 153 | var i = 0 154 | repeat { 155 | currentT = a + (b - a) / 2.0 156 | currentX = calculateBezier(currentT, x1, x2) - x 157 | if currentX > 0.0 { 158 | b = currentT 159 | } else { 160 | a = currentT 161 | } 162 | i += 1 163 | } while (fabs(currentX) > 0.0000001) 164 | && (i < 11) 165 | 166 | return currentT 167 | } 168 | } 169 | 170 | private struct SampleKey: Equatable, Hashable { 171 | let x1: Double 172 | let x2: Double 173 | let number: Int 174 | } 175 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Easing Logo 3 |

4 | The Easing library is a comprehensive set of easing functions, useful for interactive transitions and other time-based calculations. 5 | 6 | ## Features 7 | 8 | - Unified [set](#reference) of easing functions 9 | - Easy-to-use 'swifty' API to invoke calculations 10 | - Interpolation shorthands for commonly used types like `CGPoint`, `CGSize`, `CGTransform`, `UIColor` and `UIBezierPath` 11 | - Arbitrary cubic bezier based easings (see `.cubicBezier(...)`) 12 | - Emulate default easings from iOS (see `.caEaseIn`, `.caEaseOut`, `.caEaseInEaseOut`) 13 | - Interactive demo app 14 | - Supports iOS 12.0+ / Mac OS X 10.13+ / tvOS 12.0+ / watchOS 4.0+ / visionOS 1.0+ 15 | 16 | ## Usage 17 | 18 | ### Basic 19 | 20 | ````swift 21 | 22 | let startValue = 20.0 23 | let endValue = 60.0 24 | let progress = 0.5 // Assume a progress variable that ranges from 0 to 1 25 | 26 | let valueAtProgress = Easing.cubicEaseIn.calculate( 27 | d1: startValue, 28 | d2: endValue, 29 | g: progress 30 | ) 31 | ```` 32 | 33 | ### Real world example 34 | 35 | Imagine an interaction with a `UIScrollView` where its header is fully visible when the content offset is zero and fades out completely as the content offset exceeds 100 points. You can express this behavior with the following code in your `scrollViewDidScroll` method: 36 | 37 | ````swift 38 | let minOffset = 0.0 39 | let alphaForMinOffset = 0.0 40 | let maxOffset = 100.0 41 | let alphaForMaxOffset = 1.0 42 | let offset = scrollView.contentOffset.y 43 | 44 | headerView.alpha = Easing.quadraticEaseInOut.calculate( 45 | g1: minOffset, 46 | d1: alphaForMinOffset, 47 | g2: maxOffset, 48 | d2: alphaForMaxOffset, 49 | g: offset 50 | ) 51 | ```` 52 | 53 | ### Interpolatable 54 | 55 | ````swift 56 | let startTransform = CGAffineTransform.identity 57 | let endTransform = CGAffineTransform(scaleX: 2, y: 2) 58 | let progress = 0.5 // Assume a progress variable that ranges from 0 to 1 59 | 60 | view.transform = startTransform.interpolate(to: endTransform, 61 | progress: progress, 62 | easing: .linear) 63 | 64 | ```` 65 | 66 | ## Reference 67 | 68 | | Easing | Curve | 69 | | :---: | :---: | 70 | |`.linear`|| 71 | |`.smoothStep`|| 72 | |`.smootherStep`|| 73 | |`.quadraticEaseIn`|| 74 | |`.quadraticEaseOut`|| 75 | |`.quadraticEaseInOut`|| 76 | |`.cubicEaseIn`|| 77 | |`.cubicEaseOut`|| 78 | |`.cubicEaseInOut`|| 79 | |`.quarticEaseIn`|| 80 | |`.quarticEaseOut`|| 81 | |`.quarticEaseInOut`|| 82 | |`.quinticEaseIn`|| 83 | |`.quinticEaseOut`|| 84 | |`.quinticEaseInOut`|| 85 | |`.sineEaseIn`|| 86 | |`.sineEaseOut`|| 87 | |`.sineEaseInOut`|| 88 | |`.circularEaseIn`|| 89 | |`.circularEaseOut`|| 90 | |`.circularEaseInOut`|| 91 | |`.exponentialEaseIn`|| 92 | |`.exponentialEaseOut`|| 93 | |`.exponentialEaseInOut`|| 94 | |`.elasticEaseIn`|| 95 | |`.elasticEaseOut`|| 96 | |`.elasticEaseInOut`|| 97 | |`.backEaseIn`|| 98 | |`.backEaseOut`|| 99 | |`.backEaseInOut`|| 100 | |`.bounceEaseIn`|| 101 | |`.bounceEaseOut`|| 102 | |`.bounceEaseInOut`|| 103 | |`.caEaseIn`|| 104 | |`.caEaseOut`|| 105 | |`.caEaseInEaseOut`|| 106 | |`.cubicBezier(0.11, 0.87, 0.21, -0.88)`|| 107 | 108 | ## Demo app 109 | In the repo, you will find an interactive demo iOS app to experiment with different easings and discover the most suitable one for your needs. 110 | 111 | 112 | 113 | 114 | ## Integration 115 | 116 | Use Swift Package Manager and add dependency to `Package.swift` file. 117 | 118 | ```swift 119 | dependencies: [ 120 | .package(url: "https://github.com/psharanda/Easing.git", .upToNextMajor(from: "3.0.0")) 121 | ] 122 | ``` 123 | 124 | Alternatively, in Xcode select `File > Add Package Dependencies…` and add Easing repository URL: 125 | 126 | ``` 127 | https://github.com/psharanda/Easing.git 128 | ``` 129 | 130 | ## References 131 | 132 | The main set of easing functions is a Swift port of https://github.com/warrenm/AHEasing and https://github.com/ai/easings.net 133 | 134 | `CubicBezierInterpolator` is a Swift port of `nsSMILKeySpline` code from Mozilla https://github.com/mozilla-services/services-central-legacy/blob/master/content/smil/nsSMILKeySpline.cpp 135 | 136 | ## Contributing 137 | 138 | We welcome contributions! If you find a bug, have a feature request, or want to contribute code, please open an issue or submit a pull request. 139 | 140 | ## License 141 | 142 | Easing is available under the MIT license. See the LICENSE file for more info. 143 | -------------------------------------------------------------------------------- /Sources/Easing/Easing.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2016-present, Pavel Sharanda. All rights reserved. 3 | // 4 | 5 | import Foundation 6 | 7 | public enum Easing { 8 | case linear 9 | 10 | case smoothStep 11 | case smootherStep 12 | 13 | case quadraticEaseIn 14 | case quadraticEaseOut 15 | case quadraticEaseInOut 16 | 17 | case cubicEaseIn 18 | case cubicEaseOut 19 | case cubicEaseInOut 20 | 21 | case quarticEaseIn 22 | case quarticEaseOut 23 | case quarticEaseInOut 24 | 25 | case quinticEaseIn 26 | case quinticEaseOut 27 | case quinticEaseInOut 28 | 29 | case sineEaseIn 30 | case sineEaseOut 31 | case sineEaseInOut 32 | 33 | case circularEaseIn 34 | case circularEaseOut 35 | case circularEaseInOut 36 | 37 | case exponentialEaseIn 38 | case exponentialEaseOut 39 | case exponentialEaseInOut 40 | 41 | case elasticEaseIn 42 | case elasticEaseOut 43 | case elasticEaseInOut 44 | 45 | case backEaseIn 46 | case backEaseOut 47 | case backEaseInOut 48 | 49 | case bounceEaseIn 50 | case bounceEaseOut 51 | case bounceEaseInOut 52 | 53 | case cubicBezier(Double, Double, Double, Double) 54 | 55 | case custom((Double) -> Double) 56 | 57 | /** 58 | Calculate d value for g, where g1 = 0, d1 = 0, g2 = 1, d2 = 1 59 | */ 60 | public func calculate(_ g: Double, clamp: Bool = true) -> Double { 61 | return calculate(g1: 0, d1: 0, g2: 1, d2: 1, g: g, clamp: clamp) 62 | } 63 | 64 | /** 65 | Calculate d value for g, where g1 = 0, d1 = d1, g2 = 1, d2 = d2 66 | */ 67 | public func calculate(d1: Double, d2: Double, g: Double, clamp: Bool = true) -> Double { 68 | return calculate(g1: 0, d1: d1, g2: 1, d2: d2, g: g, clamp: clamp) 69 | } 70 | 71 | /** 72 | Calculate d value for g, where g1 = g1, d1 = d1, g2 = g2, d2 = d2 73 | */ 74 | public func calculate(g1: Double, d1: Double, g2: Double, d2: Double, g: Double, clamp: Bool = true) -> Double { 75 | var g = g 76 | 77 | if clamp { 78 | g = max(min(g1, g2), g) 79 | g = min(max(g1, g2), g) 80 | } 81 | 82 | let t = (g - g1) / (g2 - g1) 83 | let resT = easingFunction(t) 84 | return d1 + resT * (d2 - d1) 85 | } 86 | 87 | private var easingFunction: (Double) -> Double { 88 | switch self { 89 | case .linear: 90 | return Easing._linear 91 | case .smoothStep: 92 | return Easing._smoothStep 93 | case .smootherStep: 94 | return Easing._smootherStep 95 | case .quadraticEaseIn: 96 | return Easing._quarticEaseIn 97 | case .quadraticEaseOut: 98 | return Easing._quadraticEaseOut 99 | case .quadraticEaseInOut: 100 | return Easing._quadraticEaseInOut 101 | case .cubicEaseIn: 102 | return Easing._cubicEaseIn 103 | case .cubicEaseOut: 104 | return Easing._cubicEaseOut 105 | case .cubicEaseInOut: 106 | return Easing._cubicEaseInOut 107 | case .quarticEaseIn: 108 | return Easing._quarticEaseIn 109 | case .quarticEaseOut: 110 | return Easing._quarticEaseOut 111 | case .quarticEaseInOut: 112 | return Easing._quarticEaseInOut 113 | case .quinticEaseIn: 114 | return Easing._quinticEaseIn 115 | case .quinticEaseOut: 116 | return Easing._quinticEaseOut 117 | case .quinticEaseInOut: 118 | return Easing._quinticEaseInOut 119 | case .sineEaseIn: 120 | return Easing._sineEaseIn 121 | case .sineEaseOut: 122 | return Easing._sineEaseOut 123 | case .sineEaseInOut: 124 | return Easing._sineEaseInOut 125 | case .circularEaseIn: 126 | return Easing._circularEaseIn 127 | case .circularEaseOut: 128 | return Easing._circularEaseOut 129 | case .circularEaseInOut: 130 | return Easing._circularEaseInOut 131 | case .exponentialEaseIn: 132 | return Easing._exponentialEaseIn 133 | case .exponentialEaseOut: 134 | return Easing._exponentialEaseOut 135 | case .exponentialEaseInOut: 136 | return Easing._exponentialEaseInOut 137 | case .elasticEaseIn: 138 | return Easing._elasticEaseIn 139 | case .elasticEaseOut: 140 | return Easing._elasticEaseOut 141 | case .elasticEaseInOut: 142 | return Easing._elasticEaseInOut 143 | case .backEaseIn: 144 | return Easing._backEaseIn 145 | case .backEaseOut: 146 | return Easing._backEaseOut 147 | case .backEaseInOut: 148 | return Easing._backEaseInOut 149 | case .bounceEaseIn: 150 | return Easing._bounceEaseIn 151 | case .bounceEaseOut: 152 | return Easing._bounceEaseOut 153 | case .bounceEaseInOut: 154 | return Easing._bounceEaseInOut 155 | case let .cubicBezier(x1, y1, x2, y2): 156 | return { (p: Double) -> Double in 157 | Easing._cubicBezier(p, x1: x1, y1: y1, x2: x2, y2: y2) 158 | } 159 | case let .custom(f): 160 | return f 161 | } 162 | } 163 | 164 | // Based on https://github.com/warrenm/AHEasing and https://github.com/ai/easings.net 165 | 166 | // Modeled after the line y = x 167 | private static func _linear(_ p: Double) -> Double { 168 | return p 169 | } 170 | 171 | // fast but ugly easeInOut 172 | private static func _smoothStep(_ p: Double) -> Double { 173 | return p * p * (3 - 2 * p) 174 | } 175 | 176 | // fast but ugly easeInOut 177 | private static func _smootherStep(_ p: Double) -> Double { 178 | return p * p * p * (p * (p * 6 - 15) + 10) 179 | } 180 | 181 | // Modeled after the parabola y = x^2 182 | private static func _quadraticEaseIn(_ p: Double) -> Double { 183 | return p * p 184 | } 185 | 186 | // Modeled after the parabola y = -x^2 + 2x 187 | private static func _quadraticEaseOut(_ p: Double) -> Double { 188 | return -(p * (p - 2)) 189 | } 190 | 191 | // Modeled after the piecewise quadratic 192 | // y = (1/2)((2x)^2) ; [0, 0.5) 193 | // y = -(1/2)((2x-1)*(2x-3) - 1) ; [0.5, 1] 194 | private static func _quadraticEaseInOut(_ p: Double) -> Double { 195 | if p < 0.5 { 196 | return 2 * p * p 197 | } else { 198 | return (-2 * p * p) + (4 * p) - 1 199 | } 200 | } 201 | 202 | // Modeled after the cubic y = x^3 203 | private static func _cubicEaseIn(_ p: Double) -> Double { 204 | return p * p * p 205 | } 206 | 207 | // Modeled after the cubic y = (x - 1)^3 + 1 208 | private static func _cubicEaseOut(_ p: Double) -> Double { 209 | let f = (p - 1) 210 | return f * f * f + 1 211 | } 212 | 213 | // Modeled after the piecewise cubic 214 | // y = (1/2)((2x)^3) ; [0, 0.5) 215 | // y = (1/2)((2x-2)^3 + 2) ; [0.5, 1] 216 | private static func _cubicEaseInOut(_ p: Double) -> Double { 217 | if p < 0.5 { 218 | return 4 * p * p * p 219 | } else { 220 | let f = ((2 * p) - 2) 221 | return 0.5 * f * f * f + 1 222 | } 223 | } 224 | 225 | // Modeled after the quartic x^4 226 | private static func _quarticEaseIn(_ p: Double) -> Double { 227 | return p * p * p * p 228 | } 229 | 230 | // Modeled after the quartic y = 1 - (x - 1)^4 231 | private static func _quarticEaseOut(_ p: Double) -> Double { 232 | let f = (p - 1) 233 | return f * f * f * (1 - p) + 1 234 | } 235 | 236 | // Modeled after the piecewise quartic 237 | // y = (1/2)((2x)^4) ; [0, 0.5) 238 | // y = -(1/2)((2x-2)^4 - 2) ; [0.5, 1] 239 | private static func _quarticEaseInOut(_ p: Double) -> Double { 240 | if p < 0.5 { 241 | return 8 * p * p * p * p 242 | } else { 243 | let f = (p - 1) 244 | return -8 * f * f * f * f + 1 245 | } 246 | } 247 | 248 | // Modeled after the quintic y = x^5 249 | private static func _quinticEaseIn(_ p: Double) -> Double { 250 | return p * p * p * p * p 251 | } 252 | 253 | // Modeled after the quintic y = (x - 1)^5 + 1 254 | private static func _quinticEaseOut(_ p: Double) -> Double { 255 | let f = (p - 1) 256 | return f * f * f * f * f + 1 257 | } 258 | 259 | // Modeled after the piecewise quintic 260 | // y = (1/2)((2x)^5) ; [0, 0.5) 261 | // y = (1/2)((2x-2)^5 + 2) ; [0.5, 1] 262 | private static func _quinticEaseInOut(_ p: Double) -> Double { 263 | if p < 0.5 { 264 | return 16 * p * p * p * p * p 265 | } else { 266 | let f = ((2 * p) - 2) 267 | return 0.5 * f * f * f * f * f + 1 268 | } 269 | } 270 | 271 | // Modeled after quarter-cycle of sine wave 272 | private static func _sineEaseIn(_ p: Double) -> Double { 273 | return sin((p - 1) * .pi / 2) + 1 274 | } 275 | 276 | // Modeled after quarter-cycle of sine wave (different phase) 277 | private static func _sineEaseOut(_ p: Double) -> Double { 278 | return sin(p * .pi / 2) 279 | } 280 | 281 | // Modeled after half sine wave 282 | private static func _sineEaseInOut(_ p: Double) -> Double { 283 | return 0.5 * (1 - cos(p * .pi)) 284 | } 285 | 286 | // Modeled after shifted quadrant IV of unit circle 287 | private static func _circularEaseIn(_ p: Double) -> Double { 288 | return 1 - sqrt(1 - (p * p)) 289 | } 290 | 291 | // Modeled after shifted quadrant II of unit circle 292 | private static func _circularEaseOut(_ p: Double) -> Double { 293 | return sqrt((2 - p) * p) 294 | } 295 | 296 | // Modeled after the piecewise circular function 297 | // y = (1/2)(1 - sqrt(1 - 4x^2)) ; [0, 0.5) 298 | // y = (1/2)(sqrt(-(2x - 3)*(2x - 1)) + 1) ; [0.5, 1] 299 | private static func _circularEaseInOut(_ p: Double) -> Double { 300 | if p < 0.5 { 301 | return 0.5 * (1 - sqrt(1 - 4 * (p * p))) 302 | } else { 303 | return 0.5 * (sqrt(-((2 * p) - 3) * ((2 * p) - 1)) + 1) 304 | } 305 | } 306 | 307 | // Modeled after the exponential function y = 2^(10(x - 1)) 308 | private static func _exponentialEaseIn(_ p: Double) -> Double { 309 | return (p == 0.0) ? p : pow(2, 10 * (p - 1)) 310 | } 311 | 312 | // Modeled after the exponential function y = -2^(-10x) + 1 313 | private static func _exponentialEaseOut(_ p: Double) -> Double { 314 | return (p == 1.0) ? p : 1 - pow(2, -10 * p) 315 | } 316 | 317 | // Modeled after the piecewise exponential 318 | // y = (1/2)2^(10(2x - 1)) ; [0,0.5) 319 | // y = -(1/2)*2^(-10(2x - 1))) + 1 ; [0.5,1] 320 | private static func _exponentialEaseInOut(_ p: Double) -> Double { 321 | if p == 0.0 || p == 1.0 { 322 | return p 323 | } 324 | 325 | if p < 0.5 { 326 | return 0.5 * pow(2, (20 * p) - 10) 327 | } else { 328 | return -0.5 * pow(2, (-20 * p) + 10) + 1 329 | } 330 | } 331 | 332 | // Modeled after the damped sine wave y = sin(13pi/2*x)*pow(2, 10 * (x - 1)) 333 | private static func _elasticEaseIn(_ p: Double) -> Double { 334 | return sin(13 * .pi / 2 * p) * pow(2, 10 * (p - 1)) 335 | } 336 | 337 | // Modeled after the damped sine wave y = sin(-13pi/2*(x + 1))*pow(2, -10x) + 1 338 | private static func _elasticEaseOut(_ p: Double) -> Double { 339 | return sin(-13 * .pi / 2 * (p + 1)) * pow(2, -10 * p) + 1 340 | } 341 | 342 | // Modeled after the piecewise exponentially-damped sine wave: 343 | // y = (1/2)*sin(13pi/2*(2*x))*pow(2, 10 * ((2*x) - 1)) ; [0,0.5) 344 | // y = (1/2)*(sin(-13pi/2*((2x-1)+1))*pow(2,-10(2*x-1)) + 2) ; [0.5, 1] 345 | private static func _elasticEaseInOut(_ p: Double) -> Double { 346 | if p < 0.5 { 347 | return 0.5 * sin(13 * .pi / 2 * (2 * p)) * pow(2, 10 * ((2 * p) - 1)) 348 | } else { 349 | return 0.5 * (sin(-13 * .pi / 2 * ((2 * p - 1) + 1)) * pow(2, -10 * (2 * p - 1)) + 2) 350 | } 351 | } 352 | 353 | // Modeled after the overshooting cubic y = x^3-x*sin(x*pi) 354 | private static func _backEaseIn(_ p: Double) -> Double { 355 | return p * p * p - p * sin(p * .pi) 356 | } 357 | 358 | // Modeled after overshooting cubic y = 1-((1-x)^3-(1-x)*sin((1-x)*pi)) 359 | private static func _backEaseOut(_ p: Double) -> Double { 360 | let f = (1 - p) 361 | return 1 - (f * f * f - f * sin(f * .pi)) 362 | } 363 | 364 | // Modeled after the piecewise overshooting cubic function: 365 | // y = (1/2)*((2x)^3-(2x)*sin(2*x*pi)) ; [0, 0.5) 366 | // y = (1/2)*(1-((1-x)^3-(1-x)*sin((1-x)*pi))+1) ; [0.5, 1] 367 | private static func _backEaseInOut(_ p: Double) -> Double { 368 | if p < 0.5 { 369 | let f = 2 * p 370 | let x = (f * f * f - f * sin(f * .pi)) 371 | return 0.5 * x 372 | } else { 373 | let f = (1 - (2 * p - 1)) 374 | let x = (f * f * f - f * sin(f * .pi)) 375 | return 0.5 * (1 - x) + 0.5 376 | } 377 | } 378 | 379 | private static func _bounceEaseIn(_ p: Double) -> Double { 380 | return 1 - _bounceEaseOut(1 - p) 381 | } 382 | 383 | private static func _bounceEaseOut(_ p: Double) -> Double { 384 | if p < 4 / 11.0 { 385 | return (121 * p * p) / 16.0 386 | } else if p < 8 / 11.0 { 387 | return (363 / 40.0 * p * p) - (99 / 10.0 * p) + 17 / 5.0 388 | } else if p < 9 / 10.0 { 389 | return (4356 / 361.0 * p * p) - (35442 / 1805.0 * p) + 16061 / 1805.0 390 | } else { 391 | return (54 / 5.0 * p * p) - (513 / 25.0 * p) + 268 / 25.0 392 | } 393 | } 394 | 395 | private static func _bounceEaseInOut(_ p: Double) -> Double { 396 | if p < 0.5 { 397 | return 0.5 * _bounceEaseIn(p * 2) 398 | } else { 399 | return 0.5 * _bounceEaseOut(p * 2 - 1) + 0.5 400 | } 401 | } 402 | 403 | private static func _cubicBezier(_ p: Double, x1: Double, y1: Double, x2: Double, y2: Double) 404 | -> Double 405 | { 406 | return CubicBezierCalculator(x1: x1, y1: y1, x2: x2, y2: y2).calculate(p) 407 | } 408 | } 409 | -------------------------------------------------------------------------------- /Demo/Demo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 56; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1B2723ED2C1473DC00EE2AFB /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B2723EC2C1473DC00EE2AFB /* AppDelegate.swift */; }; 11 | 1B2723EF2C1473DC00EE2AFB /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B2723EE2C1473DC00EE2AFB /* SceneDelegate.swift */; }; 12 | 1B2723F12C1473DC00EE2AFB /* EasingListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B2723F02C1473DC00EE2AFB /* EasingListViewController.swift */; }; 13 | 1B2723F62C1473DE00EE2AFB /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1B2723F52C1473DE00EE2AFB /* Assets.xcassets */; }; 14 | 1B2723F92C1473DE00EE2AFB /* Base in Resources */ = {isa = PBXBuildFile; fileRef = 1B2723F82C1473DE00EE2AFB /* Base */; }; 15 | 1B27240C2C14740C00EE2AFB /* Easing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B2724002C14740C00EE2AFB /* Easing.swift */; }; 16 | 1B27240D2C14740C00EE2AFB /* Easing+CAMediaTimeFunction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B2724012C14740C00EE2AFB /* Easing+CAMediaTimeFunction.swift */; }; 17 | 1B27240E2C14740C00EE2AFB /* CGPrimitives+Interpolatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B2724032C14740C00EE2AFB /* CGPrimitives+Interpolatable.swift */; }; 18 | 1B27240F2C14740C00EE2AFB /* Interpolatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B2724042C14740C00EE2AFB /* Interpolatable.swift */; }; 19 | 1B2724102C14740C00EE2AFB /* UIBezierPath+Interpolatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B2724052C14740C00EE2AFB /* UIBezierPath+Interpolatable.swift */; }; 20 | 1B2724112C14740C00EE2AFB /* UIColor+Interpolatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B2724062C14740C00EE2AFB /* UIColor+Interpolatable.swift */; }; 21 | 1B2724122C14740C00EE2AFB /* CGPath+PathElements.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B2724082C14740C00EE2AFB /* CGPath+PathElements.swift */; }; 22 | 1B2724132C14740C00EE2AFB /* CubicBezierInterpolator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B2724092C14740C00EE2AFB /* CubicBezierInterpolator.swift */; }; 23 | 1B2724162C14816200EE2AFB /* FixFlex in Frameworks */ = {isa = PBXBuildFile; productRef = 1B2724152C14816200EE2AFB /* FixFlex */; }; 24 | 1B2724182C14D20200EE2AFB /* AdaptiveContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B2724172C14D20200EE2AFB /* AdaptiveContentView.swift */; }; 25 | 1B27241B2C14D23600EE2AFB /* EasingDemoCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B27241A2C14D23600EE2AFB /* EasingDemoCell.swift */; }; 26 | 1B27241D2C14D28200EE2AFB /* EasingChartView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B27241C2C14D28200EE2AFB /* EasingChartView.swift */; }; 27 | 1B27241F2C15C3D800EE2AFB /* EasingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B27241E2C15C3D800EE2AFB /* EasingViewController.swift */; }; 28 | 1B2724272C15CB2700EE2AFB /* DemoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B2724262C15CB2700EE2AFB /* DemoTests.swift */; }; 29 | 1B27242F2C15CB9D00EE2AFB /* iOSSnapshotTestCase in Frameworks */ = {isa = PBXBuildFile; productRef = 1B27242E2C15CB9D00EE2AFB /* iOSSnapshotTestCase */; }; 30 | 1B2724312C15CBDB00EE2AFB /* EasingDemo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B2724302C15CBDB00EE2AFB /* EasingDemo.swift */; }; 31 | /* End PBXBuildFile section */ 32 | 33 | /* Begin PBXContainerItemProxy section */ 34 | 1B2724282C15CB2700EE2AFB /* PBXContainerItemProxy */ = { 35 | isa = PBXContainerItemProxy; 36 | containerPortal = 1B2723E12C1473DC00EE2AFB /* Project object */; 37 | proxyType = 1; 38 | remoteGlobalIDString = 1B2723E82C1473DC00EE2AFB; 39 | remoteInfo = Demo; 40 | }; 41 | /* End PBXContainerItemProxy section */ 42 | 43 | /* Begin PBXFileReference section */ 44 | 1B2723E92C1473DC00EE2AFB /* Demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Demo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 45 | 1B2723EC2C1473DC00EE2AFB /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 46 | 1B2723EE2C1473DC00EE2AFB /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 47 | 1B2723F02C1473DC00EE2AFB /* EasingListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EasingListViewController.swift; sourceTree = ""; }; 48 | 1B2723F52C1473DE00EE2AFB /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 49 | 1B2723F82C1473DE00EE2AFB /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 50 | 1B2723FA2C1473DE00EE2AFB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 51 | 1B2724002C14740C00EE2AFB /* Easing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Easing.swift; sourceTree = ""; }; 52 | 1B2724012C14740C00EE2AFB /* Easing+CAMediaTimeFunction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Easing+CAMediaTimeFunction.swift"; sourceTree = ""; }; 53 | 1B2724032C14740C00EE2AFB /* CGPrimitives+Interpolatable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CGPrimitives+Interpolatable.swift"; sourceTree = ""; }; 54 | 1B2724042C14740C00EE2AFB /* Interpolatable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Interpolatable.swift; sourceTree = ""; }; 55 | 1B2724052C14740C00EE2AFB /* UIBezierPath+Interpolatable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIBezierPath+Interpolatable.swift"; sourceTree = ""; }; 56 | 1B2724062C14740C00EE2AFB /* UIColor+Interpolatable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIColor+Interpolatable.swift"; sourceTree = ""; }; 57 | 1B2724082C14740C00EE2AFB /* CGPath+PathElements.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CGPath+PathElements.swift"; sourceTree = ""; }; 58 | 1B2724092C14740C00EE2AFB /* CubicBezierInterpolator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CubicBezierInterpolator.swift; sourceTree = ""; }; 59 | 1B2724172C14D20200EE2AFB /* AdaptiveContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdaptiveContentView.swift; sourceTree = ""; }; 60 | 1B27241A2C14D23600EE2AFB /* EasingDemoCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EasingDemoCell.swift; sourceTree = ""; }; 61 | 1B27241C2C14D28200EE2AFB /* EasingChartView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EasingChartView.swift; sourceTree = ""; }; 62 | 1B27241E2C15C3D800EE2AFB /* EasingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EasingViewController.swift; sourceTree = ""; }; 63 | 1B2724242C15CB2700EE2AFB /* DemoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = DemoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 64 | 1B2724262C15CB2700EE2AFB /* DemoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DemoTests.swift; sourceTree = ""; }; 65 | 1B2724302C15CBDB00EE2AFB /* EasingDemo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EasingDemo.swift; sourceTree = ""; }; 66 | /* End PBXFileReference section */ 67 | 68 | /* Begin PBXFrameworksBuildPhase section */ 69 | 1B2723E62C1473DC00EE2AFB /* Frameworks */ = { 70 | isa = PBXFrameworksBuildPhase; 71 | buildActionMask = 2147483647; 72 | files = ( 73 | 1B2724162C14816200EE2AFB /* FixFlex in Frameworks */, 74 | ); 75 | runOnlyForDeploymentPostprocessing = 0; 76 | }; 77 | 1B2724212C15CB2700EE2AFB /* Frameworks */ = { 78 | isa = PBXFrameworksBuildPhase; 79 | buildActionMask = 2147483647; 80 | files = ( 81 | 1B27242F2C15CB9D00EE2AFB /* iOSSnapshotTestCase in Frameworks */, 82 | ); 83 | runOnlyForDeploymentPostprocessing = 0; 84 | }; 85 | /* End PBXFrameworksBuildPhase section */ 86 | 87 | /* Begin PBXGroup section */ 88 | 1B2723E02C1473DC00EE2AFB = { 89 | isa = PBXGroup; 90 | children = ( 91 | 1B27240B2C14740C00EE2AFB /* Sources */, 92 | 1B2723EB2C1473DC00EE2AFB /* Demo */, 93 | 1B2724252C15CB2700EE2AFB /* DemoTests */, 94 | 1B2723EA2C1473DC00EE2AFB /* Products */, 95 | ); 96 | sourceTree = ""; 97 | }; 98 | 1B2723EA2C1473DC00EE2AFB /* Products */ = { 99 | isa = PBXGroup; 100 | children = ( 101 | 1B2723E92C1473DC00EE2AFB /* Demo.app */, 102 | 1B2724242C15CB2700EE2AFB /* DemoTests.xctest */, 103 | ); 104 | name = Products; 105 | sourceTree = ""; 106 | }; 107 | 1B2723EB2C1473DC00EE2AFB /* Demo */ = { 108 | isa = PBXGroup; 109 | children = ( 110 | 1B2724302C15CBDB00EE2AFB /* EasingDemo.swift */, 111 | 1B2724172C14D20200EE2AFB /* AdaptiveContentView.swift */, 112 | 1B27241C2C14D28200EE2AFB /* EasingChartView.swift */, 113 | 1B27241A2C14D23600EE2AFB /* EasingDemoCell.swift */, 114 | 1B2723F02C1473DC00EE2AFB /* EasingListViewController.swift */, 115 | 1B27241E2C15C3D800EE2AFB /* EasingViewController.swift */, 116 | 1B2723EC2C1473DC00EE2AFB /* AppDelegate.swift */, 117 | 1B2723EE2C1473DC00EE2AFB /* SceneDelegate.swift */, 118 | 1B2723F52C1473DE00EE2AFB /* Assets.xcassets */, 119 | 1B2723F72C1473DE00EE2AFB /* LaunchScreen.storyboard */, 120 | 1B2723FA2C1473DE00EE2AFB /* Info.plist */, 121 | ); 122 | path = Demo; 123 | sourceTree = ""; 124 | }; 125 | 1B2724022C14740C00EE2AFB /* Easing */ = { 126 | isa = PBXGroup; 127 | children = ( 128 | 1B2724002C14740C00EE2AFB /* Easing.swift */, 129 | 1B2724012C14740C00EE2AFB /* Easing+CAMediaTimeFunction.swift */, 130 | ); 131 | path = Easing; 132 | sourceTree = ""; 133 | }; 134 | 1B2724072C14740C00EE2AFB /* Interpolatable */ = { 135 | isa = PBXGroup; 136 | children = ( 137 | 1B2724032C14740C00EE2AFB /* CGPrimitives+Interpolatable.swift */, 138 | 1B2724042C14740C00EE2AFB /* Interpolatable.swift */, 139 | 1B2724052C14740C00EE2AFB /* UIBezierPath+Interpolatable.swift */, 140 | 1B2724062C14740C00EE2AFB /* UIColor+Interpolatable.swift */, 141 | ); 142 | path = Interpolatable; 143 | sourceTree = ""; 144 | }; 145 | 1B27240A2C14740C00EE2AFB /* Utils */ = { 146 | isa = PBXGroup; 147 | children = ( 148 | 1B2724082C14740C00EE2AFB /* CGPath+PathElements.swift */, 149 | 1B2724092C14740C00EE2AFB /* CubicBezierInterpolator.swift */, 150 | ); 151 | path = Utils; 152 | sourceTree = ""; 153 | }; 154 | 1B27240B2C14740C00EE2AFB /* Sources */ = { 155 | isa = PBXGroup; 156 | children = ( 157 | 1B2724022C14740C00EE2AFB /* Easing */, 158 | 1B2724072C14740C00EE2AFB /* Interpolatable */, 159 | 1B27240A2C14740C00EE2AFB /* Utils */, 160 | ); 161 | name = Sources; 162 | path = ../Sources; 163 | sourceTree = ""; 164 | }; 165 | 1B2724252C15CB2700EE2AFB /* DemoTests */ = { 166 | isa = PBXGroup; 167 | children = ( 168 | 1B2724262C15CB2700EE2AFB /* DemoTests.swift */, 169 | ); 170 | path = DemoTests; 171 | sourceTree = ""; 172 | }; 173 | /* End PBXGroup section */ 174 | 175 | /* Begin PBXNativeTarget section */ 176 | 1B2723E82C1473DC00EE2AFB /* Demo */ = { 177 | isa = PBXNativeTarget; 178 | buildConfigurationList = 1B2723FD2C1473DE00EE2AFB /* Build configuration list for PBXNativeTarget "Demo" */; 179 | buildPhases = ( 180 | 1B2723E52C1473DC00EE2AFB /* Sources */, 181 | 1B2723E62C1473DC00EE2AFB /* Frameworks */, 182 | 1B2723E72C1473DC00EE2AFB /* Resources */, 183 | ); 184 | buildRules = ( 185 | ); 186 | dependencies = ( 187 | ); 188 | name = Demo; 189 | packageProductDependencies = ( 190 | 1B2724152C14816200EE2AFB /* FixFlex */, 191 | ); 192 | productName = Demo; 193 | productReference = 1B2723E92C1473DC00EE2AFB /* Demo.app */; 194 | productType = "com.apple.product-type.application"; 195 | }; 196 | 1B2724232C15CB2700EE2AFB /* DemoTests */ = { 197 | isa = PBXNativeTarget; 198 | buildConfigurationList = 1B27242A2C15CB2700EE2AFB /* Build configuration list for PBXNativeTarget "DemoTests" */; 199 | buildPhases = ( 200 | 1B2724202C15CB2700EE2AFB /* Sources */, 201 | 1B2724212C15CB2700EE2AFB /* Frameworks */, 202 | 1B2724222C15CB2700EE2AFB /* Resources */, 203 | ); 204 | buildRules = ( 205 | ); 206 | dependencies = ( 207 | 1B2724292C15CB2700EE2AFB /* PBXTargetDependency */, 208 | ); 209 | name = DemoTests; 210 | packageProductDependencies = ( 211 | 1B27242E2C15CB9D00EE2AFB /* iOSSnapshotTestCase */, 212 | ); 213 | productName = DemoTests; 214 | productReference = 1B2724242C15CB2700EE2AFB /* DemoTests.xctest */; 215 | productType = "com.apple.product-type.bundle.unit-test"; 216 | }; 217 | /* End PBXNativeTarget section */ 218 | 219 | /* Begin PBXProject section */ 220 | 1B2723E12C1473DC00EE2AFB /* Project object */ = { 221 | isa = PBXProject; 222 | attributes = { 223 | BuildIndependentTargetsInParallel = 1; 224 | LastSwiftUpdateCheck = 1530; 225 | LastUpgradeCheck = 1530; 226 | TargetAttributes = { 227 | 1B2723E82C1473DC00EE2AFB = { 228 | CreatedOnToolsVersion = 15.3; 229 | }; 230 | 1B2724232C15CB2700EE2AFB = { 231 | CreatedOnToolsVersion = 15.3; 232 | TestTargetID = 1B2723E82C1473DC00EE2AFB; 233 | }; 234 | }; 235 | }; 236 | buildConfigurationList = 1B2723E42C1473DC00EE2AFB /* Build configuration list for PBXProject "Demo" */; 237 | compatibilityVersion = "Xcode 14.0"; 238 | developmentRegion = en; 239 | hasScannedForEncodings = 0; 240 | knownRegions = ( 241 | en, 242 | Base, 243 | ); 244 | mainGroup = 1B2723E02C1473DC00EE2AFB; 245 | packageReferences = ( 246 | 1B2724142C14816200EE2AFB /* XCRemoteSwiftPackageReference "FixFlex" */, 247 | 1B27242D2C15CB9D00EE2AFB /* XCRemoteSwiftPackageReference "ios-snapshot-test-case" */, 248 | ); 249 | productRefGroup = 1B2723EA2C1473DC00EE2AFB /* Products */; 250 | projectDirPath = ""; 251 | projectRoot = ""; 252 | targets = ( 253 | 1B2723E82C1473DC00EE2AFB /* Demo */, 254 | 1B2724232C15CB2700EE2AFB /* DemoTests */, 255 | ); 256 | }; 257 | /* End PBXProject section */ 258 | 259 | /* Begin PBXResourcesBuildPhase section */ 260 | 1B2723E72C1473DC00EE2AFB /* Resources */ = { 261 | isa = PBXResourcesBuildPhase; 262 | buildActionMask = 2147483647; 263 | files = ( 264 | 1B2723F62C1473DE00EE2AFB /* Assets.xcassets in Resources */, 265 | 1B2723F92C1473DE00EE2AFB /* Base in Resources */, 266 | ); 267 | runOnlyForDeploymentPostprocessing = 0; 268 | }; 269 | 1B2724222C15CB2700EE2AFB /* Resources */ = { 270 | isa = PBXResourcesBuildPhase; 271 | buildActionMask = 2147483647; 272 | files = ( 273 | ); 274 | runOnlyForDeploymentPostprocessing = 0; 275 | }; 276 | /* End PBXResourcesBuildPhase section */ 277 | 278 | /* Begin PBXSourcesBuildPhase section */ 279 | 1B2723E52C1473DC00EE2AFB /* Sources */ = { 280 | isa = PBXSourcesBuildPhase; 281 | buildActionMask = 2147483647; 282 | files = ( 283 | 1B2723F12C1473DC00EE2AFB /* EasingListViewController.swift in Sources */, 284 | 1B2724112C14740C00EE2AFB /* UIColor+Interpolatable.swift in Sources */, 285 | 1B27240F2C14740C00EE2AFB /* Interpolatable.swift in Sources */, 286 | 1B27240D2C14740C00EE2AFB /* Easing+CAMediaTimeFunction.swift in Sources */, 287 | 1B2724132C14740C00EE2AFB /* CubicBezierInterpolator.swift in Sources */, 288 | 1B2724102C14740C00EE2AFB /* UIBezierPath+Interpolatable.swift in Sources */, 289 | 1B2723ED2C1473DC00EE2AFB /* AppDelegate.swift in Sources */, 290 | 1B2724312C15CBDB00EE2AFB /* EasingDemo.swift in Sources */, 291 | 1B27240E2C14740C00EE2AFB /* CGPrimitives+Interpolatable.swift in Sources */, 292 | 1B27240C2C14740C00EE2AFB /* Easing.swift in Sources */, 293 | 1B2724182C14D20200EE2AFB /* AdaptiveContentView.swift in Sources */, 294 | 1B27241F2C15C3D800EE2AFB /* EasingViewController.swift in Sources */, 295 | 1B27241D2C14D28200EE2AFB /* EasingChartView.swift in Sources */, 296 | 1B27241B2C14D23600EE2AFB /* EasingDemoCell.swift in Sources */, 297 | 1B2724122C14740C00EE2AFB /* CGPath+PathElements.swift in Sources */, 298 | 1B2723EF2C1473DC00EE2AFB /* SceneDelegate.swift in Sources */, 299 | ); 300 | runOnlyForDeploymentPostprocessing = 0; 301 | }; 302 | 1B2724202C15CB2700EE2AFB /* Sources */ = { 303 | isa = PBXSourcesBuildPhase; 304 | buildActionMask = 2147483647; 305 | files = ( 306 | 1B2724272C15CB2700EE2AFB /* DemoTests.swift in Sources */, 307 | ); 308 | runOnlyForDeploymentPostprocessing = 0; 309 | }; 310 | /* End PBXSourcesBuildPhase section */ 311 | 312 | /* Begin PBXTargetDependency section */ 313 | 1B2724292C15CB2700EE2AFB /* PBXTargetDependency */ = { 314 | isa = PBXTargetDependency; 315 | target = 1B2723E82C1473DC00EE2AFB /* Demo */; 316 | targetProxy = 1B2724282C15CB2700EE2AFB /* PBXContainerItemProxy */; 317 | }; 318 | /* End PBXTargetDependency section */ 319 | 320 | /* Begin PBXVariantGroup section */ 321 | 1B2723F72C1473DE00EE2AFB /* LaunchScreen.storyboard */ = { 322 | isa = PBXVariantGroup; 323 | children = ( 324 | 1B2723F82C1473DE00EE2AFB /* Base */, 325 | ); 326 | name = LaunchScreen.storyboard; 327 | sourceTree = ""; 328 | }; 329 | /* End PBXVariantGroup section */ 330 | 331 | /* Begin XCBuildConfiguration section */ 332 | 1B2723FB2C1473DE00EE2AFB /* Debug */ = { 333 | isa = XCBuildConfiguration; 334 | buildSettings = { 335 | ALWAYS_SEARCH_USER_PATHS = NO; 336 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 337 | CLANG_ANALYZER_NONNULL = YES; 338 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 339 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 340 | CLANG_ENABLE_MODULES = YES; 341 | CLANG_ENABLE_OBJC_ARC = YES; 342 | CLANG_ENABLE_OBJC_WEAK = YES; 343 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 344 | CLANG_WARN_BOOL_CONVERSION = YES; 345 | CLANG_WARN_COMMA = YES; 346 | CLANG_WARN_CONSTANT_CONVERSION = YES; 347 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 348 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 349 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 350 | CLANG_WARN_EMPTY_BODY = YES; 351 | CLANG_WARN_ENUM_CONVERSION = YES; 352 | CLANG_WARN_INFINITE_RECURSION = YES; 353 | CLANG_WARN_INT_CONVERSION = YES; 354 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 355 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 356 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 357 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 358 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 359 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 360 | CLANG_WARN_STRICT_PROTOTYPES = YES; 361 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 362 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 363 | CLANG_WARN_UNREACHABLE_CODE = YES; 364 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 365 | COPY_PHASE_STRIP = NO; 366 | DEBUG_INFORMATION_FORMAT = dwarf; 367 | ENABLE_STRICT_OBJC_MSGSEND = YES; 368 | ENABLE_TESTABILITY = YES; 369 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 370 | GCC_C_LANGUAGE_STANDARD = gnu17; 371 | GCC_DYNAMIC_NO_PIC = NO; 372 | GCC_NO_COMMON_BLOCKS = YES; 373 | GCC_OPTIMIZATION_LEVEL = 0; 374 | GCC_PREPROCESSOR_DEFINITIONS = ( 375 | "DEBUG=1", 376 | "$(inherited)", 377 | ); 378 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 379 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 380 | GCC_WARN_UNDECLARED_SELECTOR = YES; 381 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 382 | GCC_WARN_UNUSED_FUNCTION = YES; 383 | GCC_WARN_UNUSED_VARIABLE = YES; 384 | IPHONEOS_DEPLOYMENT_TARGET = 17.4; 385 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 386 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 387 | MTL_FAST_MATH = YES; 388 | ONLY_ACTIVE_ARCH = YES; 389 | SDKROOT = iphoneos; 390 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; 391 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 392 | }; 393 | name = Debug; 394 | }; 395 | 1B2723FC2C1473DE00EE2AFB /* Release */ = { 396 | isa = XCBuildConfiguration; 397 | buildSettings = { 398 | ALWAYS_SEARCH_USER_PATHS = NO; 399 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 400 | CLANG_ANALYZER_NONNULL = YES; 401 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 402 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 403 | CLANG_ENABLE_MODULES = YES; 404 | CLANG_ENABLE_OBJC_ARC = YES; 405 | CLANG_ENABLE_OBJC_WEAK = YES; 406 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 407 | CLANG_WARN_BOOL_CONVERSION = YES; 408 | CLANG_WARN_COMMA = YES; 409 | CLANG_WARN_CONSTANT_CONVERSION = YES; 410 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 411 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 412 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 413 | CLANG_WARN_EMPTY_BODY = YES; 414 | CLANG_WARN_ENUM_CONVERSION = YES; 415 | CLANG_WARN_INFINITE_RECURSION = YES; 416 | CLANG_WARN_INT_CONVERSION = YES; 417 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 418 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 419 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 420 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 421 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 422 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 423 | CLANG_WARN_STRICT_PROTOTYPES = YES; 424 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 425 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 426 | CLANG_WARN_UNREACHABLE_CODE = YES; 427 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 428 | COPY_PHASE_STRIP = NO; 429 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 430 | ENABLE_NS_ASSERTIONS = NO; 431 | ENABLE_STRICT_OBJC_MSGSEND = YES; 432 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 433 | GCC_C_LANGUAGE_STANDARD = gnu17; 434 | GCC_NO_COMMON_BLOCKS = YES; 435 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 436 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 437 | GCC_WARN_UNDECLARED_SELECTOR = YES; 438 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 439 | GCC_WARN_UNUSED_FUNCTION = YES; 440 | GCC_WARN_UNUSED_VARIABLE = YES; 441 | IPHONEOS_DEPLOYMENT_TARGET = 17.4; 442 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 443 | MTL_ENABLE_DEBUG_INFO = NO; 444 | MTL_FAST_MATH = YES; 445 | SDKROOT = iphoneos; 446 | SWIFT_COMPILATION_MODE = wholemodule; 447 | VALIDATE_PRODUCT = YES; 448 | }; 449 | name = Release; 450 | }; 451 | 1B2723FE2C1473DE00EE2AFB /* Debug */ = { 452 | isa = XCBuildConfiguration; 453 | buildSettings = { 454 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 455 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 456 | CODE_SIGN_STYLE = Automatic; 457 | CURRENT_PROJECT_VERSION = 1; 458 | DEVELOPMENT_TEAM = 9SCKTYVAB7; 459 | GENERATE_INFOPLIST_FILE = YES; 460 | INFOPLIST_FILE = Demo/Info.plist; 461 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 462 | INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; 463 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 464 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 465 | IPHONEOS_DEPLOYMENT_TARGET = 15.0; 466 | LD_RUNPATH_SEARCH_PATHS = ( 467 | "$(inherited)", 468 | "@executable_path/Frameworks", 469 | ); 470 | MARKETING_VERSION = 1.0; 471 | PRODUCT_BUNDLE_IDENTIFIER = test.psharanda.Easing.Demo.Demo; 472 | PRODUCT_NAME = "$(TARGET_NAME)"; 473 | SWIFT_EMIT_LOC_STRINGS = YES; 474 | SWIFT_VERSION = 5.0; 475 | TARGETED_DEVICE_FAMILY = "1,2"; 476 | }; 477 | name = Debug; 478 | }; 479 | 1B2723FF2C1473DE00EE2AFB /* Release */ = { 480 | isa = XCBuildConfiguration; 481 | buildSettings = { 482 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 483 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 484 | CODE_SIGN_STYLE = Automatic; 485 | CURRENT_PROJECT_VERSION = 1; 486 | DEVELOPMENT_TEAM = 9SCKTYVAB7; 487 | GENERATE_INFOPLIST_FILE = YES; 488 | INFOPLIST_FILE = Demo/Info.plist; 489 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 490 | INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; 491 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 492 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 493 | IPHONEOS_DEPLOYMENT_TARGET = 15.0; 494 | LD_RUNPATH_SEARCH_PATHS = ( 495 | "$(inherited)", 496 | "@executable_path/Frameworks", 497 | ); 498 | MARKETING_VERSION = 1.0; 499 | PRODUCT_BUNDLE_IDENTIFIER = test.psharanda.Easing.Demo.Demo; 500 | PRODUCT_NAME = "$(TARGET_NAME)"; 501 | SWIFT_EMIT_LOC_STRINGS = YES; 502 | SWIFT_VERSION = 5.0; 503 | TARGETED_DEVICE_FAMILY = "1,2"; 504 | }; 505 | name = Release; 506 | }; 507 | 1B27242B2C15CB2700EE2AFB /* Debug */ = { 508 | isa = XCBuildConfiguration; 509 | buildSettings = { 510 | BUNDLE_LOADER = "$(TEST_HOST)"; 511 | CODE_SIGN_STYLE = Automatic; 512 | CURRENT_PROJECT_VERSION = 1; 513 | DEVELOPMENT_TEAM = 9SCKTYVAB7; 514 | GENERATE_INFOPLIST_FILE = YES; 515 | MARKETING_VERSION = 1.0; 516 | PRODUCT_BUNDLE_IDENTIFIER = test.psharanda.Easing.Demo.DemoTests; 517 | PRODUCT_NAME = "$(TARGET_NAME)"; 518 | SWIFT_EMIT_LOC_STRINGS = NO; 519 | SWIFT_VERSION = 5.0; 520 | TARGETED_DEVICE_FAMILY = "1,2"; 521 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Demo.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Demo"; 522 | }; 523 | name = Debug; 524 | }; 525 | 1B27242C2C15CB2700EE2AFB /* Release */ = { 526 | isa = XCBuildConfiguration; 527 | buildSettings = { 528 | BUNDLE_LOADER = "$(TEST_HOST)"; 529 | CODE_SIGN_STYLE = Automatic; 530 | CURRENT_PROJECT_VERSION = 1; 531 | DEVELOPMENT_TEAM = 9SCKTYVAB7; 532 | GENERATE_INFOPLIST_FILE = YES; 533 | MARKETING_VERSION = 1.0; 534 | PRODUCT_BUNDLE_IDENTIFIER = test.psharanda.Easing.Demo.DemoTests; 535 | PRODUCT_NAME = "$(TARGET_NAME)"; 536 | SWIFT_EMIT_LOC_STRINGS = NO; 537 | SWIFT_VERSION = 5.0; 538 | TARGETED_DEVICE_FAMILY = "1,2"; 539 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Demo.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Demo"; 540 | }; 541 | name = Release; 542 | }; 543 | /* End XCBuildConfiguration section */ 544 | 545 | /* Begin XCConfigurationList section */ 546 | 1B2723E42C1473DC00EE2AFB /* Build configuration list for PBXProject "Demo" */ = { 547 | isa = XCConfigurationList; 548 | buildConfigurations = ( 549 | 1B2723FB2C1473DE00EE2AFB /* Debug */, 550 | 1B2723FC2C1473DE00EE2AFB /* Release */, 551 | ); 552 | defaultConfigurationIsVisible = 0; 553 | defaultConfigurationName = Release; 554 | }; 555 | 1B2723FD2C1473DE00EE2AFB /* Build configuration list for PBXNativeTarget "Demo" */ = { 556 | isa = XCConfigurationList; 557 | buildConfigurations = ( 558 | 1B2723FE2C1473DE00EE2AFB /* Debug */, 559 | 1B2723FF2C1473DE00EE2AFB /* Release */, 560 | ); 561 | defaultConfigurationIsVisible = 0; 562 | defaultConfigurationName = Release; 563 | }; 564 | 1B27242A2C15CB2700EE2AFB /* Build configuration list for PBXNativeTarget "DemoTests" */ = { 565 | isa = XCConfigurationList; 566 | buildConfigurations = ( 567 | 1B27242B2C15CB2700EE2AFB /* Debug */, 568 | 1B27242C2C15CB2700EE2AFB /* Release */, 569 | ); 570 | defaultConfigurationIsVisible = 0; 571 | defaultConfigurationName = Release; 572 | }; 573 | /* End XCConfigurationList section */ 574 | 575 | /* Begin XCRemoteSwiftPackageReference section */ 576 | 1B2724142C14816200EE2AFB /* XCRemoteSwiftPackageReference "FixFlex" */ = { 577 | isa = XCRemoteSwiftPackageReference; 578 | repositoryURL = "https://github.com/psharanda/FixFlex"; 579 | requirement = { 580 | kind = upToNextMajorVersion; 581 | minimumVersion = 1.2.1; 582 | }; 583 | }; 584 | 1B27242D2C15CB9D00EE2AFB /* XCRemoteSwiftPackageReference "ios-snapshot-test-case" */ = { 585 | isa = XCRemoteSwiftPackageReference; 586 | repositoryURL = "https://github.com/uber/ios-snapshot-test-case"; 587 | requirement = { 588 | kind = upToNextMajorVersion; 589 | minimumVersion = 8.0.0; 590 | }; 591 | }; 592 | /* End XCRemoteSwiftPackageReference section */ 593 | 594 | /* Begin XCSwiftPackageProductDependency section */ 595 | 1B2724152C14816200EE2AFB /* FixFlex */ = { 596 | isa = XCSwiftPackageProductDependency; 597 | package = 1B2724142C14816200EE2AFB /* XCRemoteSwiftPackageReference "FixFlex" */; 598 | productName = FixFlex; 599 | }; 600 | 1B27242E2C15CB9D00EE2AFB /* iOSSnapshotTestCase */ = { 601 | isa = XCSwiftPackageProductDependency; 602 | package = 1B27242D2C15CB9D00EE2AFB /* XCRemoteSwiftPackageReference "ios-snapshot-test-case" */; 603 | productName = iOSSnapshotTestCase; 604 | }; 605 | /* End XCSwiftPackageProductDependency section */ 606 | }; 607 | rootObject = 1B2723E12C1473DC00EE2AFB /* Project object */; 608 | } 609 | --------------------------------------------------------------------------------