Skip to content

Commit 46b4d4c

Browse files
authored
Merge pull request #7 from joomcode/feature/add-extension-for-bottom-sheet-presentation
Add convenience methods to present BottomSheet. Take out appearance parameters to configuration
2 parents 9e58dcd + 1c02adf commit 46b4d4c

9 files changed

Lines changed: 276 additions & 62 deletions

File tree

BottomSheetDemo.xcodeproj/project.pbxproj

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@
3737
7DB24C8A27BD1813001030C7 /* BottomSheetUtils.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 7DB24C8327BD1813001030C7 /* BottomSheetUtils.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
3838
7DB24C9327BD18F1001030C7 /* BottomSheetUtils.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7DB24C8327BD1813001030C7 /* BottomSheetUtils.framework */; };
3939
7DB24CEC27BD1FAD001030C7 /* JMMulticastDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7DB24CE927BD1FAD001030C7 /* JMMulticastDelegate.m */; };
40+
7DD5185028AA2FBA003F3D2A /* UIViewController+Convenience.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DD5184E28AA2F46003F3D2A /* UIViewController+Convenience.swift */; };
41+
7DD5185328AA369E003F3D2A /* BottomSheetConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DD5185128AA34D9003F3D2A /* BottomSheetConfiguration.swift */; };
4042
/* End PBXBuildFile section */
4143

4244
/* Begin PBXContainerItemProxy section */
@@ -109,6 +111,8 @@
109111
7DA9B01927439FA100284B0F /* UIControl+EventHandling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIControl+EventHandling.swift"; sourceTree = "<group>"; };
110112
7DB24C8327BD1813001030C7 /* BottomSheetUtils.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = BottomSheetUtils.framework; sourceTree = BUILT_PRODUCTS_DIR; };
111113
7DB24CE927BD1FAD001030C7 /* JMMulticastDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JMMulticastDelegate.m; sourceTree = "<group>"; };
114+
7DD5184E28AA2F46003F3D2A /* UIViewController+Convenience.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+Convenience.swift"; sourceTree = "<group>"; };
115+
7DD5185128AA34D9003F3D2A /* BottomSheetConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BottomSheetConfiguration.swift; sourceTree = "<group>"; };
112116
/* End PBXFileReference section */
113117

114118
/* Begin PBXFrameworksBuildPhase section */
@@ -243,8 +247,10 @@
243247
isa = PBXGroup;
244248
children = (
245249
7DA6E0B4274F915A009F5C37 /* BottomSheetTransitioningDelegate.swift */,
250+
7DD5184D28AA2F3B003F3D2A /* Extensions */,
246251
7DA6E0B5274F915A009F5C37 /* NavigationController */,
247252
7DA6E0B9274F915A009F5C37 /* Presentation */,
253+
7DD5185128AA34D9003F3D2A /* BottomSheetConfiguration.swift */,
248254
);
249255
path = Core;
250256
sourceTree = "<group>";
@@ -329,6 +335,14 @@
329335
path = Sources/BottomSheetUtils;
330336
sourceTree = "<group>";
331337
};
338+
7DD5184D28AA2F3B003F3D2A /* Extensions */ = {
339+
isa = PBXGroup;
340+
children = (
341+
7DD5184E28AA2F46003F3D2A /* UIViewController+Convenience.swift */,
342+
);
343+
path = Extensions;
344+
sourceTree = "<group>";
345+
};
332346
7DDA237727BD25C800D006FE /* include */ = {
333347
isa = PBXGroup;
334348
children = (
@@ -497,8 +511,10 @@
497511
7DA6E0DE274F918E009F5C37 /* BottomSheetModalDismissalHandler.swift in Sources */,
498512
7DA6E0DF274F918E009F5C37 /* BottomSheetPresentationController.swift in Sources */,
499513
7DA6E0E7274F919A009F5C37 /* UIScrollView+MulticastDelegate.swift in Sources */,
514+
7DD5185328AA369E003F3D2A /* BottomSheetConfiguration.swift in Sources */,
500515
7DA6E0E8274F919A009F5C37 /* UINavigationController+MulticastDelegate.swift in Sources */,
501516
7DA6E0E6274F9196009F5C37 /* CGSize+Helpers.swift in Sources */,
517+
7DD5185028AA2FBA003F3D2A /* UIViewController+Convenience.swift in Sources */,
502518
7DA6E0E2274F9196009F5C37 /* UIViewController+Lifecycle.swift in Sources */,
503519
7DA6E0DC274F918A009F5C37 /* BottomSheetNavigationStyle.swift in Sources */,
504520
7DA6E0DA274F918A009F5C37 /* BottomSheetNavigationController.swift in Sources */,

BottomSheetDemo/Sources/User Interface/Screens/Resize/ResizeViewController.swift

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -153,9 +153,17 @@ final class ResizeViewController: UIViewController {
153153

154154
private func updateContentHeight(newValue: CGFloat) {
155155
guard newValue >= 200 && newValue < 5000 else { return }
156-
157-
currentHeight = newValue
158-
updatePreferredContentSize()
156+
157+
let updates = { [self] in
158+
currentHeight = newValue
159+
updatePreferredContentSize()
160+
}
161+
162+
if navigationController == nil {
163+
UIView.animate(withDuration: 0.25, animations: updates)
164+
} else {
165+
updates()
166+
}
159167
}
160168

161169
@objc

BottomSheetDemo/Sources/User Interface/Screens/Root/RootViewController.swift

Lines changed: 3 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -48,37 +48,12 @@ final class RootViewController: UIViewController {
4848
}
4949
}
5050

51-
private var transitionDelegate: UIViewControllerTransitioningDelegate?
52-
5351
@objc
5452
private func handleShowBottomSheet() {
5553
let viewController = ResizeViewController(initialHeight: 300)
56-
let navigationController = BottomSheetNavigationController(rootViewController: viewController)
57-
transitionDelegate = BottomSheetTransitioningDelegate(presentationControllerFactory: self)
58-
navigationController.transitioningDelegate = transitionDelegate
59-
navigationController.modalPresentationStyle = .custom
60-
present(navigationController, animated: true, completion: nil)
61-
}
62-
}
63-
64-
extension RootViewController: BottomSheetPresentationControllerFactory {
65-
func makeBottomSheetPresentationController(
66-
presentedViewController: UIViewController,
67-
presentingViewController: UIViewController?
68-
) -> BottomSheetPresentationController {
69-
.init(
70-
presentedViewController: presentedViewController,
71-
presentingViewController: presentingViewController,
72-
dismissalHandler: self
54+
presentBottomSheetInsideNavigationController(
55+
viewController: viewController,
56+
configuration: .default
7357
)
7458
}
7559
}
76-
77-
extension RootViewController: BottomSheetModalDismissalHandler {
78-
var canBeDismissed: Bool { true }
79-
80-
func performDismissal(animated: Bool) {
81-
presentedViewController?.dismiss(animated: animated, completion: nil)
82-
transitionDelegate = nil
83-
}
84-
}

README.md

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ Bottom Sheet component is designed to handle any content, including a scrolling
77
- ✅ build flows inside using `BottomSheetNavigationController`
88
- ✅ supports all system transitions: push and (interactive) pop
99
- ✅ transition animated between different content sizes
10+
- ✅ Customize appearance:
11+
- pull bar visibility
12+
- corner radius
13+
- shadow background color
1014

1115
## How it looks like
1216

@@ -28,16 +32,36 @@ To integrate Bottom Sheet into your Xcode project using Swift Package Manager, a
2832

2933
```swift
3034
dependencies: [
31-
.package(url: "https://github.com/joomcode/BottomSheet", from: "1.0.0")
35+
.package(url: "https://github.com/joomcode/BottomSheet", from: "2.0.0")
3236
]
3337
```
3438

3539
## Getting started
3640

3741
This repo contains [demo](https://github.com/joomcode/BottomSheet/tree/main/BottomSheetDemo), which can be a great start for understanding Bottom Sheet usage, but here are simple steps to follow:
38-
1. Set content's size using [preferredContentSize](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621476-preferredcontentsize)
39-
2. (optional) Conform to `ScrollableBottomSheetPresentedController` if your view controller is list-based
40-
3. Present using custom transition `BottomSheetTransitioningDelegate`
42+
1. Create `UIViewController` to present and set content's size by [preferredContentSize](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621476-preferredcontentsize) property
43+
2. (optional) Conform to [ScrollableBottomSheetPresentedController](https://github.com/joomcode/BottomSheet/blob/81b0e2a7d405311b8456649452a8c49098490033/Sources/BottomSheet/Core/Presentation/BottomSheetPresentationController.swift#L12-L14) if your view controller is list-based
44+
3. Present by using [presentBottomSheet(viewController:configuration:)](https://github.com/joomcode/BottomSheet/blob/1870921364ed2cd68d51d7e7837e16e692278ff5/Sources/BottomSheet/Core/Extensions/UIViewController%2BConvenience.swift#L79)
45+
46+
If you want to build flows, use `BottomSheetNavigationController`
47+
```Swift
48+
presentBottomSheetInsideNavigationController(
49+
viewController: viewControllerToPresent,
50+
configuration: .default
51+
)
52+
```
53+
54+
You can customize appearance passing configuration parameter
55+
```Swift
56+
presentBottomSheet(
57+
viewController: viewControllerToPresent,
58+
configuration: BottomSheetConfiguration(
59+
cornerRadius: 10,
60+
pullBarConfiguration: .visible(.init(height: 20)),
61+
shadowConfiguration: .init(backgroundColor: UIColor.black.withAlphaComponent(0.6))
62+
)
63+
)
64+
```
4165

4266
## Resources
4367

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
//
2+
// BottomSheetConfiguration.swift
3+
// BottomSheetDemo
4+
//
5+
// Created by Mikhail Maslo on 15.08.2022.
6+
// Copyright © 2022 Joom. All rights reserved.
7+
//
8+
9+
import UIKit
10+
11+
public struct BottomSheetConfiguration {
12+
public enum PullBarConfiguration {
13+
public struct PullBarAppearance {
14+
public let height: CGFloat
15+
16+
public init(height: CGFloat) {
17+
self.height = height
18+
}
19+
}
20+
21+
case hidden
22+
case visible(PullBarAppearance)
23+
24+
public static let `default`: PullBarConfiguration = .visible(PullBarAppearance(height: 20))
25+
}
26+
27+
public struct ShadowConfiguration {
28+
public let backgroundColor: UIColor
29+
30+
public init(backgroundColor: UIColor) {
31+
self.backgroundColor = backgroundColor
32+
}
33+
34+
public static let `default` = ShadowConfiguration(backgroundColor: UIColor.black.withAlphaComponent(0.6))
35+
}
36+
37+
public let cornerRadius: CGFloat
38+
public let pullBarConfiguration: PullBarConfiguration
39+
public let shadowConfiguration: ShadowConfiguration
40+
41+
public init(
42+
cornerRadius: CGFloat,
43+
pullBarConfiguration: PullBarConfiguration,
44+
shadowConfiguration: ShadowConfiguration
45+
) {
46+
self.cornerRadius = cornerRadius
47+
self.pullBarConfiguration = pullBarConfiguration
48+
self.shadowConfiguration = shadowConfiguration
49+
}
50+
51+
public static let `default` = BottomSheetConfiguration(
52+
cornerRadius: 10,
53+
pullBarConfiguration: .default,
54+
shadowConfiguration: .default
55+
)
56+
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
//
2+
// UIViewController+Convenience.swift
3+
// BottomSheetDemo
4+
//
5+
// Created by Mikhail Maslo on 15.08.2022.
6+
// Copyright © 2022 Joom. All rights reserved.
7+
//
8+
9+
import UIKit
10+
11+
public final class DefaultBottomSheetPresentationControllerFactory: BottomSheetPresentationControllerFactory {
12+
// MARK: - Nested types
13+
14+
public typealias DismissalHandlerProvider = () -> BottomSheetModalDismissalHandler
15+
16+
// MARK: - Public properties
17+
18+
private let configuration: BottomSheetConfiguration
19+
private let dismissalHandlerProvider: DismissalHandlerProvider
20+
21+
// MARK: - Init
22+
23+
public init(
24+
configuration: BottomSheetConfiguration,
25+
dismissalHandlerProvider: @escaping DismissalHandlerProvider
26+
) {
27+
self.dismissalHandlerProvider = dismissalHandlerProvider
28+
self.configuration = configuration
29+
}
30+
31+
// MARK: - BottomSheetPresentationControllerFactory
32+
33+
public func makeBottomSheetPresentationController(
34+
presentedViewController: UIViewController,
35+
presentingViewController: UIViewController?
36+
) -> BottomSheetPresentationController {
37+
BottomSheetPresentationController(
38+
presentedViewController: presentedViewController,
39+
presentingViewController: presentingViewController,
40+
dismissalHandler: dismissalHandlerProvider(),
41+
configuration: configuration
42+
)
43+
}
44+
}
45+
46+
public final class DefaultBottomSheetModalDismissalHandler: BottomSheetModalDismissalHandler {
47+
// MARK: - Private properties
48+
49+
private weak var presentingViewController: UIViewController?
50+
private let dismissCompletion: (() -> Void)?
51+
52+
// MARK: - Init
53+
54+
init(
55+
presentingViewController: UIViewController?,
56+
dismissCompletion: (() -> Void)?
57+
) {
58+
self.presentingViewController = presentingViewController
59+
self.dismissCompletion = dismissCompletion
60+
}
61+
62+
// MARK: - BottomSheetModalDismissalHandler
63+
64+
public let canBeDismissed: Bool = true
65+
66+
public func performDismissal(animated: Bool) {
67+
presentingViewController?.presentedViewController?.dismiss(animated: animated, completion: dismissCompletion)
68+
}
69+
}
70+
71+
public extension UIViewController {
72+
private(set) var bottomSheetTransitionDelegate: UIViewControllerTransitioningDelegate? {
73+
get { objc_getAssociatedObject(self, &Self.bottomSheetTransitionDelegateKey) as? UIViewControllerTransitioningDelegate }
74+
set { objc_setAssociatedObject(self, &Self.bottomSheetTransitionDelegateKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
75+
}
76+
77+
private static var bottomSheetTransitionDelegateKey: UInt8 = 0
78+
79+
func presentBottomSheet(viewController: UIViewController, configuration: BottomSheetConfiguration) {
80+
weak var presentingViewController = self
81+
weak var currentBottomSheetTransitionDelegate: UIViewControllerTransitioningDelegate?
82+
let presentationControllerFactory = DefaultBottomSheetPresentationControllerFactory(configuration: configuration) {
83+
DefaultBottomSheetModalDismissalHandler(presentingViewController: presentingViewController) {
84+
if currentBottomSheetTransitionDelegate === presentingViewController?.bottomSheetTransitionDelegate {
85+
presentingViewController?.bottomSheetTransitionDelegate = nil
86+
}
87+
}
88+
}
89+
bottomSheetTransitionDelegate = BottomSheetTransitioningDelegate(
90+
presentationControllerFactory: presentationControllerFactory
91+
)
92+
currentBottomSheetTransitionDelegate = bottomSheetTransitionDelegate
93+
viewController.transitioningDelegate = bottomSheetTransitionDelegate
94+
viewController.modalPresentationStyle = .custom
95+
present(viewController, animated: true, completion: nil)
96+
}
97+
98+
func presentBottomSheetInsideNavigationController(viewController: UIViewController, configuration: BottomSheetConfiguration) {
99+
let navigationController = BottomSheetNavigationController(rootViewController: viewController, configuration: configuration)
100+
presentBottomSheet(viewController: navigationController, configuration: configuration)
101+
}
102+
}

Sources/BottomSheet/Core/NavigationController/BottomSheetNavigationController.swift

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,20 @@ public final class BottomSheetNavigationController: UINavigationController {
1616
private var canAnimatePreferredContentSizeUpdates = false
1717

1818
private weak var lastTransitionViewController: UIViewController?
19-
19+
20+
private let configuration: BottomSheetConfiguration
21+
22+
// MARK: - Init
23+
24+
public init(rootViewController: UIViewController, configuration: BottomSheetConfiguration) {
25+
self.configuration = configuration
26+
super.init(rootViewController: rootViewController)
27+
}
28+
29+
required init?(coder aDecoder: NSCoder) {
30+
fatalError("init(coder:) has not been implemented")
31+
}
32+
2033
// MARK: - UIViewController
2134

2235
public override func viewDidLoad() {
@@ -128,7 +141,7 @@ extension BottomSheetNavigationController: UINavigationControllerDelegate {
128141
}
129142

130143
lastTransitionViewController = fromVC
131-
return BottomSheetNavigationAnimatedTransitioning(operation: operation)
144+
return BottomSheetNavigationAnimatedTransitioning(operation: operation, configuration: configuration)
132145
}
133146

134147
public func navigationController(

Sources/BottomSheet/Core/NavigationController/BottomSheetNavigationStyle.swift

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,16 @@ public final class BottomSheetNavigationAnimatedTransitioning: NSObject, UIViewC
1212
// MARK: - Private
1313

1414
private let operation: UINavigationController.Operation
15+
private let configuration: BottomSheetConfiguration
1516

1617
// MARK: - Init
1718

18-
public init(operation: UINavigationController.Operation) {
19+
public init(
20+
operation: UINavigationController.Operation,
21+
configuration: BottomSheetConfiguration
22+
) {
1923
self.operation = operation
24+
self.configuration = configuration
2025
}
2126

2227
// MARK: - UIViewControllerAnimatedTransitioning
@@ -78,9 +83,10 @@ public final class BottomSheetNavigationAnimatedTransitioning: NSObject, UIViewC
7883
height: destinationViewController.preferredContentSize.height + destinationView.safeAreaInsets.top + destinationView.safeAreaInsets.bottom
7984
)
8085

81-
let maxHeight = containerViewWindow.bounds.size.height
82-
- containerViewWindow.safeAreaInsets.top
83-
- BottomSheetPresentationController.pullBarHeight
86+
var maxHeight = containerViewWindow.bounds.size.height - containerViewWindow.safeAreaInsets.top
87+
if case .visible(let appearance) = configuration.pullBarConfiguration {
88+
maxHeight -= appearance.height
89+
}
8490

8591
let targetSize = CGSize(
8692
width: preferredContentSize.width,

0 commit comments

Comments
 (0)