Skip to content

Commit 2b0f317

Browse files
author
Mark Pospesel
authored
[Issue-26] Customize animations (#28)
* [Issue-26] Customize animations * Remove source code from README
1 parent 7c61299 commit 2b0f317

12 files changed

+143
-81
lines changed

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ let package = Package(
2121
),
2222
.package(
2323
url: "https://github.com/yml-org/YMatterType.git",
24-
from: "1.6.0"
24+
from: "1.7.0"
2525
)
2626
],
2727
targets: [

README.md

Lines changed: 13 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,6 @@ Usage
2121
### Initializers
2222
The Bottom sheet controller can be initialized with either a title and a view or else with a view controller.
2323

24-
```swift
25-
init(
26-
title: String,
27-
childView: UIView,
28-
appearance: BottomSheetController.Appearance = .default
29-
)
30-
31-
init(
32-
childController: UIViewController,
33-
appearance: BottomSheetController.Appearance = .default
34-
)
35-
```
36-
3724
When initializing with a view controller, the title is drawn from `UIViewController.title`. When the view controller is a `UINavigationController`, the header appearance options are ignored and the navigation controller's navigation bar is displayed as the sheet's header. In this situation, if you wish to have a close button, then that should be set using the view controller's `navigationItem.rightBarButtonItem` or `.leftBarButtonItem`.
3825

3926
Both initializers include an appearance parameter that allows you to fully customize the sheet's appearance. You can also update the sheet's appearance at any time.
@@ -84,33 +71,15 @@ final class ViewController: UIViewController {
8471
### Customization
8572
`BottomSheetController` has an `appearance` property of type `Appearance`.
8673

87-
`Appearance` lets you customize the bottom sheet appearance. We can customize the appearance of the indicator view, the header view, dimmer color, animation etc.
74+
`Appearance` lets you customize how the bottom sheet both appears and behaves. You can customize:
8875

89-
```swift
90-
/// Determines the appearance of the bottom sheet.
91-
public struct Appearance {
92-
/// Appearance of the drag indicator.
93-
public var indicatorAppearance: DragIndicatorView.Appearance?
94-
/// Appearance of the sheet header view.
95-
public var headerAppearance: SheetHeaderView.Appearance?
96-
/// Bottom sheet layout properties such as corner radius. Default is `.default`.
97-
public let layout: Layout
98-
/// Bottom sheet's shadow. Default is `nil` (no shadow).
99-
public let elevation: Elevation?
100-
/// Dimmer view color. Default is 'UIColor.black.withAlphaComponent(0.5)'.
101-
public let dimmerColor: UIColor?
102-
/// Animation duration on bottom sheet. Default is `0.3`.
103-
public let animationDuration: TimeInterval
104-
/// Animation type during presenting. Default is `curveEaseIn`.
105-
public let presentAnimationCurve: UIView.AnimationOptions
106-
/// Animation type during dismissing. Default is `curveEaseOut`.
107-
public let dismissAnimationCurve: UIView.AnimationOptions
108-
/// (Optional) Minimum content view height. Default is `nil`.
109-
///
110-
/// Only applicable for resizable sheets. `nil` means to use the content view's intrinsic height as the minimum.
111-
public var minimumContentHeight: CGFloat?
112-
}
113-
```
76+
* drag indicator (whether you have one at all or what its size and color are)
77+
* header (whether you have one at all or what its text color, typography, and optional close button image are)
78+
* layout (corner radius and minimum, maximum, and ideal sizes for the sheet's contents)
79+
* drop shadow (if any)
80+
* dimmer color
81+
* present animation
82+
* dismiss animation
11483

11584
**Update or customize appearance**
11685

@@ -133,8 +102,12 @@ sheet.appearance.elevation = Elevation(
133102
color: .black,
134103
opacity: 0.4
135104
)
105+
sheet.appearance.presentAnimation = Animation(
106+
duration: 0.4,
107+
curve: .spring(damping: 0.6, velocity: 0.4)
108+
)
136109

137-
// Present the sheet.
110+
// Present the sheet with a spring animation.
138111
present(sheet, animated: true)
139112
```
140113

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
//
2+
// Animation+BottomSheet.swift
3+
// YBottomSheet
4+
//
5+
// Created by Mark Pospesel on 5/4/23.
6+
// Copyright © 2023 Y Media Labs. All rights reserved.
7+
//
8+
9+
import YCoreUI
10+
11+
/// Default animations for bottom sheets
12+
public extension Animation {
13+
/// Default animation for presenting a bottom sheet
14+
static let defaultPresent = Animation(curve: .regular(options: .curveEaseIn))
15+
16+
/// Default animation for dismissing a bottom sheet
17+
static let defaultDismiss = Animation(curve: .regular(options: .curveEaseOut))
18+
}

Sources/YBottomSheet/Animation/BottomSheetAnimator.swift

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,14 @@ class BottomSheetAnimator: NSObject {
1313
/// Bottom sheet controller.
1414
let sheetViewController: BottomSheetController
1515

16+
enum Direction {
17+
case present
18+
case dismiss
19+
}
20+
21+
/// Animation direction (present or dismiss)
22+
let direction: Direction
23+
1624
/// Override for isReduceMotionEnabled. Default is `nil`.
1725
///
1826
/// For unit testing. When non-`nil` it will be returned instead of
@@ -25,16 +33,24 @@ class BottomSheetAnimator: NSObject {
2533
}
2634

2735
/// Initializes a bottom sheet animator.
28-
/// - Parameter sheetViewController: the sheet being animated.
29-
init(sheetViewController: BottomSheetController) {
36+
/// - Parameters:
37+
/// - sheetViewController: the sheet being animated.
38+
/// - direction: animation direction
39+
init(sheetViewController: BottomSheetController, direction: Direction) {
3040
self.sheetViewController = sheetViewController
41+
self.direction = direction
3142
super.init()
3243
}
3344
}
3445

3546
extension BottomSheetAnimator: UIViewControllerAnimatedTransitioning {
3647
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
37-
sheetViewController.appearance.animationDuration
48+
switch direction {
49+
case .present:
50+
return sheetViewController.appearance.presentAnimation.duration
51+
case .dismiss:
52+
return sheetViewController.appearance.dismissAnimation.duration
53+
}
3854
}
3955

4056
// Override this method and perform the animations

Sources/YBottomSheet/Animation/BottomSheetDismissAnimator.swift

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@ import UIKit
1010

1111
/// Performs the sheet dismiss animation.
1212
class BottomSheetDismissAnimator: BottomSheetAnimator {
13+
/// Initializes a bottom sheet animator.
14+
/// - Parameter sheetViewController: the sheet being animated.
15+
required init(sheetViewController: BottomSheetController) {
16+
super.init(sheetViewController: sheetViewController, direction: .dismiss)
17+
}
18+
1319
override func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
1420
guard let fromViewController = transitionContext.viewController(forKey: .from),
1521
let toViewController = transitionContext.viewController(forKey: .to) else {
@@ -31,12 +37,8 @@ class BottomSheetDismissAnimator: BottomSheetAnimator {
3137
) {
3238
sheet.dimmerView.alpha = 0
3339
}
34-
35-
UIView.animate(
36-
withDuration: duration,
37-
delay: .zero,
38-
options: [.beginFromCurrentState, sheet.appearance.dismissAnimationCurve]
39-
) {
40+
41+
UIView.animate(with: sheet.appearance.dismissAnimation) {
4042
if self.isReduceMotionEnabled {
4143
sheet.sheetView.alpha = 0
4244
} else {

Sources/YBottomSheet/Animation/BottomSheetPresentAnimator.swift

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@ import UIKit
1010

1111
/// Performs the sheet present animation.
1212
class BottomSheetPresentAnimator: BottomSheetAnimator {
13+
/// Initializes a bottom sheet animator.
14+
/// - Parameter sheetViewController: the sheet being animated.
15+
required init(sheetViewController: BottomSheetController) {
16+
super.init(sheetViewController: sheetViewController, direction: .present)
17+
}
18+
1319
override func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
1420
guard let toViewController = transitionContext.viewController(forKey: .to) else {
1521
transitionContext.completeTransition(false)
@@ -44,11 +50,7 @@ class BottomSheetPresentAnimator: BottomSheetAnimator {
4450
sheet.dimmerView.alpha = 1
4551
}
4652

47-
UIView.animate(
48-
withDuration: duration,
49-
delay: .zero,
50-
options: [.beginFromCurrentState, sheet.appearance.presentAnimationCurve]
51-
) {
53+
UIView.animate(with: sheet.appearance.presentAnimation) {
5254
if self.isReduceMotionEnabled {
5355
sheet.sheetView.alpha = 1
5456
} else {

Sources/YBottomSheet/BottomSheetController+Appearance.swift

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,10 @@ extension BottomSheetController {
2222
public var elevation: Elevation?
2323
/// Dimmer view color. Default is 'UIColor.black.withAlphaComponent(0.5)'.
2424
public var dimmerColor: UIColor?
25-
/// Animation duration on bottom sheet. Default is `0.3`.
26-
public var animationDuration: TimeInterval
27-
/// Animation type during presenting. Default is `curveEaseIn`.
28-
public var presentAnimationCurve: UIView.AnimationOptions
29-
/// Animation type during dismissing. Default is `curveEaseOut`.
30-
public var dismissAnimationCurve: UIView.AnimationOptions
31-
/// Whether the sheet can be dismissed by swiping down or tapping on the dimmer. Default is `true`.
25+
/// Animation for presenting the bottom sheet. Default = `.defaultPresent`.
26+
public var presentAnimation: Animation
27+
/// Animation for dismissing the bottom sheet. Default = `.defaultDismiss`.
28+
public var dismissAnimation: Animation
3229
///
3330
/// The user can always dismiss the sheet from the close button if it is visible.
3431
public var isDismissAllowed: Bool
@@ -43,31 +40,28 @@ extension BottomSheetController {
4340
/// - indicatorAppearance: appearance of the drag indicator or pass `nil` to hide.
4441
/// - headerAppearance: appearance of the sheet header view or pass `nil` to hide.
4542
/// - layout: bottom sheet layout properties such as corner radius.
46-
/// - elevation: bottom sheet's shadow or pass `nil` to hide
43+
/// - elevation: bottom sheet's shadow or pass `nil` to hide.
4744
/// - dimmerColor: dimmer view color or pass `nil` to hide.
48-
/// - animationDuration: animation duration for bottom sheet. Default is `0.3`.
49-
/// - presentAnimationCurve: animation type during presenting.
50-
/// - dismissAnimationCurve: animation type during dismiss.
45+
/// - presentAnimation: animation for presenting the bottom sheet.
46+
/// - dismissAnimation: animation for dismissing the bottom sheet.
5147
/// - isDismissAllowed: whether the sheet can be dismissed by swiping down or tapping on the dimmer.
5248
public init(
5349
indicatorAppearance: DragIndicatorView.Appearance? = nil,
5450
headerAppearance: SheetHeaderView.Appearance? = .default,
5551
layout: Layout = .default,
5652
elevation: Elevation? = nil,
5753
dimmerColor: UIColor? = .black.withAlphaComponent(0.5),
58-
animationDuration: TimeInterval = 0.3,
59-
presentAnimationCurve: UIView.AnimationOptions = .curveEaseIn,
60-
dismissAnimationCurve: UIView.AnimationOptions = .curveEaseOut,
54+
presentAnimation: Animation = .defaultPresent,
55+
dismissAnimation: Animation = .defaultDismiss,
6156
isDismissAllowed: Bool = true
6257
) {
6358
self.indicatorAppearance = indicatorAppearance
6459
self.headerAppearance = headerAppearance
6560
self.layout = layout
6661
self.elevation = elevation
6762
self.dimmerColor = dimmerColor
68-
self.animationDuration = animationDuration
69-
self.presentAnimationCurve = presentAnimationCurve
70-
self.dismissAnimationCurve = dismissAnimationCurve
63+
self.presentAnimation = presentAnimation
64+
self.dismissAnimation = dismissAnimation
7165
self.isDismissAllowed = isDismissAllowed
7266
}
7367
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
//
2+
// Animation+BottomSheetTests.swift
3+
// YBottomSheet
4+
//
5+
// Created by Mark Pospesel on 5/4/23.
6+
// Copyright © 2023 Y Media Labs. All rights reserved.
7+
//
8+
9+
import XCTest
10+
import YCoreUI
11+
@testable import YBottomSheet
12+
13+
final class AnimationBottomSheetTests: XCTestCase {
14+
func test_defaultPresent() {
15+
// Given
16+
let sut = Animation.defaultPresent
17+
18+
// Then
19+
XCTAssertEqual(sut.duration, 0.3)
20+
XCTAssertEqual(sut.delay, 0.0)
21+
XCTAssertEqual(sut.curve, .regular(options: .curveEaseIn))
22+
}
23+
24+
func test_defaultDismiss() {
25+
// Given
26+
let sut = Animation.defaultDismiss
27+
28+
// Then
29+
XCTAssertEqual(sut.duration, 0.3)
30+
XCTAssertEqual(sut.delay, 0.0)
31+
XCTAssertEqual(sut.curve, .regular(options: .curveEaseOut))
32+
}
33+
}

Tests/YBottomSheetTests/Animation/BottomSheetAnimatorTests.swift

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,22 @@ final class BottomSheetAnimatorTests: XCTestCase {
1717
XCTAssertEqual(sut.sheetViewController, sheetController)
1818
}
1919

20-
func test_duration() {
20+
func test_presentDuration() {
2121
let main = UIViewController()
2222
let sheetController = BottomSheetController(title: "Bottom Sheet", childView: UIView())
23-
let sut = makeSUT(sheetViewController: sheetController)
23+
let sut = makeSUT(sheetViewController: sheetController, direction: .present)
24+
let context = MockAnimationContext(from: main, to: sheetController)
25+
26+
XCTAssertEqual(sut.transitionDuration(using: context), sheetController.appearance.presentAnimation.duration)
27+
}
28+
29+
func test_dismissDuration() {
30+
let main = UIViewController()
31+
let sheetController = BottomSheetController(title: "Bottom Sheet", childView: UIView())
32+
let sut = makeSUT(sheetViewController: sheetController, direction: .dismiss)
2433
let context = MockAnimationContext(from: main, to: sheetController)
2534

26-
XCTAssertEqual(sut.transitionDuration(using: context), sheetController.appearance.animationDuration)
35+
XCTAssertEqual(sut.transitionDuration(using: context), sheetController.appearance.dismissAnimation.duration)
2736
}
2837

2938
func test_animate() {
@@ -42,10 +51,11 @@ final class BottomSheetAnimatorTests: XCTestCase {
4251
private extension BottomSheetAnimatorTests {
4352
func makeSUT(
4453
sheetViewController: BottomSheetController,
54+
direction: BottomSheetAnimator.Direction = .present,
4555
file: StaticString = #filePath,
4656
line: UInt = #line
4757
) -> BottomSheetAnimator {
48-
let sut = BottomSheetAnimator(sheetViewController: sheetViewController)
58+
let sut = BottomSheetAnimator(sheetViewController: sheetViewController, direction: direction)
4959
trackForMemoryLeak(sut, file: file, line: line)
5060
return sut
5161
}

Tests/YBottomSheetTests/Animation/BottomSheetDismissAnimatorTests.swift

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,15 @@
77
//
88

99
import XCTest
10+
import YCoreUI
1011
@testable import YBottomSheet
1112

1213
final class BottomSheetDismissAnimatorTests: XCTestCase {
1314
func test_animate() throws {
1415
let sheetController = makeSheet()
1516
let (sut, context) = try makeSUT(sheetViewController: sheetController, to: sheetController)
1617

18+
XCTAssertEqual(sut.transitionDuration(using: context), 0.0)
1719
XCTAssertTrue(sut is BottomSheetDismissAnimator)
1820
XCTAssertFalse(context.wasCompleteCalled)
1921
sut.animateTransition(using: context)
@@ -32,6 +34,7 @@ final class BottomSheetDismissAnimatorTests: XCTestCase {
3234
isReduceMotionEnabled: false
3335
)
3436

37+
XCTAssertEqual(sut.transitionDuration(using: context), 0.0)
3538
sut.animateTransition(using: context)
3639

3740
// Wait for the run loop to tick (animate keyboard)
@@ -49,6 +52,7 @@ final class BottomSheetDismissAnimatorTests: XCTestCase {
4952
isReduceMotionEnabled: true
5053
)
5154

55+
XCTAssertEqual(sut.transitionDuration(using: context), 0.0)
5256
sut.animateTransition(using: context)
5357

5458
// Wait for the run loop to tick (animate keyboard)
@@ -62,6 +66,7 @@ final class BottomSheetDismissAnimatorTests: XCTestCase {
6266
let sheetController = makeSheet()
6367
let (sut, context) = try makeSUT(sheetViewController: sheetController, to: nil)
6468

69+
XCTAssertEqual(sut.transitionDuration(using: context), 0.0)
6570
XCTAssertFalse(context.wasCompleteCalled)
6671
sut.animateTransition(using: context)
6772

@@ -96,7 +101,9 @@ private extension BottomSheetDismissAnimatorTests {
96101
let sheet = BottomSheetController(
97102
title: "Bottom Sheet",
98103
childView: UIView(),
99-
appearance: BottomSheetController.Appearance(animationDuration: 0.0)
104+
appearance: BottomSheetController.Appearance(
105+
dismissAnimation: Animation(duration: 0.0, curve: .regular(options: .curveEaseOut))
106+
)
100107
)
101108
trackForMemoryLeak(sheet)
102109
return sheet

0 commit comments

Comments
 (0)