diff --git a/BottomSheetDemo.xcodeproj/project.pbxproj b/BottomSheetDemo.xcodeproj/project.pbxproj index 18a04c5..e178868 100644 --- a/BottomSheetDemo.xcodeproj/project.pbxproj +++ b/BottomSheetDemo.xcodeproj/project.pbxproj @@ -38,6 +38,7 @@ 7DB24C9327BD18F1001030C7 /* BottomSheetUtils.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7DB24C8327BD1813001030C7 /* BottomSheetUtils.framework */; }; 7DB24CEC27BD1FAD001030C7 /* JMMulticastDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7DB24CE927BD1FAD001030C7 /* JMMulticastDelegate.m */; }; 7DD5185028AA2FBA003F3D2A /* UIViewController+Convenience.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DD5184E28AA2F46003F3D2A /* UIViewController+Convenience.swift */; }; + 7DD5185328AA369E003F3D2A /* BottomSheetConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DD5185128AA34D9003F3D2A /* BottomSheetConfiguration.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -111,6 +112,7 @@ 7DB24C8327BD1813001030C7 /* BottomSheetUtils.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = BottomSheetUtils.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 7DB24CE927BD1FAD001030C7 /* JMMulticastDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JMMulticastDelegate.m; sourceTree = ""; }; 7DD5184E28AA2F46003F3D2A /* UIViewController+Convenience.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+Convenience.swift"; sourceTree = ""; }; + 7DD5185128AA34D9003F3D2A /* BottomSheetConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BottomSheetConfiguration.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -248,6 +250,7 @@ 7DD5184D28AA2F3B003F3D2A /* Extensions */, 7DA6E0B5274F915A009F5C37 /* NavigationController */, 7DA6E0B9274F915A009F5C37 /* Presentation */, + 7DD5185128AA34D9003F3D2A /* BottomSheetConfiguration.swift */, ); path = Core; sourceTree = ""; @@ -508,6 +511,7 @@ 7DA6E0DE274F918E009F5C37 /* BottomSheetModalDismissalHandler.swift in Sources */, 7DA6E0DF274F918E009F5C37 /* BottomSheetPresentationController.swift in Sources */, 7DA6E0E7274F919A009F5C37 /* UIScrollView+MulticastDelegate.swift in Sources */, + 7DD5185328AA369E003F3D2A /* BottomSheetConfiguration.swift in Sources */, 7DA6E0E8274F919A009F5C37 /* UINavigationController+MulticastDelegate.swift in Sources */, 7DA6E0E6274F9196009F5C37 /* CGSize+Helpers.swift in Sources */, 7DD5185028AA2FBA003F3D2A /* UIViewController+Convenience.swift in Sources */, diff --git a/BottomSheetDemo/Sources/User Interface/Screens/Root/RootViewController.swift b/BottomSheetDemo/Sources/User Interface/Screens/Root/RootViewController.swift index 5913ea3..f000f8a 100644 --- a/BottomSheetDemo/Sources/User Interface/Screens/Root/RootViewController.swift +++ b/BottomSheetDemo/Sources/User Interface/Screens/Root/RootViewController.swift @@ -51,7 +51,9 @@ final class RootViewController: UIViewController { @objc private func handleShowBottomSheet() { let viewController = ResizeViewController(initialHeight: 300) - let navigationController = BottomSheetNavigationController(rootViewController: viewController) - presentBottomSheet(viewController: navigationController) + presentBottomSheetInsideNavigationController( + viewController: viewController, + configuration: .default + ) } } diff --git a/Sources/BottomSheet/Core/BottomSheetConfiguration.swift b/Sources/BottomSheet/Core/BottomSheetConfiguration.swift new file mode 100644 index 0000000..1dcb352 --- /dev/null +++ b/Sources/BottomSheet/Core/BottomSheetConfiguration.swift @@ -0,0 +1,56 @@ +// +// BottomSheetConfiguration.swift +// BottomSheetDemo +// +// Created by Mikhail Maslo on 15.08.2022. +// Copyright © 2022 Joom. All rights reserved. +// + +import UIKit + +public struct BottomSheetConfiguration { + public enum PullBarConfiguration { + public struct PullBarAppearance { + public let height: CGFloat + + public init(height: CGFloat) { + self.height = height + } + } + + case hidden + case visible(PullBarAppearance) + + public static let `default`: PullBarConfiguration = .visible(PullBarAppearance(height: 20)) + } + + public struct ShadowConfiguration { + public let backgroundColor: UIColor + + public init(backgroundColor: UIColor) { + self.backgroundColor = backgroundColor + } + + public static let `default` = ShadowConfiguration(backgroundColor: UIColor.black.withAlphaComponent(0.6)) + } + + public let cornerRadius: CGFloat + public let pullBarConfiguration: PullBarConfiguration + public let shadowConfiguration: ShadowConfiguration + + public init( + cornerRadius: CGFloat, + pullBarConfiguration: PullBarConfiguration, + shadowConfiguration: ShadowConfiguration + ) { + self.cornerRadius = cornerRadius + self.pullBarConfiguration = pullBarConfiguration + self.shadowConfiguration = shadowConfiguration + } + + public static let `default` = BottomSheetConfiguration( + cornerRadius: 10, + pullBarConfiguration: .default, + shadowConfiguration: .default + ) +} diff --git a/Sources/BottomSheet/Core/Extensions/UIViewController+Convenience.swift b/Sources/BottomSheet/Core/Extensions/UIViewController+Convenience.swift index c5cd24c..e72106e 100644 --- a/Sources/BottomSheet/Core/Extensions/UIViewController+Convenience.swift +++ b/Sources/BottomSheet/Core/Extensions/UIViewController+Convenience.swift @@ -15,12 +15,17 @@ public final class DefaultBottomSheetPresentationControllerFactory: BottomSheetP // MARK: - Public properties + private let configuration: BottomSheetConfiguration private let dismissalHandlerProvider: DismissalHandlerProvider // MARK: - Init - public init(dismissalHandlerProvider: @escaping DismissalHandlerProvider) { + public init( + configuration: BottomSheetConfiguration, + dismissalHandlerProvider: @escaping DismissalHandlerProvider + ) { self.dismissalHandlerProvider = dismissalHandlerProvider + self.configuration = configuration } // MARK: - BottomSheetPresentationControllerFactory @@ -32,7 +37,8 @@ public final class DefaultBottomSheetPresentationControllerFactory: BottomSheetP BottomSheetPresentationController( presentedViewController: presentedViewController, presentingViewController: presentingViewController, - dismissalHandler: dismissalHandlerProvider() + dismissalHandler: dismissalHandlerProvider(), + configuration: configuration ) } } @@ -70,10 +76,10 @@ public extension UIViewController { private static var bottomSheetTransitionDelegateKey: UInt8 = 0 - func presentBottomSheet(viewController: UIViewController) { + func presentBottomSheet(viewController: UIViewController, configuration: BottomSheetConfiguration) { weak var presentingViewController = self weak var currentBottomSheetTransitionDelegate: UIViewControllerTransitioningDelegate? - let presentationControllerFactory = DefaultBottomSheetPresentationControllerFactory { + let presentationControllerFactory = DefaultBottomSheetPresentationControllerFactory(configuration: configuration) { DefaultBottomSheetModalDismissalHandler(presentingViewController: presentingViewController) { if currentBottomSheetTransitionDelegate === presentingViewController?.bottomSheetTransitionDelegate { presentingViewController?.bottomSheetTransitionDelegate = nil @@ -88,4 +94,9 @@ public extension UIViewController { viewController.modalPresentationStyle = .custom present(viewController, animated: true, completion: nil) } + + func presentBottomSheetInsideNavigationController(viewController: UIViewController, configuration: BottomSheetConfiguration) { + let navigationController = BottomSheetNavigationController(rootViewController: viewController, configuration: configuration) + presentBottomSheet(viewController: navigationController, configuration: configuration) + } } diff --git a/Sources/BottomSheet/Core/NavigationController/BottomSheetNavigationController.swift b/Sources/BottomSheet/Core/NavigationController/BottomSheetNavigationController.swift index 389138d..bdd4827 100644 --- a/Sources/BottomSheet/Core/NavigationController/BottomSheetNavigationController.swift +++ b/Sources/BottomSheet/Core/NavigationController/BottomSheetNavigationController.swift @@ -16,7 +16,20 @@ public final class BottomSheetNavigationController: UINavigationController { private var canAnimatePreferredContentSizeUpdates = false private weak var lastTransitionViewController: UIViewController? - + + private let configuration: BottomSheetConfiguration + + // MARK: - Init + + public init(rootViewController: UIViewController, configuration: BottomSheetConfiguration) { + self.configuration = configuration + super.init(rootViewController: rootViewController) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + // MARK: - UIViewController public override func viewDidLoad() { @@ -128,7 +141,7 @@ extension BottomSheetNavigationController: UINavigationControllerDelegate { } lastTransitionViewController = fromVC - return BottomSheetNavigationAnimatedTransitioning(operation: operation) + return BottomSheetNavigationAnimatedTransitioning(operation: operation, configuration: configuration) } public func navigationController( diff --git a/Sources/BottomSheet/Core/NavigationController/BottomSheetNavigationStyle.swift b/Sources/BottomSheet/Core/NavigationController/BottomSheetNavigationStyle.swift index 158a092..715d37c 100644 --- a/Sources/BottomSheet/Core/NavigationController/BottomSheetNavigationStyle.swift +++ b/Sources/BottomSheet/Core/NavigationController/BottomSheetNavigationStyle.swift @@ -12,11 +12,16 @@ public final class BottomSheetNavigationAnimatedTransitioning: NSObject, UIViewC // MARK: - Private private let operation: UINavigationController.Operation + private let configuration: BottomSheetConfiguration // MARK: - Init - public init(operation: UINavigationController.Operation) { + public init( + operation: UINavigationController.Operation, + configuration: BottomSheetConfiguration + ) { self.operation = operation + self.configuration = configuration } // MARK: - UIViewControllerAnimatedTransitioning @@ -78,9 +83,10 @@ public final class BottomSheetNavigationAnimatedTransitioning: NSObject, UIViewC height: destinationViewController.preferredContentSize.height + destinationView.safeAreaInsets.top + destinationView.safeAreaInsets.bottom ) - let maxHeight = containerViewWindow.bounds.size.height - - containerViewWindow.safeAreaInsets.top - - BottomSheetPresentationController.pullBarHeight + var maxHeight = containerViewWindow.bounds.size.height - containerViewWindow.safeAreaInsets.top + if case .visible(let appearance) = configuration.pullBarConfiguration { + maxHeight -= appearance.height + } let targetSize = CGSize( width: preferredContentSize.width, diff --git a/Sources/BottomSheet/Core/Presentation/BottomSheetPresentationController.swift b/Sources/BottomSheet/Core/Presentation/BottomSheetPresentationController.swift index 4d385f7..10036d1 100644 --- a/Sources/BottomSheet/Core/Presentation/BottomSheetPresentationController.swift +++ b/Sources/BottomSheet/Core/Presentation/BottomSheetPresentationController.swift @@ -23,17 +23,8 @@ public final class BottomSheetPresentationController: UIPresentationController { case dismissing } - private struct Style { - static let cornerRadius: CGFloat = 10 - static let pullBarHeight = Style.cornerRadius * 2 - } - // MARK: - Public properties - static var pullBarHeight: CGFloat { - Style.pullBarHeight - } - var interactiveTransition: UIViewControllerInteractiveTransitioning? { interactionController } @@ -75,15 +66,18 @@ public final class BottomSheetPresentationController: UIPresentationController { private var cachedInsets: UIEdgeInsets = .zero private let dismissalHandler: BottomSheetModalDismissalHandler + private let configuration: BottomSheetConfiguration // MARK: - Init public init( presentedViewController: UIViewController, presentingViewController: UIViewController?, - dismissalHandler: BottomSheetModalDismissalHandler + dismissalHandler: BottomSheetModalDismissalHandler, + configuration: BottomSheetConfiguration ) { self.dismissalHandler = dismissalHandler + self.configuration = configuration super.init(presentedViewController: presentedViewController, presenting: presentingViewController) } @@ -266,7 +260,7 @@ public final class BottomSheetPresentationController: UIPresentationController { presentedViewController.view.clipsToBounds = true pullBar?.layer.mask = nil - presentedViewController.view.layer.cornerRadius = Style.cornerRadius + presentedViewController.view.layer.cornerRadius = configuration.cornerRadius presentedViewController.view.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner] } @@ -276,22 +270,31 @@ public final class BottomSheetPresentationController: UIPresentationController { return } - let shadingView = UIView() - shadingView.backgroundColor = UIColor.black.withAlphaComponent(0.6) - containerView.addSubview(shadingView) - shadingView.frame = containerView.bounds + addShadow(containerView: containerView) + addPullBarIfNeeded(containerView: containerView) + } + private func addPullBarIfNeeded(containerView: UIView) { + guard case .visible(let appearance) = configuration.pullBarConfiguration else { return } let pullBar = PullBar() - pullBar.frame.size = CGSize(width: containerView.frame.width, height: Style.pullBarHeight) + pullBar.frame.size = CGSize(width: containerView.frame.width, height: appearance.height) containerView.addSubview(pullBar) + self.pullBar = pullBar + } + + private func addShadow(containerView: UIView) { + let shadingView = UIView() + shadingView.backgroundColor = configuration.shadowConfiguration.backgroundColor + containerView.addSubview(shadingView) + shadingView.frame = containerView.bounds + let tapGesture = UITapGestureRecognizer() shadingView.addGestureRecognizer(tapGesture) tapGesture.addTarget(self, action: #selector(handleShadingViewTapGesture)) self.shadingView = shadingView - self.pullBar = pullBar } @objc @@ -314,7 +317,10 @@ public final class BottomSheetPresentationController: UIPresentationController { let windowInsets = presentedView?.window?.safeAreaInsets ?? cachedInsets let preferredHeight = presentedViewController.preferredContentSize.height + windowInsets.bottom - let maxHeight = containerView.bounds.height - windowInsets.top - Style.pullBarHeight + var maxHeight = containerView.bounds.height - windowInsets.top + if case .visible(let appearance) = configuration.pullBarConfiguration { + maxHeight -= appearance.height + } let height = min(preferredHeight, maxHeight) return .init( @@ -334,7 +340,9 @@ public final class BottomSheetPresentationController: UIPresentationController { let targetFrame = targetFrameForPresentedView() if !oldFrame.isAlmostEqual(to: targetFrame) { presentedView.frame = targetFrame - pullBar?.frame.origin.y = presentedView.frame.minY - Style.pullBarHeight + pixelSize + if case .visible(let appearance) = configuration.pullBarConfiguration { + pullBar?.frame.origin.y = presentedView.frame.minY - appearance.height + pixelSize + } } } @@ -520,15 +528,21 @@ extension BottomSheetPresentationController: UIViewControllerAnimatedTransitioni size: sourceView.frame.size ) + let updatePullBarFrame = { + guard case .visible(let appearnce) = self.configuration.pullBarConfiguration else { return } + + self.pullBar?.frame.origin.y = presentedView.frame.minY - appearnce.height + pixelSize + } + presentedView.frame = isPresenting ? offscreenFrame : frameInContainer - pullBar?.frame.origin.y = presentedView.frame.minY - Style.pullBarHeight + pixelSize + updatePullBarFrame() shadingView?.alpha = isPresenting ? 0 : 1 applyStyle() let animations = { presentedView.frame = isPresenting ? frameInContainer : offscreenFrame - self.pullBar?.frame.origin.y = presentedView.frame.minY - Style.pullBarHeight + pixelSize + updatePullBarFrame() self.shadingView?.alpha = isPresenting ? 1 : 0 }