├── Examples
├── MatchedAnimationDemo
│ ├── Shared
│ │ ├── Assets.xcassets
│ │ │ ├── Contents.json
│ │ │ ├── AccentColor.colorset
│ │ │ │ └── Contents.json
│ │ │ └── AppIcon.appiconset
│ │ │ │ └── Contents.json
│ │ ├── MatchedAnimationDemoApp.swift
│ │ └── ContentView.swift
│ ├── MatchedAnimationDemo.xcodeproj
│ │ ├── project.xcworkspace
│ │ │ ├── contents.xcworkspacedata
│ │ │ └── xcshareddata
│ │ │ │ └── IDEWorkspaceChecks.plist
│ │ ├── xcshareddata
│ │ │ └── xcschemes
│ │ │ │ ├── MatchedAnimationDemo (iOS).xcscheme
│ │ │ │ └── MatchedAnimationDemo (macOS).xcscheme
│ │ └── project.pbxproj
│ └── macOS
│ │ └── macOS.entitlements
└── Package.swift
├── .gitignore
├── .swiftpm
└── xcode
│ ├── package.xcworkspace
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
│ └── xcshareddata
│ └── xcschemes
│ └── MatchedAnimation.xcscheme
├── Package.swift
├── LICENSE
├── README.md
├── Sources
└── MatchedAnimation
│ ├── MatchedAnimation.swift
│ ├── Animation.swift
│ └── ParsedAnimation.swift
└── Tests
└── MatchedAnimationTests
└── AnimationTests.swift
/Examples/MatchedAnimationDemo/Shared/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 | xcuserdata/
6 | DerivedData/
7 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
8 |
--------------------------------------------------------------------------------
/Examples/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.2
2 |
3 | import PackageDescription
4 |
5 | let package = Package(
6 | name: "Examples",
7 | products: [],
8 | targets: []
9 | )
10 |
--------------------------------------------------------------------------------
/Examples/MatchedAnimationDemo/MatchedAnimationDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Examples/MatchedAnimationDemo/Shared/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 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Examples/MatchedAnimationDemo/MatchedAnimationDemo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Examples/MatchedAnimationDemo/Shared/MatchedAnimationDemoApp.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MatchedAnimationDemoApp.swift
3 | // Shared
4 | //
5 | // Created by Ryan Carver on 12/17/21.
6 | //
7 |
8 | import SwiftUI
9 |
10 | @main
11 | struct MatchedAnimationDemoApp: App {
12 | var body: some Scene {
13 | WindowGroup {
14 | ContentView()
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Examples/MatchedAnimationDemo/macOS/macOS.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.files.user-selected.read-only
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.5
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "swift-matched-animation",
8 | platforms: [
9 | .iOS(.v13),
10 | .macOS(.v11)
11 | ],
12 | products: [
13 | .library(
14 | name: "MatchedAnimation",
15 | targets: ["MatchedAnimation"]),
16 | ],
17 | targets: [
18 | .target(
19 | name: "MatchedAnimation",
20 | dependencies: []),
21 | .testTarget(
22 | name: "MatchedAnimationTests",
23 | dependencies: ["MatchedAnimation"]),
24 | ]
25 | )
26 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Ryan Carver
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # MatchedAnimation
2 |
3 | Match animations in SwiftUI and UIKit/AppKit.
4 |
5 | ```swift
6 | /// Draw a box in UIKit
7 | struct BoxView: UIViewRepresentable {
8 | let size: CGSize
9 | let position: CGPoint
10 | func makeUIView(context: Context) -> UIView {
11 | let view = UIView(frame: .zero)
12 | let box = UIView()
13 | box.backgroundColor = .red
14 | view.addSubview(box)
15 | return view
16 | }
17 | func updateUIView(_ view: UIView, context: Context) {
18 | // 🌟 Wrap changes to UIKit with UIPropertyAnimator that matches the SwiftUI Animation
19 | withMatchedAnimation(context.transaction.animation) {
20 | view.subviews.first?.frame = CGRect(origin: position, size: size)
21 | }
22 | }
23 | }
24 | ```
25 |
26 | 
27 |
28 | ## Limitations
29 |
30 | * FIXME: bezier conversions are not quite right
31 | * Currently only supports easing animations, not spring animations
32 |
33 | ## License
34 |
35 | This library is released under the MIT license. See LICENSE for details.
36 |
--------------------------------------------------------------------------------
/Sources/MatchedAnimation/MatchedAnimation.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | #if canImport(UIKit)
4 | /// Perform the body with animation, matching animations in UIKit and SwiftUI.
5 | ///
6 | /// If the animation cannot be matched, no animation is performed.
7 | public func withMatchedAnimation(_ animation: Animation? = .default, _ body: @escaping () -> Void) {
8 | guard let animation = animation,
9 | let animator = animation.uiPropertyAnimator
10 | else {
11 | return body()
12 | }
13 | animator.addAnimations(body)
14 | withAnimation(animation) {
15 | animator.startAnimation()
16 | }
17 | }
18 | #endif
19 |
20 | #if canImport(AppKit)
21 | /// Perform the body with animation, matching animations in AppKit and SwiftUI.
22 | ///
23 | /// Defaults to implicit animations enabled.
24 | /// If the animation cannot be matched, no animation is performed.
25 | public func withMatchedAnimation(_ animation: Animation?, allowsImplicitAnimation: Bool = true, _ body: @escaping () -> Void) {
26 | guard let animation = animation else {
27 | withoutNSAnimation(body)
28 | return
29 | }
30 | NSAnimationContext.runAnimationGroup { context in
31 | do {
32 | try animation.applyToContext(
33 | context,
34 | allowsImplicitAnimation: allowsImplicitAnimation
35 | )
36 | withAnimation(animation, body)
37 | } catch {
38 | withoutNSAnimation(body)
39 | }
40 | }
41 | }
42 | // Remove default animations that would be used by animator() proxy.
43 | private func withoutNSAnimation(_ body: @escaping () -> Void) {
44 | NSAnimationContext.runAnimationGroup { context in
45 | context.duration = 0
46 | body()
47 | }
48 | }
49 | #endif
50 |
--------------------------------------------------------------------------------
/Tests/MatchedAnimationTests/AnimationTests.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 | import XCTest
3 | @testable import MatchedAnimation
4 |
5 | final class AnimationDurationTests: XCTestCase {
6 | func test_duration() {
7 | XCTAssertEqual(Animation.default.duration, 0.35)
8 | XCTAssertEqual(Animation.linear(duration: 0.5).duration, 0.5)
9 | XCTAssertEqual(Animation.linear(duration: 1.0).duration, 1.0)
10 | XCTAssertEqual(Animation.easeInOut(duration: 1.0).duration, 1.0)
11 | }
12 | func test_duration_speed() {
13 | XCTAssertEqual(Animation.easeInOut(duration: 1.0).speed(2).duration, 2.0)
14 | }
15 | }
16 |
17 | final class AnimationControlPointTests: XCTestCase {
18 | func test_linear() throws {
19 | let p = try XCTUnwrap(Animation.linear.controlPoints)
20 | print(Animation.linear.parse())
21 | XCTAssertEqual(p.cp1, CGPoint(x: 2.0/3.0, y: 2.0/3.0))
22 | XCTAssertEqual(p.cp2, CGPoint(x: 1, y: 1))
23 | }
24 | func test_timingCurve_linear() throws {
25 | let p = try XCTUnwrap(Animation.timingCurve(0, 0, 1, 1).controlPoints)
26 | XCTAssertEqual(p.cp1, CGPoint(x: 2.0/3.0, y: 2.0/3.0))
27 | XCTAssertEqual(p.cp2, CGPoint(x: 1, y: 1))
28 | }
29 | func test_timingCurve_other() throws {
30 | let p = try XCTUnwrap(Animation.timingCurve(0.17,0.67,0.85,0.65).controlPoints)
31 | print(Animation.timingCurve(0.17,0.67,0.85,0.65)
32 | .parse())
33 | XCTAssertEqual(p.cp1, CGPoint(x: 0.17, y: 0.67))
34 | XCTAssertEqual(p.cp2, CGPoint(x: 0.85, y: 0.65))
35 | }
36 | func test_default() throws {
37 | // let p = try XCTUnwrap(Animation.linear.controlPoints)
38 | try XCTSkipIf(true, "unknown values")
39 | }
40 | func test_easeIn() throws {
41 | // let p = try XCTUnwrap(Animation.easeIn.controlPoints)
42 | try XCTSkipIf(true, "unknown values")
43 | }
44 | func test_easeOut() throws {
45 | // let p = try XCTUnwrap(Animation.easeOut.controlPoints)
46 | try XCTSkipIf(true, "unknown values")
47 | }
48 | func test_easeInOut() throws {
49 | // let p = try XCTUnwrap(Animation.easeInOut.controlPoints)
50 | try XCTSkipIf(true, "unknown values")
51 | }
52 | func test_spring() {
53 | XCTAssertNil(Animation.spring().controlPoints)
54 | }
55 | func test_interactiveSpring() {
56 | XCTAssertNil(Animation.interactiveSpring().controlPoints)
57 | }
58 | func test_interpolatingSpring() {
59 | XCTAssertNil(Animation.interpolatingSpring(stiffness: 0.5, damping: 0.5).controlPoints)
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/Sources/MatchedAnimation/Animation.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | #if canImport(UIKit)
4 | extension Animation {
5 | /// Create a UIPropertyAnimator that matches this Animation.
6 | ///
7 | /// Returns nil if anmy required values of the animation couldn't be found.
8 | public var uiPropertyAnimator: UIViewPropertyAnimator? {
9 | let p = parse()
10 | guard let duration = p.duration,
11 | let controlPoints = p.controlPoints
12 | else { return nil }
13 | return UIViewPropertyAnimator(
14 | duration: duration,
15 | controlPoint1: controlPoints.cp1,
16 | controlPoint2: controlPoints.cp2
17 | )
18 | }
19 | }
20 | #endif
21 |
22 | #if canImport(AppKit)
23 | extension Animation {
24 | /// Set the timing function and duration on the animation context.
25 | /// - Parameters:
26 | /// - context: The context to modify
27 | /// - allowsImplicitAnimation: Whether to enable implicit animations.
28 | /// - Throws:
29 | /// - Error if the animation can't be applied.
30 | public func applyToContext(_ context: NSAnimationContext, allowsImplicitAnimation: Bool) throws {
31 | let p = parse()
32 | guard let duration = p.duration else {
33 | throw MatchedAnimationError.missingDuration
34 | }
35 | guard let controlPoints = p.controlPoints else {
36 | throw MatchedAnimationError.missingContolPoints
37 | }
38 | context.duration = duration
39 | context.timingFunction = CAMediaTimingFunction(controlPoints: controlPoints)
40 | context.allowsImplicitAnimation = allowsImplicitAnimation
41 | }
42 | }
43 | extension CAMediaTimingFunction {
44 | convenience init(controlPoints p: ControlPoints) {
45 | self.init(controlPoints: Float(p.cp1.x), Float(p.cp1.y), Float(p.cp2.x), Float(p.cp2.y))
46 | }
47 | }
48 | #endif
49 |
50 | enum MatchedAnimationError: Error {
51 | case missingDuration
52 | case missingContolPoints
53 | }
54 |
55 | extension Animation {
56 |
57 | func parse() -> ParsedAnimation {
58 | var output = ParsedAnimation()
59 | guard let base = Mirror(reflecting: self).children.first else { return output }
60 | parseAnimation(&output, mirror: Mirror(reflecting: base.value))
61 | return output
62 | }
63 |
64 | /// The duration of the animation, if available.
65 | var duration: Double? {
66 | parse().duration
67 | }
68 |
69 | /// The cubic control points for the animation, if available.
70 | var controlPoints: ControlPoints? {
71 | parse().controlPoints
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/xcshareddata/xcschemes/MatchedAnimation.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
33 |
39 |
40 |
41 |
42 |
43 |
53 |
54 |
60 |
61 |
67 |
68 |
69 |
70 |
72 |
73 |
76 |
77 |
78 |
--------------------------------------------------------------------------------
/Examples/MatchedAnimationDemo/Shared/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "scale" : "2x",
6 | "size" : "20x20"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "scale" : "3x",
11 | "size" : "20x20"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "scale" : "2x",
16 | "size" : "29x29"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "scale" : "3x",
21 | "size" : "29x29"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "scale" : "2x",
26 | "size" : "40x40"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "scale" : "3x",
31 | "size" : "40x40"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "scale" : "2x",
36 | "size" : "60x60"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "scale" : "3x",
41 | "size" : "60x60"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "scale" : "1x",
46 | "size" : "20x20"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "scale" : "2x",
51 | "size" : "20x20"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "scale" : "1x",
56 | "size" : "29x29"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "scale" : "2x",
61 | "size" : "29x29"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "scale" : "1x",
66 | "size" : "40x40"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "scale" : "2x",
71 | "size" : "40x40"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "scale" : "1x",
76 | "size" : "76x76"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "scale" : "2x",
81 | "size" : "76x76"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "scale" : "2x",
86 | "size" : "83.5x83.5"
87 | },
88 | {
89 | "idiom" : "ios-marketing",
90 | "scale" : "1x",
91 | "size" : "1024x1024"
92 | },
93 | {
94 | "idiom" : "mac",
95 | "scale" : "1x",
96 | "size" : "16x16"
97 | },
98 | {
99 | "idiom" : "mac",
100 | "scale" : "2x",
101 | "size" : "16x16"
102 | },
103 | {
104 | "idiom" : "mac",
105 | "scale" : "1x",
106 | "size" : "32x32"
107 | },
108 | {
109 | "idiom" : "mac",
110 | "scale" : "2x",
111 | "size" : "32x32"
112 | },
113 | {
114 | "idiom" : "mac",
115 | "scale" : "1x",
116 | "size" : "128x128"
117 | },
118 | {
119 | "idiom" : "mac",
120 | "scale" : "2x",
121 | "size" : "128x128"
122 | },
123 | {
124 | "idiom" : "mac",
125 | "scale" : "1x",
126 | "size" : "256x256"
127 | },
128 | {
129 | "idiom" : "mac",
130 | "scale" : "2x",
131 | "size" : "256x256"
132 | },
133 | {
134 | "idiom" : "mac",
135 | "scale" : "1x",
136 | "size" : "512x512"
137 | },
138 | {
139 | "idiom" : "mac",
140 | "scale" : "2x",
141 | "size" : "512x512"
142 | }
143 | ],
144 | "info" : {
145 | "author" : "xcode",
146 | "version" : 1
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/Examples/MatchedAnimationDemo/MatchedAnimationDemo.xcodeproj/xcshareddata/xcschemes/MatchedAnimationDemo (iOS).xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
33 |
39 |
40 |
41 |
42 |
43 |
53 |
55 |
61 |
62 |
63 |
64 |
70 |
72 |
78 |
79 |
80 |
81 |
83 |
84 |
87 |
88 |
89 |
--------------------------------------------------------------------------------
/Examples/MatchedAnimationDemo/MatchedAnimationDemo.xcodeproj/xcshareddata/xcschemes/MatchedAnimationDemo (macOS).xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
33 |
39 |
40 |
41 |
42 |
43 |
53 |
55 |
61 |
62 |
63 |
64 |
70 |
72 |
78 |
79 |
80 |
81 |
83 |
84 |
87 |
88 |
89 |
--------------------------------------------------------------------------------
/Sources/MatchedAnimation/ParsedAnimation.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | /// The result of parsing an Animation.
4 | struct ParsedAnimation {
5 | var type: AnimationType?
6 | var baseDuration: Double?
7 | var speedMultipler: Double?
8 | }
9 |
10 | extension ParsedAnimation {
11 | var duration: Double? {
12 | guard let d = baseDuration else { return nil }
13 | guard let s = speedMultipler else { return d }
14 | return d * s
15 | }
16 | var controlPoints: ControlPoints? {
17 | switch type {
18 | case .bezier(let points):
19 | return points.controlPoints
20 | case .none:
21 | return nil
22 | }
23 | }
24 | }
25 |
26 | enum AnimationType {
27 | case bezier(BezierPoints)
28 | }
29 |
30 | /// Quadradic bezier; how Animation represents itself internally.
31 | struct BezierPoints {
32 | let ax: CGFloat
33 | let ay: CGFloat
34 | let bx: CGFloat
35 | let by: CGFloat
36 | let cx: CGFloat
37 | let cy: CGFloat
38 | }
39 |
40 | /// Cubic bezier: how we convert to CoreGraphics, UIKit, and AppKit animations.
41 | struct ControlPoints: Equatable {
42 | var cp1: CGPoint
43 | var cp2: CGPoint
44 | }
45 |
46 | extension BezierPoints {
47 | var controlPoints: ControlPoints {
48 | let cp1 = CGPoint(
49 | x: ((2 * cx) - ax) / 3,
50 | y: ((2 * cy) - ay) / 3
51 | )
52 | let cp2 = CGPoint(
53 | x: ((2 * cx) + bx) / 3,
54 | y: ((2 * cy) + by) / 3
55 | )
56 | return ControlPoints(cp1: cp1, cp2: cp2)
57 | }
58 | }
59 |
60 | func parseAnimation(_ output: inout ParsedAnimation, mirror: Mirror) {
61 | let labels = mirror.children.map(\.label)
62 | switch labels {
63 | case ["duration", "curve"]:
64 | parseBezierAnimation(&output, mirror: mirror)
65 | case ["animation", "speed"]:
66 | parseSpeedAnimation(&output, mirror: mirror)
67 | default:
68 | break
69 | }
70 | }
71 |
72 | func parseBezierAnimation(_ output: inout ParsedAnimation, mirror: Mirror) {
73 | for c in mirror.children {
74 | switch c.label {
75 | case "duration":
76 | output.baseDuration = c.value as? Double
77 | case "curve":
78 | parseBezierTimingCurve(&output, mirror: Mirror(reflecting: c.value))
79 | default:
80 | break
81 | }
82 | }
83 | }
84 |
85 | func parseSpeedAnimation(_ output: inout ParsedAnimation, mirror: Mirror) {
86 | for c in mirror.children {
87 | switch c.label {
88 | case "speed":
89 | output.speedMultipler = c.value as? Double
90 | case "animation":
91 | parseAnimation(&output, mirror: Mirror(reflecting: c.value))
92 | default:
93 | break
94 | }
95 | }
96 | }
97 |
98 | func parseBezierTimingCurve(_ output: inout ParsedAnimation, mirror: Mirror) {
99 | var ax, ay, bx, by, cx, cy: Double?
100 | for c in mirror.children {
101 | switch c.label {
102 | case "ax": ax = c.value as? Double
103 | case "ay": ay = c.value as? Double
104 | case "bx": bx = c.value as? Double
105 | case "by": by = c.value as? Double
106 | case "cx": cx = c.value as? Double
107 | case "cy": cy = c.value as? Double
108 | default:
109 | break
110 | }
111 | }
112 | if let ax = ax, let ay = ay, let bx = bx, let by = by, let cx = cx, let cy = cy {
113 | output.type = .bezier(BezierPoints(ax: ax, ay: ay, bx: bx, by: by, cx: cx, cy: cy))
114 | }
115 | }
116 |
117 | func xcontrolPoints() -> ControlPoints? {
118 |
119 | /// The SwiftUI Animation to match
120 | let animation = Animation.easeInOut(duration: 1)
121 | print("input", animation)
122 | //BezierAnimation(duration: 1.0, curve: SwiftUI.(unknown context at $7fff5d38c400).BezierTimingCurve(ax: -2.0, bx: 3.0, cx: 0.0, ay: -2.0, by: 3.0, cy: 0.0))
123 | //BezierAnimation(duration: 1.0, curve: SwiftUI.(unknown context at $7fff5d38c400).BezierTimingCurve(ax: 0.52, bx: -0.78, cx: 1.26, ay: -2.0, by: 3.0, cy: 0.0))
124 |
125 | /// Copy the quadratic points
126 | let startA = CGPoint(x: -0.52, y: -3.0)
127 | let endA = CGPoint(x: -0.78, y: 3.0)
128 | let controlA = CGPoint(x: 1.26, y: 0.0)
129 |
130 | /// Copy the quadratic points
131 | let n: CGFloat = 1
132 | let start = CGPoint(x: startA.x/n, y: startA.y/n)
133 | let end = CGPoint(x: endA.x/n, y: endA.y/n)
134 | let control = CGPoint(x: controlA.x/n, y: controlA.y/n)
135 |
136 | let cp1 = CGPoint(
137 | x: ((2 * control.x) + start.x) / 3,
138 | y: ((2 * control.y) + start.y) / 3
139 | )
140 | let cp2 = CGPoint(
141 | x: ((2 * control.x) + end.x) / 3,
142 | y: ((2 * control.y) + end.y) / 3
143 | )
144 |
145 | // Constructing an animation from control points matches SwiftUI and UIKit
146 | let curve = Animation.timingCurve(cp1.x, cp1.y, cp2.x, cp2.y, duration: 1)
147 | print("curve", curve)
148 |
149 | print("cp1", cp1)
150 | print("cp2", cp2)
151 |
152 | return ControlPoints(cp1: cp1, cp2: cp1)
153 | }
154 |
--------------------------------------------------------------------------------
/Examples/MatchedAnimationDemo/Shared/ContentView.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 | import MatchedAnimation
3 |
4 | struct ContentView: View {
5 |
6 | static let animation: Animation? = .easeInOut(duration: 1)
7 |
8 | @State var flag: Bool = false
9 | @State var isAnimated: Bool = true
10 |
11 | var size: CGSize {
12 | CGSize(width: 40, height: 50)
13 | }
14 |
15 | func position(in container: CGSize) -> CGPoint {
16 | CGPoint(
17 | x: flag ? container.width - self.size.width : 0,
18 | y: 0
19 | )
20 | }
21 |
22 | var offset: CGPoint {
23 | flag ? CGPoint(x: 20, y: 0) : .zero
24 | }
25 |
26 | var body: some View {
27 | ZStack {
28 | VStack {
29 | VStack(alignment: .leading) {
30 | Text("1. SwiftUI position")
31 | Text("2. *Kit position")
32 | Text("3. SwiftUI position and color")
33 | Text("4. *Kit position, SwiftUI color")
34 | }
35 | .font(.caption)
36 | .frame(maxWidth: .infinity, alignment: .leading)
37 |
38 | GeometryReader { g in
39 | VStack {
40 | Group {
41 | SwiftUIBox(
42 | size: self.size,
43 | position: self.position(in: g.size)
44 | )
45 | RepresentedBox(
46 | size: self.size,
47 | position: self.position(in: g.size)
48 | )
49 | Rectangle()
50 | .foregroundColor(flag ? .red : .orange)
51 | .offset(x: self.offset.x, y: self.offset.y)
52 | RepresentedContentBox(
53 | offset: self.offset
54 | ) {
55 | Rectangle()
56 | .foregroundColor(flag ? .red : .orange)
57 | }
58 | }
59 | .frame(height: self.size.height)
60 | .background(Color.primary.opacity(0.1))
61 | }
62 | }
63 | .padding(.vertical, 100)
64 | // Implicit animations work.
65 | //.animation(isAnimated ? Self.animation : nil, value: flag)
66 | }
67 |
68 | VStack {
69 | Button {
70 | // Explicit animations work
71 | withAnimation(isAnimated ? Self.animation : nil) {
72 | flag.toggle()
73 | }
74 | } label: { Text("Flip") }
75 | Toggle("Animated", isOn: $isAnimated)
76 | }
77 | .frame(maxHeight: .infinity, alignment: .bottom)
78 | }
79 | .padding()
80 | }
81 | }
82 |
83 | struct ContentView_Previews: PreviewProvider {
84 | static var previews: some View {
85 | ContentView()
86 | }
87 | }
88 |
89 | struct SwiftUIBox: View {
90 | let size: CGSize
91 | let position: CGPoint
92 | var body: some View {
93 | Rectangle()
94 | .foregroundColor(.blue)
95 | .frame(width: size.width, height: size.height)
96 | .position(x: position.x + (size.width/2), y: position.y + (size.height/2))
97 | }
98 | }
99 |
100 | struct RepresentedBox {
101 | let size: CGSize
102 | let position: CGPoint
103 | }
104 |
105 | struct RepresentedContentBox {
106 | let offset: CGPoint
107 | let content: () -> Content
108 | func makeCoordinator() -> Coordinator {
109 | Coordinator(model: ViewModel(content: content))
110 | }
111 | class Coordinator {
112 | init(model: ViewModel) {
113 | self.model = model
114 | }
115 | var model: ViewModel
116 | }
117 | class ViewModel: ObservableObject {
118 | init(content: @escaping () -> Content) {
119 | self.content = content
120 | }
121 | @Published var content: () -> Content
122 | }
123 | struct ViewWrapper: View {
124 | @ObservedObject var model: ViewModel
125 | var body: some View {
126 | model.content()
127 | }
128 | }
129 | }
130 |
131 | #if canImport(UIKit)
132 | extension RepresentedBox: UIViewRepresentable {
133 | func makeUIView(context: Context) -> UIView {
134 | let view = UIView(frame: .zero)
135 | let box = UIView()
136 | box.backgroundColor = .red
137 | view.addSubview(box)
138 | return view
139 | }
140 | func updateUIView(_ view: UIView, context: Context) {
141 | withMatchedAnimation(context.transaction.animation) {
142 | view.subviews.first?.frame = CGRect(origin: position, size: size)
143 | }
144 | }
145 | }
146 | extension RepresentedContentBox: UIViewControllerRepresentable {
147 | func makeUIViewController(context: Context) -> UIHostingController {
148 | UIHostingController(rootView: ViewWrapper(model: context.coordinator.model))
149 | }
150 | func updateUIViewController(_ controller: UIHostingController, context: Context) {
151 | withMatchedAnimation(context.transaction.animation) {
152 | controller.view.frame.origin = offset
153 | context.coordinator.model.content = content
154 | }
155 | }
156 | }
157 | #endif
158 |
159 | #if canImport(AppKit)
160 | extension RepresentedBox: NSViewRepresentable {
161 | func makeNSView(context: Context) -> NSView {
162 | let view = NSView(frame: .zero)
163 | let box = NSView()
164 | box.wantsLayer = true
165 | box.layer?.backgroundColor = NSColor.red.cgColor
166 | view.addSubview(box)
167 | return view
168 | }
169 | func updateNSView(_ view: NSView, context: Context) {
170 | withMatchedAnimation(context.transaction.animation) {
171 | // Using animator proxy shows that animations are removed from implicit and explicit animations
172 | view.subviews.first?.animator().frame = CGRect(origin: position, size: size)
173 | }
174 | }
175 | }
176 | extension RepresentedContentBox: NSViewControllerRepresentable {
177 | func makeNSViewController(context: Context) -> NSHostingController {
178 | NSHostingController(rootView: ViewWrapper(model: context.coordinator.model))
179 | }
180 | func updateNSViewController(_ controller: NSHostingController, context: Context) {
181 | withMatchedAnimation(context.transaction.animation) {
182 | // Not using animator proxy shows that animations are applied to implicit animations.
183 | controller.view.frame.origin = offset
184 | context.coordinator.model.content = content
185 | }
186 | }
187 | }
188 | #endif
189 |
--------------------------------------------------------------------------------
/Examples/MatchedAnimationDemo/MatchedAnimationDemo.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 55;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | C402FF1F276D9CC80049E48B /* MatchedAnimationDemoApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = C402FF0F276D9CC60049E48B /* MatchedAnimationDemoApp.swift */; };
11 | C402FF20276D9CC80049E48B /* MatchedAnimationDemoApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = C402FF0F276D9CC60049E48B /* MatchedAnimationDemoApp.swift */; };
12 | C402FF21276D9CC80049E48B /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C402FF10276D9CC60049E48B /* ContentView.swift */; };
13 | C402FF22276D9CC80049E48B /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C402FF10276D9CC60049E48B /* ContentView.swift */; };
14 | C402FF23276D9CC80049E48B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C402FF11276D9CC70049E48B /* Assets.xcassets */; };
15 | C402FF24276D9CC80049E48B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C402FF11276D9CC70049E48B /* Assets.xcassets */; };
16 | C402FF34276DA4300049E48B /* MatchedAnimation in Frameworks */ = {isa = PBXBuildFile; productRef = C402FF33276DA4300049E48B /* MatchedAnimation */; };
17 | C402FF36276DA4390049E48B /* MatchedAnimation in Frameworks */ = {isa = PBXBuildFile; productRef = C402FF35276DA4390049E48B /* MatchedAnimation */; };
18 | /* End PBXBuildFile section */
19 |
20 | /* Begin PBXFileReference section */
21 | C402FF0F276D9CC60049E48B /* MatchedAnimationDemoApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatchedAnimationDemoApp.swift; sourceTree = ""; };
22 | C402FF10276D9CC60049E48B /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; };
23 | C402FF11276D9CC70049E48B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
24 | C402FF16276D9CC80049E48B /* MatchedAnimationDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MatchedAnimationDemo.app; sourceTree = BUILT_PRODUCTS_DIR; };
25 | C402FF1C276D9CC80049E48B /* MatchedAnimationDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MatchedAnimationDemo.app; sourceTree = BUILT_PRODUCTS_DIR; };
26 | C402FF1E276D9CC80049E48B /* macOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = macOS.entitlements; sourceTree = ""; };
27 | C402FF37276DAA940049E48B /* swift-matched-animation */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = "swift-matched-animation"; path = ../..; sourceTree = ""; };
28 | /* End PBXFileReference section */
29 |
30 | /* Begin PBXFrameworksBuildPhase section */
31 | C402FF13276D9CC80049E48B /* Frameworks */ = {
32 | isa = PBXFrameworksBuildPhase;
33 | buildActionMask = 2147483647;
34 | files = (
35 | C402FF34276DA4300049E48B /* MatchedAnimation in Frameworks */,
36 | );
37 | runOnlyForDeploymentPostprocessing = 0;
38 | };
39 | C402FF19276D9CC80049E48B /* Frameworks */ = {
40 | isa = PBXFrameworksBuildPhase;
41 | buildActionMask = 2147483647;
42 | files = (
43 | C402FF36276DA4390049E48B /* MatchedAnimation in Frameworks */,
44 | );
45 | runOnlyForDeploymentPostprocessing = 0;
46 | };
47 | /* End PBXFrameworksBuildPhase section */
48 |
49 | /* Begin PBXGroup section */
50 | C402FF09276D9CC50049E48B = {
51 | isa = PBXGroup;
52 | children = (
53 | C402FF37276DAA940049E48B /* swift-matched-animation */,
54 | C402FF0E276D9CC60049E48B /* Shared */,
55 | C402FF1D276D9CC80049E48B /* macOS */,
56 | C402FF17276D9CC80049E48B /* Products */,
57 | C402FF2E276D9CEF0049E48B /* Frameworks */,
58 | );
59 | sourceTree = "";
60 | };
61 | C402FF0E276D9CC60049E48B /* Shared */ = {
62 | isa = PBXGroup;
63 | children = (
64 | C402FF0F276D9CC60049E48B /* MatchedAnimationDemoApp.swift */,
65 | C402FF10276D9CC60049E48B /* ContentView.swift */,
66 | C402FF11276D9CC70049E48B /* Assets.xcassets */,
67 | );
68 | path = Shared;
69 | sourceTree = "";
70 | };
71 | C402FF17276D9CC80049E48B /* Products */ = {
72 | isa = PBXGroup;
73 | children = (
74 | C402FF16276D9CC80049E48B /* MatchedAnimationDemo.app */,
75 | C402FF1C276D9CC80049E48B /* MatchedAnimationDemo.app */,
76 | );
77 | name = Products;
78 | sourceTree = "";
79 | };
80 | C402FF1D276D9CC80049E48B /* macOS */ = {
81 | isa = PBXGroup;
82 | children = (
83 | C402FF1E276D9CC80049E48B /* macOS.entitlements */,
84 | );
85 | path = macOS;
86 | sourceTree = "";
87 | };
88 | C402FF2E276D9CEF0049E48B /* Frameworks */ = {
89 | isa = PBXGroup;
90 | children = (
91 | );
92 | name = Frameworks;
93 | sourceTree = "";
94 | };
95 | /* End PBXGroup section */
96 |
97 | /* Begin PBXNativeTarget section */
98 | C402FF15276D9CC80049E48B /* MatchedAnimationDemo (iOS) */ = {
99 | isa = PBXNativeTarget;
100 | buildConfigurationList = C402FF27276D9CC80049E48B /* Build configuration list for PBXNativeTarget "MatchedAnimationDemo (iOS)" */;
101 | buildPhases = (
102 | C402FF12276D9CC80049E48B /* Sources */,
103 | C402FF13276D9CC80049E48B /* Frameworks */,
104 | C402FF14276D9CC80049E48B /* Resources */,
105 | );
106 | buildRules = (
107 | );
108 | dependencies = (
109 | );
110 | name = "MatchedAnimationDemo (iOS)";
111 | packageProductDependencies = (
112 | C402FF33276DA4300049E48B /* MatchedAnimation */,
113 | );
114 | productName = "MatchedAnimationDemo (iOS)";
115 | productReference = C402FF16276D9CC80049E48B /* MatchedAnimationDemo.app */;
116 | productType = "com.apple.product-type.application";
117 | };
118 | C402FF1B276D9CC80049E48B /* MatchedAnimationDemo (macOS) */ = {
119 | isa = PBXNativeTarget;
120 | buildConfigurationList = C402FF2A276D9CC80049E48B /* Build configuration list for PBXNativeTarget "MatchedAnimationDemo (macOS)" */;
121 | buildPhases = (
122 | C402FF18276D9CC80049E48B /* Sources */,
123 | C402FF19276D9CC80049E48B /* Frameworks */,
124 | C402FF1A276D9CC80049E48B /* Resources */,
125 | );
126 | buildRules = (
127 | );
128 | dependencies = (
129 | );
130 | name = "MatchedAnimationDemo (macOS)";
131 | packageProductDependencies = (
132 | C402FF35276DA4390049E48B /* MatchedAnimation */,
133 | );
134 | productName = "MatchedAnimationDemo (macOS)";
135 | productReference = C402FF1C276D9CC80049E48B /* MatchedAnimationDemo.app */;
136 | productType = "com.apple.product-type.application";
137 | };
138 | /* End PBXNativeTarget section */
139 |
140 | /* Begin PBXProject section */
141 | C402FF0A276D9CC50049E48B /* Project object */ = {
142 | isa = PBXProject;
143 | attributes = {
144 | BuildIndependentTargetsInParallel = 1;
145 | LastSwiftUpdateCheck = 1320;
146 | LastUpgradeCheck = 1320;
147 | TargetAttributes = {
148 | C402FF15276D9CC80049E48B = {
149 | CreatedOnToolsVersion = 13.2;
150 | };
151 | C402FF1B276D9CC80049E48B = {
152 | CreatedOnToolsVersion = 13.2;
153 | };
154 | };
155 | };
156 | buildConfigurationList = C402FF0D276D9CC50049E48B /* Build configuration list for PBXProject "MatchedAnimationDemo" */;
157 | compatibilityVersion = "Xcode 13.0";
158 | developmentRegion = en;
159 | hasScannedForEncodings = 0;
160 | knownRegions = (
161 | en,
162 | Base,
163 | );
164 | mainGroup = C402FF09276D9CC50049E48B;
165 | productRefGroup = C402FF17276D9CC80049E48B /* Products */;
166 | projectDirPath = "";
167 | projectRoot = "";
168 | targets = (
169 | C402FF15276D9CC80049E48B /* MatchedAnimationDemo (iOS) */,
170 | C402FF1B276D9CC80049E48B /* MatchedAnimationDemo (macOS) */,
171 | );
172 | };
173 | /* End PBXProject section */
174 |
175 | /* Begin PBXResourcesBuildPhase section */
176 | C402FF14276D9CC80049E48B /* Resources */ = {
177 | isa = PBXResourcesBuildPhase;
178 | buildActionMask = 2147483647;
179 | files = (
180 | C402FF23276D9CC80049E48B /* Assets.xcassets in Resources */,
181 | );
182 | runOnlyForDeploymentPostprocessing = 0;
183 | };
184 | C402FF1A276D9CC80049E48B /* Resources */ = {
185 | isa = PBXResourcesBuildPhase;
186 | buildActionMask = 2147483647;
187 | files = (
188 | C402FF24276D9CC80049E48B /* Assets.xcassets in Resources */,
189 | );
190 | runOnlyForDeploymentPostprocessing = 0;
191 | };
192 | /* End PBXResourcesBuildPhase section */
193 |
194 | /* Begin PBXSourcesBuildPhase section */
195 | C402FF12276D9CC80049E48B /* Sources */ = {
196 | isa = PBXSourcesBuildPhase;
197 | buildActionMask = 2147483647;
198 | files = (
199 | C402FF21276D9CC80049E48B /* ContentView.swift in Sources */,
200 | C402FF1F276D9CC80049E48B /* MatchedAnimationDemoApp.swift in Sources */,
201 | );
202 | runOnlyForDeploymentPostprocessing = 0;
203 | };
204 | C402FF18276D9CC80049E48B /* Sources */ = {
205 | isa = PBXSourcesBuildPhase;
206 | buildActionMask = 2147483647;
207 | files = (
208 | C402FF22276D9CC80049E48B /* ContentView.swift in Sources */,
209 | C402FF20276D9CC80049E48B /* MatchedAnimationDemoApp.swift in Sources */,
210 | );
211 | runOnlyForDeploymentPostprocessing = 0;
212 | };
213 | /* End PBXSourcesBuildPhase section */
214 |
215 | /* Begin XCBuildConfiguration section */
216 | C402FF25276D9CC80049E48B /* Debug */ = {
217 | isa = XCBuildConfiguration;
218 | buildSettings = {
219 | ALWAYS_SEARCH_USER_PATHS = NO;
220 | CLANG_ANALYZER_NONNULL = YES;
221 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
222 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
223 | CLANG_CXX_LIBRARY = "libc++";
224 | CLANG_ENABLE_MODULES = YES;
225 | CLANG_ENABLE_OBJC_ARC = YES;
226 | CLANG_ENABLE_OBJC_WEAK = YES;
227 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
228 | CLANG_WARN_BOOL_CONVERSION = YES;
229 | CLANG_WARN_COMMA = YES;
230 | CLANG_WARN_CONSTANT_CONVERSION = YES;
231 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
232 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
233 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
234 | CLANG_WARN_EMPTY_BODY = YES;
235 | CLANG_WARN_ENUM_CONVERSION = YES;
236 | CLANG_WARN_INFINITE_RECURSION = YES;
237 | CLANG_WARN_INT_CONVERSION = YES;
238 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
239 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
240 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
241 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
242 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
243 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
244 | CLANG_WARN_STRICT_PROTOTYPES = YES;
245 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
246 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
247 | CLANG_WARN_UNREACHABLE_CODE = YES;
248 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
249 | COPY_PHASE_STRIP = NO;
250 | DEBUG_INFORMATION_FORMAT = dwarf;
251 | ENABLE_STRICT_OBJC_MSGSEND = YES;
252 | ENABLE_TESTABILITY = YES;
253 | GCC_C_LANGUAGE_STANDARD = gnu11;
254 | GCC_DYNAMIC_NO_PIC = NO;
255 | GCC_NO_COMMON_BLOCKS = YES;
256 | GCC_OPTIMIZATION_LEVEL = 0;
257 | GCC_PREPROCESSOR_DEFINITIONS = (
258 | "DEBUG=1",
259 | "$(inherited)",
260 | );
261 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
262 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
263 | GCC_WARN_UNDECLARED_SELECTOR = YES;
264 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
265 | GCC_WARN_UNUSED_FUNCTION = YES;
266 | GCC_WARN_UNUSED_VARIABLE = YES;
267 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
268 | MTL_FAST_MATH = YES;
269 | ONLY_ACTIVE_ARCH = YES;
270 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
271 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
272 | };
273 | name = Debug;
274 | };
275 | C402FF26276D9CC80049E48B /* Release */ = {
276 | isa = XCBuildConfiguration;
277 | buildSettings = {
278 | ALWAYS_SEARCH_USER_PATHS = NO;
279 | CLANG_ANALYZER_NONNULL = YES;
280 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
281 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
282 | CLANG_CXX_LIBRARY = "libc++";
283 | CLANG_ENABLE_MODULES = YES;
284 | CLANG_ENABLE_OBJC_ARC = YES;
285 | CLANG_ENABLE_OBJC_WEAK = YES;
286 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
287 | CLANG_WARN_BOOL_CONVERSION = YES;
288 | CLANG_WARN_COMMA = YES;
289 | CLANG_WARN_CONSTANT_CONVERSION = YES;
290 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
291 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
292 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
293 | CLANG_WARN_EMPTY_BODY = YES;
294 | CLANG_WARN_ENUM_CONVERSION = YES;
295 | CLANG_WARN_INFINITE_RECURSION = YES;
296 | CLANG_WARN_INT_CONVERSION = YES;
297 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
298 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
299 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
300 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
301 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
302 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
303 | CLANG_WARN_STRICT_PROTOTYPES = YES;
304 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
305 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
306 | CLANG_WARN_UNREACHABLE_CODE = YES;
307 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
308 | COPY_PHASE_STRIP = NO;
309 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
310 | ENABLE_NS_ASSERTIONS = NO;
311 | ENABLE_STRICT_OBJC_MSGSEND = YES;
312 | GCC_C_LANGUAGE_STANDARD = gnu11;
313 | GCC_NO_COMMON_BLOCKS = YES;
314 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
315 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
316 | GCC_WARN_UNDECLARED_SELECTOR = YES;
317 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
318 | GCC_WARN_UNUSED_FUNCTION = YES;
319 | GCC_WARN_UNUSED_VARIABLE = YES;
320 | MTL_ENABLE_DEBUG_INFO = NO;
321 | MTL_FAST_MATH = YES;
322 | SWIFT_COMPILATION_MODE = wholemodule;
323 | SWIFT_OPTIMIZATION_LEVEL = "-O";
324 | };
325 | name = Release;
326 | };
327 | C402FF28276D9CC80049E48B /* Debug */ = {
328 | isa = XCBuildConfiguration;
329 | buildSettings = {
330 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
331 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
332 | CODE_SIGN_STYLE = Automatic;
333 | CURRENT_PROJECT_VERSION = 1;
334 | DEVELOPMENT_TEAM = 6P93C5F6VS;
335 | ENABLE_PREVIEWS = YES;
336 | GENERATE_INFOPLIST_FILE = YES;
337 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
338 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
339 | INFOPLIST_KEY_UILaunchScreen_Generation = YES;
340 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
341 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
342 | IPHONEOS_DEPLOYMENT_TARGET = 15.2;
343 | LD_RUNPATH_SEARCH_PATHS = (
344 | "$(inherited)",
345 | "@executable_path/Frameworks",
346 | );
347 | MARKETING_VERSION = 1.0;
348 | PRODUCT_BUNDLE_IDENTIFIER = Recentralized.MatchedAnimationDemo;
349 | PRODUCT_NAME = MatchedAnimationDemo;
350 | SDKROOT = iphoneos;
351 | SWIFT_EMIT_LOC_STRINGS = YES;
352 | SWIFT_VERSION = 5.0;
353 | TARGETED_DEVICE_FAMILY = "1,2";
354 | };
355 | name = Debug;
356 | };
357 | C402FF29276D9CC80049E48B /* Release */ = {
358 | isa = XCBuildConfiguration;
359 | buildSettings = {
360 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
361 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
362 | CODE_SIGN_STYLE = Automatic;
363 | CURRENT_PROJECT_VERSION = 1;
364 | DEVELOPMENT_TEAM = 6P93C5F6VS;
365 | ENABLE_PREVIEWS = YES;
366 | GENERATE_INFOPLIST_FILE = YES;
367 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
368 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
369 | INFOPLIST_KEY_UILaunchScreen_Generation = YES;
370 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
371 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
372 | IPHONEOS_DEPLOYMENT_TARGET = 15.2;
373 | LD_RUNPATH_SEARCH_PATHS = (
374 | "$(inherited)",
375 | "@executable_path/Frameworks",
376 | );
377 | MARKETING_VERSION = 1.0;
378 | PRODUCT_BUNDLE_IDENTIFIER = Recentralized.MatchedAnimationDemo;
379 | PRODUCT_NAME = MatchedAnimationDemo;
380 | SDKROOT = iphoneos;
381 | SWIFT_EMIT_LOC_STRINGS = YES;
382 | SWIFT_VERSION = 5.0;
383 | TARGETED_DEVICE_FAMILY = "1,2";
384 | VALIDATE_PRODUCT = YES;
385 | };
386 | name = Release;
387 | };
388 | C402FF2B276D9CC80049E48B /* Debug */ = {
389 | isa = XCBuildConfiguration;
390 | buildSettings = {
391 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
392 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
393 | CODE_SIGN_ENTITLEMENTS = macOS/macOS.entitlements;
394 | CODE_SIGN_STYLE = Automatic;
395 | COMBINE_HIDPI_IMAGES = YES;
396 | CURRENT_PROJECT_VERSION = 1;
397 | DEVELOPMENT_TEAM = 6P93C5F6VS;
398 | ENABLE_HARDENED_RUNTIME = YES;
399 | ENABLE_PREVIEWS = YES;
400 | GENERATE_INFOPLIST_FILE = YES;
401 | INFOPLIST_KEY_NSHumanReadableCopyright = "";
402 | LD_RUNPATH_SEARCH_PATHS = (
403 | "$(inherited)",
404 | "@executable_path/../Frameworks",
405 | );
406 | MACOSX_DEPLOYMENT_TARGET = 12.0;
407 | MARKETING_VERSION = 1.0;
408 | PRODUCT_BUNDLE_IDENTIFIER = Recentralized.MatchedAnimationDemo;
409 | PRODUCT_NAME = MatchedAnimationDemo;
410 | SDKROOT = macosx;
411 | SWIFT_EMIT_LOC_STRINGS = YES;
412 | SWIFT_VERSION = 5.0;
413 | };
414 | name = Debug;
415 | };
416 | C402FF2C276D9CC80049E48B /* Release */ = {
417 | isa = XCBuildConfiguration;
418 | buildSettings = {
419 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
420 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
421 | CODE_SIGN_ENTITLEMENTS = macOS/macOS.entitlements;
422 | CODE_SIGN_STYLE = Automatic;
423 | COMBINE_HIDPI_IMAGES = YES;
424 | CURRENT_PROJECT_VERSION = 1;
425 | DEVELOPMENT_TEAM = 6P93C5F6VS;
426 | ENABLE_HARDENED_RUNTIME = YES;
427 | ENABLE_PREVIEWS = YES;
428 | GENERATE_INFOPLIST_FILE = YES;
429 | INFOPLIST_KEY_NSHumanReadableCopyright = "";
430 | LD_RUNPATH_SEARCH_PATHS = (
431 | "$(inherited)",
432 | "@executable_path/../Frameworks",
433 | );
434 | MACOSX_DEPLOYMENT_TARGET = 12.0;
435 | MARKETING_VERSION = 1.0;
436 | PRODUCT_BUNDLE_IDENTIFIER = Recentralized.MatchedAnimationDemo;
437 | PRODUCT_NAME = MatchedAnimationDemo;
438 | SDKROOT = macosx;
439 | SWIFT_EMIT_LOC_STRINGS = YES;
440 | SWIFT_VERSION = 5.0;
441 | };
442 | name = Release;
443 | };
444 | /* End XCBuildConfiguration section */
445 |
446 | /* Begin XCConfigurationList section */
447 | C402FF0D276D9CC50049E48B /* Build configuration list for PBXProject "MatchedAnimationDemo" */ = {
448 | isa = XCConfigurationList;
449 | buildConfigurations = (
450 | C402FF25276D9CC80049E48B /* Debug */,
451 | C402FF26276D9CC80049E48B /* Release */,
452 | );
453 | defaultConfigurationIsVisible = 0;
454 | defaultConfigurationName = Release;
455 | };
456 | C402FF27276D9CC80049E48B /* Build configuration list for PBXNativeTarget "MatchedAnimationDemo (iOS)" */ = {
457 | isa = XCConfigurationList;
458 | buildConfigurations = (
459 | C402FF28276D9CC80049E48B /* Debug */,
460 | C402FF29276D9CC80049E48B /* Release */,
461 | );
462 | defaultConfigurationIsVisible = 0;
463 | defaultConfigurationName = Release;
464 | };
465 | C402FF2A276D9CC80049E48B /* Build configuration list for PBXNativeTarget "MatchedAnimationDemo (macOS)" */ = {
466 | isa = XCConfigurationList;
467 | buildConfigurations = (
468 | C402FF2B276D9CC80049E48B /* Debug */,
469 | C402FF2C276D9CC80049E48B /* Release */,
470 | );
471 | defaultConfigurationIsVisible = 0;
472 | defaultConfigurationName = Release;
473 | };
474 | /* End XCConfigurationList section */
475 |
476 | /* Begin XCSwiftPackageProductDependency section */
477 | C402FF33276DA4300049E48B /* MatchedAnimation */ = {
478 | isa = XCSwiftPackageProductDependency;
479 | productName = MatchedAnimation;
480 | };
481 | C402FF35276DA4390049E48B /* MatchedAnimation */ = {
482 | isa = XCSwiftPackageProductDependency;
483 | productName = MatchedAnimation;
484 | };
485 | /* End XCSwiftPackageProductDependency section */
486 | };
487 | rootObject = C402FF0A276D9CC50049E48B /* Project object */;
488 | }
489 |
--------------------------------------------------------------------------------