├── .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 |
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 |
--------------------------------------------------------------------------------