Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for nesting animation groups #74

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
runs-on: macOS-11
strategy:
matrix:
platform: ['iOS_13']
platform: ['iOS_15', 'iOS_13']
fail-fast: false
steps:
- name: Checkout Repo
Expand Down
4 changes: 4 additions & 0 deletions Example/Stagehand.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
3DEA0C9624837CAB00F1ECE7 /* TransformPerformanceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DEA0C9524837CAB00F1ECE7 /* TransformPerformanceTests.swift */; };
3DEE440A231331DD0057D796 /* AnimationGroupViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DEE4409231331DD0057D796 /* AnimationGroupViewController.swift */; };
3DFE460424F4F0D000A460AC /* QuadrantView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DFE460324F4F0D000A460AC /* QuadrantView.swift */; };
5033628B2C49DE3500AEF9F7 /* NestedAnimationGroupViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5033628A2C49DE3500AEF9F7 /* NestedAnimationGroupViewController.swift */; };
607FACD61AFB9204008FA782 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACD51AFB9204008FA782 /* AppDelegate.swift */; };
607FACD81AFB9204008FA782 /* RootViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACD71AFB9204008FA782 /* RootViewController.swift */; };
607FACDD1AFB9204008FA782 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 607FACDC1AFB9204008FA782 /* Images.xcassets */; };
Expand Down Expand Up @@ -120,6 +121,7 @@
3EEB62F0CC3F471BC6454D87 /* Pods-Stagehand_Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Stagehand_Example.debug.xcconfig"; path = "Target Support Files/Pods-Stagehand_Example/Pods-Stagehand_Example.debug.xcconfig"; sourceTree = "<group>"; };
472E62FC731AD89405017C59 /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = "<group>"; };
4B84A3658CDA0BD762380354 /* Pods-Stagehand_Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Stagehand_Tests.debug.xcconfig"; path = "Target Support Files/Pods-Stagehand_Tests/Pods-Stagehand_Tests.debug.xcconfig"; sourceTree = "<group>"; };
5033628A2C49DE3500AEF9F7 /* NestedAnimationGroupViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NestedAnimationGroupViewController.swift; sourceTree = "<group>"; };
607FACD01AFB9204008FA782 /* Stagehand_Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Stagehand_Example.app; sourceTree = BUILT_PRODUCTS_DIR; };
607FACD41AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
607FACD51AFB9204008FA782 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -231,6 +233,7 @@
3D8E3D8322F2BC9300D70FCB /* RepeatingAnimationsViewController.swift */,
3DA3997B230FCFAC00DE41A0 /* ExecutionBlockViewController.swift */,
3DEE4409231331DD0057D796 /* AnimationGroupViewController.swift */,
5033628A2C49DE3500AEF9F7 /* NestedAnimationGroupViewController.swift */,
3D2712ED236A17C5001D3B4B /* AnimationQueueViewController.swift */,
);
name = "Feature Screens";
Expand Down Expand Up @@ -630,6 +633,7 @@
3D89F1E222FDEAAC008AC33E /* PropertyAssignmentViewController.swift in Sources */,
607FACD81AFB9204008FA782 /* RootViewController.swift in Sources */,
B3C4CA092459F75800893CB5 /* ShapeLayerUtils.swift in Sources */,
5033628B2C49DE3500AEF9F7 /* NestedAnimationGroupViewController.swift in Sources */,
3DBFEE422400B2460086D61C /* ChildAnimationProgressViewController.swift in Sources */,
3DA3997C230FCFAC00DE41A0 /* ExecutionBlockViewController.swift in Sources */,
3D2712EE236A17C5001D3B4B /* AnimationQueueViewController.swift in Sources */,
Expand Down
144 changes: 144 additions & 0 deletions Example/Stagehand/NestedAnimationGroupViewController.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
//
// Copyright 2019 Square Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

import Stagehand
import UIKit

final class NestedAnimationGroupViewController: DemoViewController {

// MARK: - Life Cycle

override init() {
super.init()

contentView = mainView

animationRows = [
("Sequential Nested Groups", { [unowned self] in
self.performSequentialNestedGroupAnimations()
})
]
}

// MARK: - Private Properties

private let mainView: View = .init()

// MARK: - Private Methods

private func performSequentialNestedGroupAnimations() {
var parentGroup = AnimationGroup()

let leftGroup = makeLeftAnimationGroup()
let rightGroup = makeRightAnimationGroup()

parentGroup.addAnimationGroup(leftGroup, startingAt: 0, relativeDuration: 1)
parentGroup.addAnimationGroup(rightGroup, startingAt: 0, relativeDuration: 1)

parentGroup.perform(duration: 2)
}

private func makeLeftAnimationGroup() -> AnimationGroup {
var leftGroup = AnimationGroup()
let leftAnimation1 = self.makeAnimation(translationX: 100)
let leftAnimation2 = self.makeAnimation(translationX: 75)
leftGroup.addAnimation(leftAnimation1, for: self.mainView.leftView, startingAt: 0, relativeDuration: 0.5)
leftGroup.addAnimation(leftAnimation2, for: self.mainView.leftNestView, startingAt: 0.5, relativeDuration: 0.5)
return leftGroup
}

private func makeRightAnimationGroup() -> AnimationGroup {
var rightGroup = AnimationGroup()
let rightAnimation1 = self.makeAnimation(translationX: -100)
let rightAnimation2 = self.makeAnimation(translationX: -75)
rightGroup.addAnimation(rightAnimation1, for: self.mainView.rightView, startingAt: 0, relativeDuration: 0.5)
rightGroup.addAnimation(rightAnimation2, for: self.mainView.rightNestView, startingAt: 0.5, relativeDuration: 0.5)
return rightGroup
}

private func makeAnimation(translationX: CGFloat) -> Animation<UIView> {
var animation = Animation<UIView>()
animation.addKeyframe(for: \.transform, at: 0, value: .identity)
animation.addKeyframe(for: \.transform, at: 1, value: .init(translationX: translationX, y: 0))
return animation
}
}

// MARK: -

extension NestedAnimationGroupViewController {

final class View: UIView {

// MARK: - Life Cycle

override init(frame: CGRect) {
super.init(frame: frame)

leftView.backgroundColor = .red
addSubview(leftView)

leftNestView.backgroundColor = .red
addSubview(leftNestView)

rightView.backgroundColor = .blue
addSubview(rightView)

rightNestView.backgroundColor = .blue
addSubview(rightNestView)
}

@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

// MARK: - Public Properties

let leftView: UIView = .init()
let leftNestView: UIView = .init()
let rightView: UIView = .init()
let rightNestView: UIView = .init()

// MARK: - UIView

override func layoutSubviews() {
leftView.bounds.size = .init(width: 50, height: 50)
leftView.center = .init(
x: bounds.width / 3,
y: bounds.height / 2
)

leftNestView.bounds.size = .init(width: 25, height: 25)
leftNestView.center = .init(
x: bounds.width / 3,
y: bounds.height / 2
)

rightView.bounds.size = .init(width: 50, height: 50)
rightView.center = .init(
x: bounds.width * 2 / 3,
y: bounds.height / 2
)

rightNestView.bounds.size = .init(width: 25, height: 25)
rightNestView.center = .init(
x: bounds.width * 2 / 3,
y: bounds.height / 2
)
}
}
}
1 change: 1 addition & 0 deletions Example/Stagehand/RootViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ final class RootViewController: UITableViewController {
("Repeating Animations", { RepeatingAnimationsViewController() }),
("Execution Blocks", { ExecutionBlockViewController() }),
("Animation Groups", { AnimationGroupViewController() }),
("Nested Animation Groups", { NestedAnimationGroupViewController() }),
("Animation Queues", { AnimationQueueViewController() }),
]

Expand Down
65 changes: 64 additions & 1 deletion Example/Unit Tests/AnimationGroupTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,70 @@ final class AnimationGroupTests: XCTestCase {

_ = animationInstance
}


// MARK: - Tests - Add Animation Group

func testAddAnimationGroup() {
var parentGroup = AnimationGroup()

var leftGroup = AnimationGroup()
var rightGroup = AnimationGroup()

var leftAnimation1 = Animation<ElementA>()
leftAnimation1.addKeyframe(for: \.propertyOne, at: 0, value: 0)
leftAnimation1.addKeyframe(for: \.propertyOne, at: 1, value: 1)

var leftAnimation2 = Animation<ElementA>()
leftAnimation2.addKeyframe(for: \.propertyTwo, at: 0, value: 0)
leftAnimation2.addKeyframe(for: \.propertyTwo, at: 1, value: 1)

var rightAnimation1 = Animation<ElementB>()
rightAnimation1.addKeyframe(for: \.property, at: 0, value: 1)
rightAnimation1.addKeyframe(for: \.property, at: 1, value: 0)

var rightAnimation2 = Animation<ElementB>()
rightAnimation2.addKeyframe(for: \.property, at: 0, value: 0.5)
rightAnimation2.addKeyframe(for: \.property, at: 1, value: 1)

let leftElement1 = ElementA()
let leftElement2 = ElementA()
let rightElement1 = ElementB()
let rightElement2 = ElementB()

leftGroup.addAnimation(leftAnimation1, for: leftElement1, startingAt: 0, relativeDuration: 0.5)
leftGroup.addAnimation(leftAnimation2, for: leftElement2, startingAt: 0.5, relativeDuration: 0.5)

rightGroup.addAnimation(rightAnimation1, for: rightElement1, startingAt: 0, relativeDuration: 0.5)
rightGroup.addAnimation(rightAnimation2, for: rightElement2, startingAt: 0.5, relativeDuration: 0.5)

parentGroup.addAnimationGroup(leftGroup, startingAt: 0, relativeDuration: 1)
parentGroup.addAnimationGroup(rightGroup, startingAt: 0, relativeDuration: 1)

let driver = TestDriver()

let animationInstance = AnimationInstance(
animation: parentGroup.animation,
element: parentGroup.elementContainer,
driver: driver
)

driver.runForward(to: 0.25)
XCTAssertEqual(leftElement1.propertyOne, 0.5)
XCTAssertEqual(rightElement1.property, 0.5)

driver.runForward(to: 0.75)
XCTAssertEqual(leftElement2.propertyTwo, 0.5)
XCTAssertEqual(rightElement2.property, 0.75)

driver.runForward(to: 1.0)
XCTAssertEqual(leftElement1.propertyOne, 1)
XCTAssertEqual(leftElement2.propertyTwo, 1)
XCTAssertEqual(rightElement1.property, 0)
XCTAssertEqual(rightElement2.property, 1)

_ = animationInstance
}

// MARK: - Tests - Completion Handler

func testCompletionCalledOnComplete() {
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added ...nSnapshotTests/[email protected]
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added ...nSnapshotTests/[email protected]
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added ...onSnapshotTests/[email protected]
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added ...onSnapshotTests/[email protected]
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added ...AnimationSnapshotTests/[email protected]
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added ...imationSnapshotTests/[email protected]
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added ...ationSnapshotTests/[email protected]
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added ...ests.AnimationSnapshotTests/[email protected]
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added ...ests.AnimationSnapshotTests/[email protected]
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added ...s.AnimationSnapshotTests/[email protected]
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added ...ts.AnimationSnapshotTests/[email protected]
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added ...erpolationSnapshotTests/[email protected]
Binary file added ...nterpolationSnapshotTests/[email protected]
4 changes: 4 additions & 0 deletions Example/Unit Tests/SnapshotTestCase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ class SnapshotTestCase: FBSnapshotTestCase {
// MARK: - Private Static Properties

private static let testedDevices = [
// iPhone 13 Pro (iOS 15.2)
TestDeviceConfig(systemVersion: "15.2", screenSize: CGSize(width: 390, height: 844), screenScale: 3),

// iPhone 11 Pro (iOS 13.7)
TestDeviceConfig(systemVersion: "13.7", screenSize: CGSize(width: 375, height: 812), screenScale: 3),
]

Expand Down
26 changes: 26 additions & 0 deletions Example/Unit Tests/SnapshotTestingAPNGImageTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,32 @@ final class SnapshotTestingAPNGImageTests: SnapshotTestCase {
// is restored to its original state after snapshotting.
assertSnapshot(matching: view, as: .image, named: nameForDevice(baseName: "start"))
}

func testNestedAnimationGroupSnapshot() {
let view = AnimatableContainerView(frame: .init(x: 0, y: 0, width: 200, height: 100))

var animation1 = Animation<AnimatableContainerView>()
animation1.addKeyframe(for: \.animatableView.transform, at: 0, value: .identity)
animation1.addKeyframe(for: \.animatableView.transform, at: 1, value: .init(translationX: 50, y: 0))

var animation2 = Animation<AnimatableContainerView>()
animation2.addKeyframe(for: \.animatableView.transform, at: 0, value: .init(translationX: 50, y: 0))
animation2.addKeyframe(for: \.animatableView.transform, at: 1, value: .init(translationX: 100, y: 0))

var nestedGroup = AnimationGroup()
nestedGroup.addAnimation(animation1, for: view, startingAt: 0, relativeDuration: 0.5)
nestedGroup.addAnimation(animation2, for: view, startingAt: 0.5, relativeDuration: 0.5)

var parentGroup = AnimationGroup()
parentGroup.addAnimationGroup(nestedGroup, startingAt: 0, relativeDuration: 1)

assertSnapshot(matching: view, as: .image, named: nameForDevice(baseName: "start"))
assertSnapshot(matching: parentGroup, as: .animatedImage(using: view), named: nameForDevice())

// This intentionally uses the same identifier as the snapshot from before the animation to ensure that the view
// is restored to its original state after snapshotting.
assertSnapshot(matching: view, as: .image, named: nameForDevice(baseName: "start"))
}

// MARK: - Private Methods

Expand Down
41 changes: 41 additions & 0 deletions Example/Unit Tests/SnapshotTestingFrameImageTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,47 @@ final class SnapshotTestingFrameImageTests: SnapshotTestCase {
// original state after snapshotting.
assertSnapshot(matching: view, as: .image, named: nameForDevice(baseName: "start"))
}

func testNestedAnimationGroupSnapshot() {
let view = AnimatableContainerView(frame: .init(x: 0, y: 0, width: 200, height: 100))

var animation1 = Animation<AnimatableContainerView>()
animation1.addKeyframe(for: \.animatableView.transform, at: 0, value: .identity)
animation1.addKeyframe(for: \.animatableView.transform, at: 1, value: .init(translationX: 50, y: 0))

var animation2 = Animation<AnimatableContainerView>()
animation2.addKeyframe(for: \.animatableView.transform, at: 0, value: .init(translationX: 50, y: 0))
animation2.addKeyframe(for: \.animatableView.transform, at: 1, value: .init(translationX: 100, y: 0))

var nestedGroup = AnimationGroup()
nestedGroup.addAnimation(animation1, for: view, startingAt: 0, relativeDuration: 0.5)
nestedGroup.addAnimation(animation2, for: view, startingAt: 0.5, relativeDuration: 0.5)

var parentGroup = AnimationGroup()
parentGroup.addAnimationGroup(nestedGroup, startingAt: 0, relativeDuration: 1)

assertSnapshot(
matching: parentGroup,
as: .frameImage(using: view, at: 0.0),
named: nameForDevice(baseName: "start")
)
assertSnapshot(
matching: parentGroup,
as: .frameImage(using: view, at: 0.25),
named: nameForDevice(baseName: "middle")
)
assertSnapshot(
matching: parentGroup,
as: .frameImage(using: view, at: 0.75),
named: nameForDevice(baseName: "three-quarters")
)
assertSnapshot(
matching: parentGroup,
as: .frameImage(using: view, at: 1.0),
named: nameForDevice(baseName: "end")
)
assertSnapshot(matching: view, as: .image, named: nameForDevice(baseName: "restored"))
}

// MARK: - Private Methods

Expand Down
3 changes: 3 additions & 0 deletions Scripts/build.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,13 @@ enum TaskError: Error {
}

enum Platform: String, CustomStringConvertible {
case iOS_15
case iOS_13

var destination: String {
switch self {
case .iOS_15:
return "platform=iOS Simulator,OS=15.2,name=iPhone 13 Pro"
case .iOS_13:
return "platform=iOS Simulator,OS=13.7,name=iPhone 11 Pro"
}
Expand Down
Loading