BottomSheetView
Present sheets with UISheetPresentationController
in SwiftUI.
Table of Contents
Requirements
The codebase supports iOS and requires Xcode 12.0 or newer
Installation
Xcode
Open your project. Navigate to File > Swift Packages > Add Package Dependency
. Enter the url https://github.com/ericlewis/BottomSheetView
and tap Next
.
Select the BottomSheetView
target and press Add Package
.
Swift Package Manager
Add the following line to the dependencies
in your Package.swift
file:
.package(url: "https://github.com/ericlewis/BottomSheetView.git", .upToNextMajor(from: "0.2.0"))
Next, add BottomSheetView
as a dependency for your targets:
.target(name: "AppTarget", dependencies: ["BottomSheetView"])
A completed example may look like this:
// swift-tools-version:5.5
import PackageDescription
let package = Package(
name: "ExampleApp",
dependencies: [
.package(
url: "https://github.com/ericlewis/BottomSheetView.git",
.upToNextMajor(from: "0.2.0"))
],
targets: [
.target(
name: "ExampleAppTarget",
dependencies: ["BottomSheetView"])
]
)
Examples
Example Project
If you are using Xcode 13.2.1 you can navigate to the Example
folder and open the enclosed Swift App Playground to test various features (and see how they are implemented).
Presentation
BottomSheetView
works similarly to a typical sheet
view modifier.
import SwiftUI
import BottomSheetView
struct ContentView: View {
@State
private var sheetPresented = false
var body: some View {
Button("Open Sheet") {
sheetPresented = true
}
.bottomSheet(isPresented: $sheetPresented) {
Text("Hello!")
}
}
}
BottomSheetView
also supports presentation via conditional Identifiable
objects.
import SwiftUI
import BottomSheetView
struct ContentView: View {
@State
private var string: String?
var body: some View {
Button("Open Sheet") {
string = "Hello!"
}
.bottomSheet(item: $string) { unwrappedString in
Text(unwrappedString)
}
}
}
extension String: Identifiable {
public var id: String { self }
}
Customization
BottomSheetView
can also be customized using a collection of view modifiers applied to the bottom sheet’s content.
import SwiftUI
import BottomSheetView
struct ContentView: View {
@State
private var sheetPresented = false
var body: some View {
Button("Open Sheet") {
sheetPresented = true
}
.bottomSheet(isPresented: $sheetPresented) {
Text("Hello!")
.prefersGrabberVisible(true) // Always applied to children, similar to navigation views.
}
}
}
Documentation
Environment
You can access the current selectedDetentIdentifier
property via the SwiftUI Environment property wrapper.
...
@Environment(\.selectedDetentIdentifier)
var selectedDetent
Presentation
These are extensions on View
.
/// Presents a sheet using `UISheetPresentationController` when a binding to a
/// Boolean value that you provide is true.
///
/// - Parameters:
/// - isPresented: A binding to a Boolean value that determines whether
/// to present the sheet that you create in the modifier's
/// `content` closure.
/// - onDismiss: The closure to execute when dismissing the sheet.
/// - content: A closure that returns the content of the sheet.
public func bottomSheet<Content: View>(
isPresented: Binding<Bool>,
onDismiss: (() -> Void)? = nil,
@ViewBuilder content builder: @escaping () -> Content
) -> some View
/// Presents a sheet via `UISheetPresentationController` using the given
/// item as a data source for the sheet's content.
///
/// - Parameters:
/// - item: A binding to an optional source of truth for the sheet.
/// When `item` is non-`nil`, the system passes the item's content to
/// the modifier's closure. You display this content in a sheet that you
/// create that the system displays to the user. If `item` changes,
/// the system dismisses the sheet and replaces it with a new one
/// using the same process.
/// - onDismiss: The closure to execute when dismissing the sheet.
/// - content: A closure returning the content of the sheet.
public func bottomSheet<Item: Identifiable, Content: View>(
item: Binding<Item?>,
onDismiss: (() -> Void)? = nil,
@ViewBuilder content builder: @escaping (Item) -> Content
) -> some View
Styling & Behavior
These are extensions on View
, they apply preferences to BottomSheetView
. The ViewModifier
s themselves are not public.
/// The array of heights where a sheet can rest.
public func detents(_ detents: [UISheetPresentationController.Detent]) -> some View
/// The largest detent that doesn’t dim the view underneath the sheet.
public func largestUndimmedDetentIdentifier(_ id: UISheetPresentationController.Detent.Identifier?) -> some View
/// A Boolean value that determines whether scrolling expands the sheet to a larger detent.
public func prefersScrollingExpandsWhenScrolledToEdge(_ preference: Bool) -> some View
/// A Boolean value that determines whether the sheet shows a grabber at the top.
public func prefersGrabberVisible(_ preference: Bool) -> some View
/// A Boolean value that determines whether the sheet attaches to the bottom edge of the screen in a compact-height size class.
public func prefersEdgeAttachedInCompactHeight(_ preference: Bool) -> some View
/// A Boolean value that determines whether the sheet's width matches its view controller's preferred content size.
public func widthFollowsPreferredContentSizeWhenEdgeAttached(_ preference: Bool) -> some View
/// The corner radius that the sheet attempts to present with.
public func preferredCornerRadius(_ preference: CGFloat?) -> some View
/// Conditionally prevents interactive dismissal of a popover or a sheet.
public func dismissDisabled(_ preference: Bool) -> some View
Known Issues
- Largest undimmed detent changes seem to affect the dimming of accent color elements in parent views.
- Attempt to set a constant value for
item
orisPresented
results in the sheet not being presented. - Creating a bottom sheet & doing all layout work in the bottom sheet itself results in missing accent colors.
- Workaround: instead, create a new
View
and use that in.bottomSheet
- Workaround: instead, create a new
License
BottomSheetView is released under the MIT license. See LICENSE for details.