├── .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 | 
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 |
--------------------------------------------------------------------------------