├── .DS_Store ├── Example ├── .DS_Store ├── BottomSheetExample │ ├── .DS_Store │ ├── Assets.xcassets │ │ ├── Contents.json │ │ ├── .DS_Store │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── AppDelegate.swift │ ├── ViewController.swift │ ├── Base.lproj │ │ ├── Main.storyboard │ │ └── LaunchScreen.storyboard │ └── Example │ │ └── ExampleViewController.swift └── BottomSheetExample.xcodeproj │ ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── swiftpm │ │ └── Package.resolved │ └── project.pbxproj ├── Sources ├── .DS_Store └── BottomSheet │ ├── .DS_Store │ ├── Public │ ├── BottomSheetTransitioningDelegate.swift │ └── BottomSheetConfiguration.swift │ └── Internal │ ├── BottomSheetTransition.swift │ └── BottomSheetPresentationController.swift ├── bottom_sheet.gif ├── .swiftpm └── xcode │ └── package.xcworkspace │ └── contents.xcworkspacedata ├── README.md ├── Package.swift ├── LICENSE └── .gitignore /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitalybatrakov/BottomSheet/HEAD/.DS_Store -------------------------------------------------------------------------------- /Example/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitalybatrakov/BottomSheet/HEAD/Example/.DS_Store -------------------------------------------------------------------------------- /Sources/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitalybatrakov/BottomSheet/HEAD/Sources/.DS_Store -------------------------------------------------------------------------------- /bottom_sheet.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitalybatrakov/BottomSheet/HEAD/bottom_sheet.gif -------------------------------------------------------------------------------- /Sources/BottomSheet/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitalybatrakov/BottomSheet/HEAD/Sources/BottomSheet/.DS_Store -------------------------------------------------------------------------------- /Example/BottomSheetExample/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitalybatrakov/BottomSheet/HEAD/Example/BottomSheetExample/.DS_Store -------------------------------------------------------------------------------- /Example/BottomSheetExample/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Example/BottomSheetExample/Assets.xcassets/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitalybatrakov/BottomSheet/HEAD/Example/BottomSheetExample/Assets.xcassets/.DS_Store -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/BottomSheetExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/BottomSheetExample/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 | -------------------------------------------------------------------------------- /Example/BottomSheetExample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Example/BottomSheetExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "pins" : [ 3 | { 4 | "identity" : "snapkit", 5 | "kind" : "remoteSourceControl", 6 | "location" : "https://github.com/SnapKit/SnapKit.git", 7 | "state" : { 8 | "revision" : "f222cbdf325885926566172f6f5f06af95473158", 9 | "version" : "5.6.0" 10 | } 11 | } 12 | ], 13 | "version" : 2 14 | } 15 | -------------------------------------------------------------------------------- /Example/BottomSheetExample/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | @UIApplicationMain 4 | class AppDelegate: UIResponder, UIApplicationDelegate { 5 | 6 | var window: UIWindow? 7 | 8 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 9 | 10 | window?.rootViewController = ViewController() 11 | window?.makeKeyAndVisible() 12 | 13 | return true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BottomSheet for iOS 2 | iOS 13+ 3 | Simple BottomSheet for iOS using UIKit custom transition. 4 | Automatically calculates the bottom sheet height depending on the content size. 5 | Easy to use: 6 | ```swift 7 | let vc = ExampleViewController() 8 | let delegate = BottomSheetTransitioningDelegate(configuration: .default) 9 | vc.modalPresentationStyle = .custom 10 | vc.transitioningDelegate = delegate // don't forget to retain the delegate (transitioningDelegate is weak) 11 | present(vc, animated: true) 12 | ``` 13 | 14 | ### Swift Package Manager (SPM) 15 | 16 | Add the following to your Package.swift 17 | 18 | ```swift 19 | dependencies: [ 20 | .package(url: "https://github.com/vitalybatrakov/BottomSheet.git", branch: "main") 21 | ] 22 | ``` 23 | 24 | ### It doesn’t support yet: 25 | 26 | - Content scrolling if presented view controller's content height bigger that screen height; 27 | - Using navigation (navigation controller) inside the bottom sheet. 28 | 29 | ![BottomSheet gif](bottom_sheet.gif) 30 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 5.6 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: "BottomSheet", 8 | platforms: [ 9 | .iOS(.v13), 10 | ], 11 | products: [ 12 | // Products define the executables and libraries a package produces, and make them visible to other packages. 13 | .library( 14 | name: "BottomSheet", 15 | targets: ["BottomSheet"]), 16 | ], 17 | dependencies: [ 18 | // Dependencies declare other packages that this package depends on. 19 | // .package(url: /* package url */, from: "1.0.0"), 20 | ], 21 | targets: [ 22 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 23 | // Targets can depend on other targets in this package, and on products in packages this package depends on. 24 | .target( 25 | name: "BottomSheet", 26 | dependencies: []), 27 | ] 28 | ) 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Vitaly Batrakov 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 | -------------------------------------------------------------------------------- /Example/BottomSheetExample/ViewController.swift: -------------------------------------------------------------------------------- 1 | import BottomSheet 2 | import SnapKit 3 | import UIKit 4 | 5 | final class ViewController: UIViewController { 6 | 7 | private let delegate = BottomSheetTransitioningDelegate(configuration: .default) 8 | 9 | private lazy var button: UIButton = { 10 | let button = UIButton() 11 | button.backgroundColor = .black 12 | button.setTitle("Show bottom sheet", for: .normal) 13 | button.addTarget(self, action: #selector(didTapButton), for: .touchUpInside) 14 | return button 15 | }() 16 | 17 | override func viewDidLoad() { 18 | super.viewDidLoad() 19 | 20 | view.backgroundColor = .systemBackground 21 | 22 | view.addSubview(button) 23 | button.snp.makeConstraints { 24 | $0.centerX.equalToSuperview() 25 | $0.top.equalToSuperview().offset(150) 26 | $0.width.equalTo(200) 27 | $0.height.equalTo(40) 28 | } 29 | } 30 | 31 | @objc private func didTapButton() { 32 | let vc = ExampleViewController() 33 | vc.modalPresentationStyle = .custom 34 | vc.transitioningDelegate = delegate 35 | present(vc, animated: true) 36 | } 37 | } 38 | 39 | -------------------------------------------------------------------------------- /Example/BottomSheetExample/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Example/BottomSheetExample/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Sources/BottomSheet/Public/BottomSheetTransitioningDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | public final class BottomSheetTransitioningDelegate: NSObject { 4 | 5 | // MARK: Internal properties 6 | 7 | let transition: BottomSheetTransition 8 | 9 | // MARK: Private properties 10 | 11 | private let configuration: BottomSheetConfiguration 12 | 13 | // MARK: Init 14 | 15 | public init(configuration: BottomSheetConfiguration) { 16 | self.configuration = configuration 17 | self.transition = BottomSheetTransition(configuration: configuration) 18 | } 19 | } 20 | 21 | // MARK: - UIViewControllerTransitioningDelegate 22 | 23 | extension BottomSheetTransitioningDelegate: UIViewControllerTransitioningDelegate { 24 | 25 | public func animationController( 26 | forPresented presented: UIViewController, 27 | presenting: UIViewController, 28 | source: UIViewController 29 | ) -> UIViewControllerAnimatedTransitioning? { 30 | transition.isPresenting = true 31 | transition.wantsInteractiveStart = false 32 | return transition 33 | } 34 | 35 | public func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { 36 | transition.isPresenting = false 37 | return transition 38 | } 39 | 40 | public func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { 41 | transition.isPresenting = false 42 | return transition 43 | } 44 | 45 | public func presentationController( 46 | forPresented presented: UIViewController, 47 | presenting: UIViewController?, 48 | source: UIViewController 49 | ) -> UIPresentationController? { 50 | BottomSheetPresentationController(presentedViewController: presented, 51 | presenting: presenting, 52 | configuration: configuration) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Example/BottomSheetExample/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 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Sources/BottomSheet/Public/BottomSheetConfiguration.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | public struct BottomSheetConfiguration { 4 | 5 | public let showPullBar: Bool 6 | public let tapToDismissEnabled: Bool 7 | public let panToDismissEnabled: Bool 8 | public let cornerRadius: CGFloat 9 | public let overlayColor: UIColor 10 | 11 | /// present/dismiss animation duration 12 | public let animationDuration: TimeInterval 13 | 14 | /// The damping ratio for the spring animation as it approaches its quiescent state. 15 | /// To smoothly decelerate the animation without oscillation, use a value of 1. 16 | /// Employ a damping ratio closer to zero to increase oscillation. 17 | public let dampingRatio: Double 18 | 19 | /// Fraction part of transition completed interactively required to dismiss transition 20 | /// Value range is 0...1 21 | public let dismissThreshold: CGFloat 22 | 23 | public init(showPullBar: Bool, 24 | tapToDismissEnabled: Bool, 25 | panToDismissEnabled: Bool, 26 | cornerRadius: CGFloat, 27 | overlayColor: UIColor, 28 | animationDuration: TimeInterval, 29 | dampingRatio: Double, 30 | dismissThreshold: CGFloat) { 31 | self.showPullBar = showPullBar 32 | self.tapToDismissEnabled = tapToDismissEnabled 33 | self.panToDismissEnabled = panToDismissEnabled 34 | self.cornerRadius = cornerRadius 35 | self.overlayColor = overlayColor 36 | self.animationDuration = animationDuration 37 | self.dampingRatio = dampingRatio 38 | self.dismissThreshold = dismissThreshold 39 | } 40 | 41 | // MARK: Configurations 42 | 43 | public static let `default` = BottomSheetConfiguration( 44 | showPullBar: true, 45 | tapToDismissEnabled: true, 46 | panToDismissEnabled: true, 47 | cornerRadius: 16, 48 | overlayColor: .black.withAlphaComponent(0.3), 49 | animationDuration: 0.5, 50 | dampingRatio: 0.9, 51 | dismissThreshold: 0.3 52 | ) 53 | } 54 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## User settings 6 | xcuserdata/ 7 | 8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 9 | *.xcscmblueprint 10 | *.xccheckout 11 | 12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 13 | build/ 14 | DerivedData/ 15 | *.moved-aside 16 | *.pbxuser 17 | !default.pbxuser 18 | *.mode1v3 19 | !default.mode1v3 20 | *.mode2v3 21 | !default.mode2v3 22 | *.perspectivev3 23 | !default.perspectivev3 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | 28 | ## App packaging 29 | *.ipa 30 | *.dSYM.zip 31 | *.dSYM 32 | 33 | ## Playgrounds 34 | timeline.xctimeline 35 | playground.xcworkspace 36 | 37 | # Swift Package Manager 38 | # 39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 40 | # Packages/ 41 | # Package.pins 42 | # Package.resolved 43 | # *.xcodeproj 44 | # 45 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 46 | # hence it is not needed unless you have added a package configuration file to your project 47 | # .swiftpm 48 | 49 | .build/ 50 | 51 | # CocoaPods 52 | # 53 | # We recommend against adding the Pods directory to your .gitignore. However 54 | # you should judge for yourself, the pros and cons are mentioned at: 55 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 56 | # 57 | # Pods/ 58 | # 59 | # Add this line if you want to avoid checking in source code from the Xcode workspace 60 | # *.xcworkspace 61 | 62 | # Carthage 63 | # 64 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 65 | # Carthage/Checkouts 66 | 67 | Carthage/Build/ 68 | 69 | # Accio dependency management 70 | Dependencies/ 71 | .accio/ 72 | 73 | # fastlane 74 | # 75 | # It is recommended to not store the screenshots in the git repo. 76 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 77 | # For more information about the recommended setup visit: 78 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 79 | 80 | fastlane/report.xml 81 | fastlane/Preview.html 82 | fastlane/screenshots/**/*.png 83 | fastlane/test_output 84 | 85 | # Code Injection 86 | # 87 | # After new code Injection tools there's a generated folder /iOSInjectionProject 88 | # https://github.com/johnno1962/injectionforxcode 89 | 90 | iOSInjectionProject/ 91 | -------------------------------------------------------------------------------- /Example/BottomSheetExample/Example/ExampleViewController.swift: -------------------------------------------------------------------------------- 1 | import SnapKit 2 | import UIKit 3 | 4 | final class ExampleViewController: UIViewController { 5 | 6 | private let titleLabel: UILabel = { 7 | let title = UILabel() 8 | title.font = .preferredFont(forTextStyle: .largeTitle, compatibleWith: nil) 9 | title.text = "Title" 10 | return title 11 | }() 12 | 13 | private let subtitleLabel: UILabel = { 14 | let title = UILabel() 15 | title.font = .preferredFont(forTextStyle: .subheadline, compatibleWith: nil) 16 | title.text = "Subtitle" 17 | return title 18 | }() 19 | 20 | private let descriptionLabel: UILabel = { 21 | let title = UILabel() 22 | title.font = .preferredFont(forTextStyle: .body, compatibleWith: nil) 23 | title.text = """ 24 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. 25 | """ 26 | title.numberOfLines = 0 27 | return title 28 | }() 29 | 30 | private let stack: UIStackView = { 31 | let stack = UIStackView() 32 | stack.axis = .vertical 33 | stack.spacing = 8 34 | return stack 35 | }() 36 | 37 | override func viewDidLoad() { 38 | super.viewDidLoad() 39 | 40 | view.backgroundColor = .systemBackground 41 | 42 | setupLayout() 43 | } 44 | 45 | private func setupLayout() { 46 | view.addSubview(stack) 47 | stack.snp.makeConstraints { 48 | $0.top.leading.trailing.equalToSuperview().inset(16) 49 | $0.bottom.equalTo(view.safeAreaLayoutGuide.snp.bottom) 50 | } 51 | stack.addArrangedSubview(titleLabel) 52 | stack.addArrangedSubview(subtitleLabel) 53 | stack.addArrangedSubview(descriptionLabel) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Sources/BottomSheet/Internal/BottomSheetTransition.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | final class BottomSheetTransition: UIPercentDrivenInteractiveTransition { 4 | 5 | // MARK: Internal properties 6 | 7 | /// `true` if the transition is for the presentation. 8 | /// `false` if the transition is for the dismissal. 9 | var isPresenting = true 10 | 11 | /// The progress of the dismiss animation. 12 | var dismissFractionComplete: CGFloat { 13 | dismissAnimator?.fractionComplete ?? .zero 14 | } 15 | 16 | // MARK: Private properties 17 | 18 | /// The interactive animator used for the dismiss transition. 19 | private var dismissAnimator: UIViewPropertyAnimator? 20 | 21 | /// The animator used for the presentation animation. 22 | private var presentationAnimator: UIViewPropertyAnimator? 23 | 24 | private let configuration: BottomSheetConfiguration 25 | 26 | private var animationDuration: TimeInterval { 27 | configuration.animationDuration 28 | } 29 | 30 | private var dampingRatio: Double { 31 | configuration.dampingRatio 32 | } 33 | 34 | // MARK: Init 35 | 36 | init(configuration: BottomSheetConfiguration) { 37 | self.configuration = configuration 38 | super.init() 39 | } 40 | } 41 | 42 | // MARK: - UIViewControllerAnimatedTransitioning 43 | 44 | extension BottomSheetTransition: UIViewControllerAnimatedTransitioning { 45 | 46 | func transitionDuration(using _: UIViewControllerContextTransitioning?) -> TimeInterval { 47 | animationDuration 48 | } 49 | 50 | /// Will get called when the transition is not interactive to animate presenting or dismissing of the controller. 51 | func animateTransition( 52 | using transitionContext: UIViewControllerContextTransitioning 53 | ) { 54 | interruptibleAnimator(using: transitionContext).startAnimation() 55 | } 56 | 57 | /// Will get called when the transition is interactive. 58 | func interruptibleAnimator( 59 | using transitionContext: UIViewControllerContextTransitioning 60 | ) -> UIViewImplicitlyAnimating { 61 | if isPresenting { 62 | return presentationAnimator(using: transitionContext) 63 | } else { 64 | return dismissAnimator(using: transitionContext) 65 | } 66 | } 67 | 68 | // MARK: Private methods 69 | 70 | private func presentationAnimator( 71 | using transitionContext: UIViewControllerContextTransitioning 72 | ) -> UIViewImplicitlyAnimating { 73 | presentationAnimator ?? createPresentationAnimator(using: transitionContext) 74 | } 75 | 76 | private func createPresentationAnimator( 77 | using transitionContext: UIViewControllerContextTransitioning 78 | ) -> UIViewImplicitlyAnimating { 79 | guard 80 | let toViewController = transitionContext.viewController(forKey: .to), 81 | let toView = transitionContext.view(forKey: .to) 82 | else { 83 | return UIViewPropertyAnimator() 84 | } 85 | let animator = UIViewPropertyAnimator( 86 | duration: transitionDuration(using: transitionContext), 87 | dampingRatio: dampingRatio 88 | ) 89 | presentationAnimator = animator 90 | 91 | toView.frame = transitionContext.finalFrame(for: toViewController) 92 | toView.frame.origin.y = transitionContext.containerView.frame.maxY 93 | transitionContext.containerView.addSubview(toView) 94 | 95 | animator.addAnimations { 96 | toView.frame = transitionContext.finalFrame(for: toViewController) 97 | } 98 | animator.addCompletion { [weak self] position in 99 | self?.presentationAnimator = nil 100 | 101 | guard case .end = position else { 102 | transitionContext.completeTransition(false) 103 | return 104 | } 105 | transitionContext.completeTransition(!transitionContext.transitionWasCancelled) 106 | } 107 | return animator 108 | } 109 | 110 | private func dismissAnimator( 111 | using transitionContext: UIViewControllerContextTransitioning 112 | ) -> UIViewImplicitlyAnimating { 113 | dismissAnimator ?? createDismissAnimator(using: transitionContext) 114 | } 115 | 116 | private func createDismissAnimator( 117 | using transitionContext: UIViewControllerContextTransitioning 118 | ) -> UIViewImplicitlyAnimating { 119 | guard let fromView = transitionContext.view(forKey: .from) else { 120 | return UIViewPropertyAnimator() 121 | } 122 | let animator = UIViewPropertyAnimator( 123 | duration: transitionDuration(using: transitionContext), 124 | dampingRatio: dampingRatio 125 | ) 126 | dismissAnimator = animator 127 | 128 | animator.addAnimations { 129 | fromView.frame.origin.y = fromView.frame.maxY 130 | } 131 | animator.addCompletion { [weak self] position in 132 | self?.dismissAnimator = nil 133 | 134 | guard case .end = position else { 135 | transitionContext.completeTransition(false) 136 | return 137 | } 138 | fromView.removeFromSuperview() 139 | transitionContext.completeTransition(!transitionContext.transitionWasCancelled) 140 | } 141 | return animator 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /Sources/BottomSheet/Internal/BottomSheetPresentationController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | final class BottomSheetPresentationController: UIPresentationController { 4 | 5 | // MARK: Private properties 6 | 7 | /// The object that is managing the presentation and transition. 8 | private var transitioningDelegate: BottomSheetTransitioningDelegate? { 9 | presentedViewController.transitioningDelegate as? BottomSheetTransitioningDelegate 10 | } 11 | 12 | private let pullBarView: UIView = { 13 | let view = UIView() 14 | view.bounds.size = CGSize(width: 32, height: 4) 15 | view.backgroundColor = .systemFill 16 | view.layer.cornerRadius = view.frame.height / 2 17 | return view 18 | }() 19 | 20 | private lazy var overlayView: UIView = { 21 | let view = UIView() 22 | view.backgroundColor = configuration.overlayColor 23 | if configuration.tapToDismissEnabled { 24 | let tapGR = UITapGestureRecognizer(target: self, 25 | action: #selector(didTapOverlayView)) 26 | view.addGestureRecognizer(tapGR) 27 | } 28 | return view 29 | }() 30 | 31 | /// The pan gesture used to drag and interactively dismiss the sheet. 32 | private lazy var panGesture = UIPanGestureRecognizer(target: self, 33 | action: #selector(pannedPresentedView)) 34 | 35 | private var cornerRadius: CGFloat { 36 | configuration.cornerRadius 37 | } 38 | 39 | private var dismissThreshold: CGFloat { 40 | configuration.dismissThreshold 41 | } 42 | 43 | private let configuration: BottomSheetConfiguration 44 | 45 | // MARK: Init 46 | 47 | init( 48 | presentedViewController: UIViewController, 49 | presenting: UIViewController?, 50 | configuration: BottomSheetConfiguration 51 | ) { 52 | self.configuration = configuration 53 | super.init(presentedViewController: presentedViewController, presenting: presenting) 54 | } 55 | 56 | // MARK: UIPresentationController 57 | 58 | override var frameOfPresentedViewInContainerView: CGRect { 59 | guard 60 | let containerView = containerView, 61 | let presentedView = presentedView 62 | else { 63 | return super.frameOfPresentedViewInContainerView 64 | } 65 | /// The maximum height allowed for the sheet. We allow the sheet to reach the top safe area inset. 66 | let maximumHeight = containerView.frame.height - containerView.safeAreaInsets.top - containerView.safeAreaInsets.bottom 67 | 68 | let fittingSize = CGSize(width: containerView.bounds.width, 69 | height: UIView.layoutFittingCompressedSize.height) 70 | 71 | let presentedViewHeight = presentedView.systemLayoutSizeFitting( 72 | fittingSize, 73 | withHorizontalFittingPriority: .required, 74 | verticalFittingPriority: .fittingSizeLevel 75 | ).height 76 | /// The target height of the presented view. 77 | /// If the size of the of the presented view could not be computed, meaning its equal to zero, we default to the maximum height. 78 | let targetHeight = presentedViewHeight == .zero ? maximumHeight : presentedViewHeight 79 | /// Adjust the height of the view by adding the bottom safe area inset. 80 | let adjustedHeight = min(targetHeight, maximumHeight) + containerView.safeAreaInsets.bottom 81 | 82 | let targetSize = CGSize(width: containerView.frame.width, height: adjustedHeight) 83 | let targetOrigin = CGPoint(x: .zero, y: containerView.frame.maxY - targetSize.height) 84 | 85 | return CGRect(origin: targetOrigin, size: targetSize) 86 | } 87 | 88 | override func presentationTransitionWillBegin() { 89 | super.presentationTransitionWillBegin() 90 | 91 | containerView?.addSubview(overlayView) 92 | if configuration.showPullBar { 93 | presentedView?.addSubview(pullBarView) 94 | } 95 | 96 | overlayView.alpha = 0 97 | 98 | presentedViewController.transitionCoordinator?.animate(alongsideTransition: { [weak self] _ in 99 | guard let self = self else { return } 100 | self.presentedView?.layer.cornerRadius = self.cornerRadius 101 | self.overlayView.alpha = 1 102 | }) 103 | } 104 | 105 | override func containerViewDidLayoutSubviews() { 106 | super.containerViewDidLayoutSubviews() 107 | 108 | setupSubviews() 109 | setupPresentedViewInteraction() 110 | } 111 | 112 | override func dismissalTransitionWillBegin() { 113 | super.dismissalTransitionWillBegin() 114 | 115 | presentedViewController.transitionCoordinator?.animate(alongsideTransition: { [weak self] _ in 116 | guard let self = self else { return } 117 | self.presentedView?.layer.cornerRadius = .zero 118 | self.overlayView.alpha = 0 119 | }) 120 | } 121 | 122 | // MARK: Private methods 123 | 124 | private func setupSubviews() { 125 | setupPresentedView() 126 | setupOverlayLayout() 127 | setupPullBarLayout() 128 | } 129 | 130 | private func setupPresentedView() { 131 | guard let presentedView = presentedView else { 132 | return 133 | } 134 | presentedView.layer.cornerCurve = .continuous 135 | presentedView.layer.maskedCorners = [ 136 | .layerMinXMinYCorner, 137 | .layerMaxXMinYCorner 138 | ] 139 | } 140 | 141 | private func setupOverlayLayout() { 142 | guard let containerView = containerView else { 143 | return 144 | } 145 | overlayView.frame = containerView.bounds 146 | } 147 | 148 | private func setupPullBarLayout() { 149 | guard 150 | configuration.showPullBar, 151 | let presentedView = presentedView 152 | else { 153 | return 154 | } 155 | pullBarView.frame.origin.y = 8 156 | pullBarView.center.x = presentedView.center.x 157 | presentedViewController.additionalSafeAreaInsets.top = pullBarView.frame.maxY 158 | } 159 | 160 | private func setupPresentedViewInteraction() { 161 | guard 162 | configuration.panToDismissEnabled, 163 | let presentedView = presentedView 164 | else { 165 | return 166 | } 167 | presentedView.addGestureRecognizer(panGesture) 168 | } 169 | 170 | /// Triggers the dismiss transition in an interactive manner. 171 | /// - Parameter isInteractive: Whether the transition should be started interactively by the user. 172 | private func dismiss(interactively isInteractive: Bool) { 173 | transitioningDelegate?.transition.wantsInteractiveStart = isInteractive 174 | presentedViewController.dismiss(animated: true) 175 | } 176 | 177 | /// Updates the progress of the dismiss transition. 178 | /// - Parameter translation: The translation of the presented view used to calculate the progress. 179 | private func updateTransitionProgress(for translation: CGPoint) { 180 | guard 181 | let transitioningDelegate = transitioningDelegate, 182 | let presentedView = presentedView 183 | else { 184 | return 185 | } 186 | let adjustedHeight = presentedView.frame.height - translation.y 187 | let progress = 1 - (adjustedHeight / presentedView.frame.height) 188 | transitioningDelegate.transition.update(progress) 189 | } 190 | 191 | /// Finish or cancel the dimiss transition depending on current progress of dismiss transition 192 | private func handleEndedInteraction() { 193 | guard let transitioningDelegate = transitioningDelegate else { 194 | return 195 | } 196 | if transitioningDelegate.transition.dismissFractionComplete > dismissThreshold { 197 | transitioningDelegate.transition.finish() 198 | } else { 199 | transitioningDelegate.transition.cancel() 200 | } 201 | } 202 | 203 | @objc 204 | private func didTapOverlayView() { 205 | dismiss(interactively: false) 206 | } 207 | 208 | @objc 209 | private func pannedPresentedView(_ recognizer: UIPanGestureRecognizer) { 210 | guard let presentedView = presentedView else { 211 | return 212 | } 213 | switch recognizer.state { 214 | case .began: 215 | dismiss(interactively: true) 216 | 217 | case .changed: 218 | let translation = recognizer.translation(in: presentedView) 219 | updateTransitionProgress(for: translation) 220 | 221 | case .ended, .cancelled, .failed: 222 | handleEndedInteraction() 223 | 224 | case .possible: 225 | break 226 | 227 | @unknown default: 228 | break 229 | } 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /Example/BottomSheetExample.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 55; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | BC46DDDF29747CFA0012DDF2 /* BottomSheet in Frameworks */ = {isa = PBXBuildFile; productRef = BC46DDDE29747CFA0012DDF2 /* BottomSheet */; }; 11 | BCA34AD42938FE8400AA8362 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCA34AD32938FE8400AA8362 /* AppDelegate.swift */; }; 12 | BCA34AD82938FE8400AA8362 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCA34AD72938FE8400AA8362 /* ViewController.swift */; }; 13 | BCA34ADB2938FE8400AA8362 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BCA34AD92938FE8400AA8362 /* Main.storyboard */; }; 14 | BCA34ADD2938FE8500AA8362 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BCA34ADC2938FE8500AA8362 /* Assets.xcassets */; }; 15 | BCA34AE02938FE8500AA8362 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BCA34ADE2938FE8500AA8362 /* LaunchScreen.storyboard */; }; 16 | BCA34AF2293A5ECD00AA8362 /* ExampleViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCA34AF1293A5ECD00AA8362 /* ExampleViewController.swift */; }; 17 | BCA34AF5293E469D00AA8362 /* SnapKit in Frameworks */ = {isa = PBXBuildFile; productRef = BCA34AF4293E469D00AA8362 /* SnapKit */; }; 18 | /* End PBXBuildFile section */ 19 | 20 | /* Begin PBXFileReference section */ 21 | BC46DDE129747F9D0012DDF2 /* BottomSheet */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = BottomSheet; path = ..; sourceTree = ""; }; 22 | BCA34AD02938FE8400AA8362 /* BottomSheetExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = BottomSheetExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 23 | BCA34AD32938FE8400AA8362 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 24 | BCA34AD72938FE8400AA8362 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 25 | BCA34ADA2938FE8400AA8362 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 26 | BCA34ADC2938FE8500AA8362 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 27 | BCA34ADF2938FE8500AA8362 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 28 | BCA34AF1293A5ECD00AA8362 /* ExampleViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExampleViewController.swift; sourceTree = ""; }; 29 | /* End PBXFileReference section */ 30 | 31 | /* Begin PBXFrameworksBuildPhase section */ 32 | BCA34ACD2938FE8400AA8362 /* Frameworks */ = { 33 | isa = PBXFrameworksBuildPhase; 34 | buildActionMask = 2147483647; 35 | files = ( 36 | BCA34AF5293E469D00AA8362 /* SnapKit in Frameworks */, 37 | BC46DDDF29747CFA0012DDF2 /* BottomSheet in Frameworks */, 38 | ); 39 | runOnlyForDeploymentPostprocessing = 0; 40 | }; 41 | /* End PBXFrameworksBuildPhase section */ 42 | 43 | /* Begin PBXGroup section */ 44 | BC46DDD829747AD20012DDF2 /* Frameworks */ = { 45 | isa = PBXGroup; 46 | children = ( 47 | ); 48 | name = Frameworks; 49 | sourceTree = ""; 50 | }; 51 | BC46DDE029747F9D0012DDF2 /* Packages */ = { 52 | isa = PBXGroup; 53 | children = ( 54 | BC46DDE129747F9D0012DDF2 /* BottomSheet */, 55 | ); 56 | name = Packages; 57 | sourceTree = ""; 58 | }; 59 | BCA34AC72938FE8400AA8362 = { 60 | isa = PBXGroup; 61 | children = ( 62 | BC46DDE029747F9D0012DDF2 /* Packages */, 63 | BCA34AD22938FE8400AA8362 /* BottomSheetExample */, 64 | BCA34AD12938FE8400AA8362 /* Products */, 65 | BC46DDD829747AD20012DDF2 /* Frameworks */, 66 | ); 67 | sourceTree = ""; 68 | }; 69 | BCA34AD12938FE8400AA8362 /* Products */ = { 70 | isa = PBXGroup; 71 | children = ( 72 | BCA34AD02938FE8400AA8362 /* BottomSheetExample.app */, 73 | ); 74 | name = Products; 75 | sourceTree = ""; 76 | }; 77 | BCA34AD22938FE8400AA8362 /* BottomSheetExample */ = { 78 | isa = PBXGroup; 79 | children = ( 80 | BCA34AF0293A5DD200AA8362 /* Example */, 81 | BCA34AD32938FE8400AA8362 /* AppDelegate.swift */, 82 | BCA34AD72938FE8400AA8362 /* ViewController.swift */, 83 | BCA34AD92938FE8400AA8362 /* Main.storyboard */, 84 | BCA34ADC2938FE8500AA8362 /* Assets.xcassets */, 85 | BCA34ADE2938FE8500AA8362 /* LaunchScreen.storyboard */, 86 | ); 87 | path = BottomSheetExample; 88 | sourceTree = ""; 89 | }; 90 | BCA34AF0293A5DD200AA8362 /* Example */ = { 91 | isa = PBXGroup; 92 | children = ( 93 | BCA34AF1293A5ECD00AA8362 /* ExampleViewController.swift */, 94 | ); 95 | path = Example; 96 | sourceTree = ""; 97 | }; 98 | /* End PBXGroup section */ 99 | 100 | /* Begin PBXNativeTarget section */ 101 | BCA34ACF2938FE8400AA8362 /* BottomSheetExample */ = { 102 | isa = PBXNativeTarget; 103 | buildConfigurationList = BCA34AE42938FE8500AA8362 /* Build configuration list for PBXNativeTarget "BottomSheetExample" */; 104 | buildPhases = ( 105 | BCA34ACC2938FE8400AA8362 /* Sources */, 106 | BCA34ACD2938FE8400AA8362 /* Frameworks */, 107 | BCA34ACE2938FE8400AA8362 /* Resources */, 108 | ); 109 | buildRules = ( 110 | ); 111 | dependencies = ( 112 | ); 113 | name = BottomSheetExample; 114 | packageProductDependencies = ( 115 | BCA34AF4293E469D00AA8362 /* SnapKit */, 116 | BC46DDDE29747CFA0012DDF2 /* BottomSheet */, 117 | ); 118 | productName = BottomSheetExample; 119 | productReference = BCA34AD02938FE8400AA8362 /* BottomSheetExample.app */; 120 | productType = "com.apple.product-type.application"; 121 | }; 122 | /* End PBXNativeTarget section */ 123 | 124 | /* Begin PBXProject section */ 125 | BCA34AC82938FE8400AA8362 /* Project object */ = { 126 | isa = PBXProject; 127 | attributes = { 128 | BuildIndependentTargetsInParallel = 1; 129 | LastSwiftUpdateCheck = 1340; 130 | LastUpgradeCheck = 1340; 131 | TargetAttributes = { 132 | BCA34ACF2938FE8400AA8362 = { 133 | CreatedOnToolsVersion = 13.4.1; 134 | }; 135 | }; 136 | }; 137 | buildConfigurationList = BCA34ACB2938FE8400AA8362 /* Build configuration list for PBXProject "BottomSheetExample" */; 138 | compatibilityVersion = "Xcode 13.0"; 139 | developmentRegion = en; 140 | hasScannedForEncodings = 0; 141 | knownRegions = ( 142 | en, 143 | Base, 144 | ); 145 | mainGroup = BCA34AC72938FE8400AA8362; 146 | packageReferences = ( 147 | BCA34AF3293E469D00AA8362 /* XCRemoteSwiftPackageReference "SnapKit" */, 148 | ); 149 | productRefGroup = BCA34AD12938FE8400AA8362 /* Products */; 150 | projectDirPath = ""; 151 | projectRoot = ""; 152 | targets = ( 153 | BCA34ACF2938FE8400AA8362 /* BottomSheetExample */, 154 | ); 155 | }; 156 | /* End PBXProject section */ 157 | 158 | /* Begin PBXResourcesBuildPhase section */ 159 | BCA34ACE2938FE8400AA8362 /* Resources */ = { 160 | isa = PBXResourcesBuildPhase; 161 | buildActionMask = 2147483647; 162 | files = ( 163 | BCA34AE02938FE8500AA8362 /* LaunchScreen.storyboard in Resources */, 164 | BCA34ADD2938FE8500AA8362 /* Assets.xcassets in Resources */, 165 | BCA34ADB2938FE8400AA8362 /* Main.storyboard in Resources */, 166 | ); 167 | runOnlyForDeploymentPostprocessing = 0; 168 | }; 169 | /* End PBXResourcesBuildPhase section */ 170 | 171 | /* Begin PBXSourcesBuildPhase section */ 172 | BCA34ACC2938FE8400AA8362 /* Sources */ = { 173 | isa = PBXSourcesBuildPhase; 174 | buildActionMask = 2147483647; 175 | files = ( 176 | BCA34AF2293A5ECD00AA8362 /* ExampleViewController.swift in Sources */, 177 | BCA34AD82938FE8400AA8362 /* ViewController.swift in Sources */, 178 | BCA34AD42938FE8400AA8362 /* AppDelegate.swift in Sources */, 179 | ); 180 | runOnlyForDeploymentPostprocessing = 0; 181 | }; 182 | /* End PBXSourcesBuildPhase section */ 183 | 184 | /* Begin PBXVariantGroup section */ 185 | BCA34AD92938FE8400AA8362 /* Main.storyboard */ = { 186 | isa = PBXVariantGroup; 187 | children = ( 188 | BCA34ADA2938FE8400AA8362 /* Base */, 189 | ); 190 | name = Main.storyboard; 191 | sourceTree = ""; 192 | }; 193 | BCA34ADE2938FE8500AA8362 /* LaunchScreen.storyboard */ = { 194 | isa = PBXVariantGroup; 195 | children = ( 196 | BCA34ADF2938FE8500AA8362 /* Base */, 197 | ); 198 | name = LaunchScreen.storyboard; 199 | sourceTree = ""; 200 | }; 201 | /* End PBXVariantGroup section */ 202 | 203 | /* Begin XCBuildConfiguration section */ 204 | BCA34AE22938FE8500AA8362 /* Debug */ = { 205 | isa = XCBuildConfiguration; 206 | buildSettings = { 207 | ALWAYS_SEARCH_USER_PATHS = NO; 208 | CLANG_ANALYZER_NONNULL = YES; 209 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 210 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; 211 | CLANG_ENABLE_MODULES = YES; 212 | CLANG_ENABLE_OBJC_ARC = YES; 213 | CLANG_ENABLE_OBJC_WEAK = YES; 214 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 215 | CLANG_WARN_BOOL_CONVERSION = YES; 216 | CLANG_WARN_COMMA = YES; 217 | CLANG_WARN_CONSTANT_CONVERSION = YES; 218 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 219 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 220 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 221 | CLANG_WARN_EMPTY_BODY = YES; 222 | CLANG_WARN_ENUM_CONVERSION = YES; 223 | CLANG_WARN_INFINITE_RECURSION = YES; 224 | CLANG_WARN_INT_CONVERSION = YES; 225 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 226 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 227 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 228 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 229 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 230 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 231 | CLANG_WARN_STRICT_PROTOTYPES = YES; 232 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 233 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 234 | CLANG_WARN_UNREACHABLE_CODE = YES; 235 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 236 | COPY_PHASE_STRIP = NO; 237 | DEBUG_INFORMATION_FORMAT = dwarf; 238 | ENABLE_STRICT_OBJC_MSGSEND = YES; 239 | ENABLE_TESTABILITY = YES; 240 | GCC_C_LANGUAGE_STANDARD = gnu11; 241 | GCC_DYNAMIC_NO_PIC = NO; 242 | GCC_NO_COMMON_BLOCKS = YES; 243 | GCC_OPTIMIZATION_LEVEL = 0; 244 | GCC_PREPROCESSOR_DEFINITIONS = ( 245 | "DEBUG=1", 246 | "$(inherited)", 247 | ); 248 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 249 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 250 | GCC_WARN_UNDECLARED_SELECTOR = YES; 251 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 252 | GCC_WARN_UNUSED_FUNCTION = YES; 253 | GCC_WARN_UNUSED_VARIABLE = YES; 254 | IPHONEOS_DEPLOYMENT_TARGET = 13.0; 255 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 256 | MTL_FAST_MATH = YES; 257 | ONLY_ACTIVE_ARCH = YES; 258 | SDKROOT = iphoneos; 259 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 260 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 261 | }; 262 | name = Debug; 263 | }; 264 | BCA34AE32938FE8500AA8362 /* Release */ = { 265 | isa = XCBuildConfiguration; 266 | buildSettings = { 267 | ALWAYS_SEARCH_USER_PATHS = NO; 268 | CLANG_ANALYZER_NONNULL = YES; 269 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 270 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; 271 | CLANG_ENABLE_MODULES = YES; 272 | CLANG_ENABLE_OBJC_ARC = YES; 273 | CLANG_ENABLE_OBJC_WEAK = YES; 274 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 275 | CLANG_WARN_BOOL_CONVERSION = YES; 276 | CLANG_WARN_COMMA = YES; 277 | CLANG_WARN_CONSTANT_CONVERSION = YES; 278 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 279 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 280 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 281 | CLANG_WARN_EMPTY_BODY = YES; 282 | CLANG_WARN_ENUM_CONVERSION = YES; 283 | CLANG_WARN_INFINITE_RECURSION = YES; 284 | CLANG_WARN_INT_CONVERSION = YES; 285 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 286 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 287 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 288 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 289 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 290 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 291 | CLANG_WARN_STRICT_PROTOTYPES = YES; 292 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 293 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 294 | CLANG_WARN_UNREACHABLE_CODE = YES; 295 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 296 | COPY_PHASE_STRIP = NO; 297 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 298 | ENABLE_NS_ASSERTIONS = NO; 299 | ENABLE_STRICT_OBJC_MSGSEND = YES; 300 | GCC_C_LANGUAGE_STANDARD = gnu11; 301 | GCC_NO_COMMON_BLOCKS = YES; 302 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 303 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 304 | GCC_WARN_UNDECLARED_SELECTOR = YES; 305 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 306 | GCC_WARN_UNUSED_FUNCTION = YES; 307 | GCC_WARN_UNUSED_VARIABLE = YES; 308 | IPHONEOS_DEPLOYMENT_TARGET = 13.0; 309 | MTL_ENABLE_DEBUG_INFO = NO; 310 | MTL_FAST_MATH = YES; 311 | SDKROOT = iphoneos; 312 | SWIFT_COMPILATION_MODE = wholemodule; 313 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 314 | VALIDATE_PRODUCT = YES; 315 | }; 316 | name = Release; 317 | }; 318 | BCA34AE52938FE8500AA8362 /* Debug */ = { 319 | isa = XCBuildConfiguration; 320 | buildSettings = { 321 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 322 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 323 | CODE_SIGN_STYLE = Automatic; 324 | CURRENT_PROJECT_VERSION = 1; 325 | GENERATE_INFOPLIST_FILE = YES; 326 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 327 | INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; 328 | INFOPLIST_KEY_UIMainStoryboardFile = Main; 329 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 330 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 331 | LD_RUNPATH_SEARCH_PATHS = ( 332 | "$(inherited)", 333 | "@executable_path/Frameworks", 334 | ); 335 | MARKETING_VERSION = 1.0; 336 | PRODUCT_BUNDLE_IDENTIFIER = com.test.bottomSheet.BottomSheetExample; 337 | PRODUCT_NAME = "$(TARGET_NAME)"; 338 | SWIFT_EMIT_LOC_STRINGS = YES; 339 | SWIFT_VERSION = 5.0; 340 | TARGETED_DEVICE_FAMILY = "1,2"; 341 | }; 342 | name = Debug; 343 | }; 344 | BCA34AE62938FE8500AA8362 /* Release */ = { 345 | isa = XCBuildConfiguration; 346 | buildSettings = { 347 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 348 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 349 | CODE_SIGN_STYLE = Automatic; 350 | CURRENT_PROJECT_VERSION = 1; 351 | GENERATE_INFOPLIST_FILE = YES; 352 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 353 | INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; 354 | INFOPLIST_KEY_UIMainStoryboardFile = Main; 355 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 356 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 357 | LD_RUNPATH_SEARCH_PATHS = ( 358 | "$(inherited)", 359 | "@executable_path/Frameworks", 360 | ); 361 | MARKETING_VERSION = 1.0; 362 | PRODUCT_BUNDLE_IDENTIFIER = com.test.bottomSheet.BottomSheetExample; 363 | PRODUCT_NAME = "$(TARGET_NAME)"; 364 | SWIFT_EMIT_LOC_STRINGS = YES; 365 | SWIFT_VERSION = 5.0; 366 | TARGETED_DEVICE_FAMILY = "1,2"; 367 | }; 368 | name = Release; 369 | }; 370 | /* End XCBuildConfiguration section */ 371 | 372 | /* Begin XCConfigurationList section */ 373 | BCA34ACB2938FE8400AA8362 /* Build configuration list for PBXProject "BottomSheetExample" */ = { 374 | isa = XCConfigurationList; 375 | buildConfigurations = ( 376 | BCA34AE22938FE8500AA8362 /* Debug */, 377 | BCA34AE32938FE8500AA8362 /* Release */, 378 | ); 379 | defaultConfigurationIsVisible = 0; 380 | defaultConfigurationName = Release; 381 | }; 382 | BCA34AE42938FE8500AA8362 /* Build configuration list for PBXNativeTarget "BottomSheetExample" */ = { 383 | isa = XCConfigurationList; 384 | buildConfigurations = ( 385 | BCA34AE52938FE8500AA8362 /* Debug */, 386 | BCA34AE62938FE8500AA8362 /* Release */, 387 | ); 388 | defaultConfigurationIsVisible = 0; 389 | defaultConfigurationName = Release; 390 | }; 391 | /* End XCConfigurationList section */ 392 | 393 | /* Begin XCRemoteSwiftPackageReference section */ 394 | BCA34AF3293E469D00AA8362 /* XCRemoteSwiftPackageReference "SnapKit" */ = { 395 | isa = XCRemoteSwiftPackageReference; 396 | repositoryURL = "https://github.com/SnapKit/SnapKit.git"; 397 | requirement = { 398 | kind = upToNextMajorVersion; 399 | minimumVersion = 5.0.0; 400 | }; 401 | }; 402 | /* End XCRemoteSwiftPackageReference section */ 403 | 404 | /* Begin XCSwiftPackageProductDependency section */ 405 | BC46DDDE29747CFA0012DDF2 /* BottomSheet */ = { 406 | isa = XCSwiftPackageProductDependency; 407 | productName = BottomSheet; 408 | }; 409 | BCA34AF4293E469D00AA8362 /* SnapKit */ = { 410 | isa = XCSwiftPackageProductDependency; 411 | package = BCA34AF3293E469D00AA8362 /* XCRemoteSwiftPackageReference "SnapKit" */; 412 | productName = SnapKit; 413 | }; 414 | /* End XCSwiftPackageProductDependency section */ 415 | }; 416 | rootObject = BCA34AC82938FE8400AA8362 /* Project object */; 417 | } 418 | --------------------------------------------------------------------------------