PageSheet - Customizable sheets using UISheetPresentationController in SwiftUI

Overview

PageSheet

Customizable sheet presentations in SwiftUI. Using UISheetPresentationController under the hood.

Features

  • Uses the default sheet API under the hood, ensuring maximum compatibility & stability.
  • Exposes the exact same API as the default SwiftUI sheet implementation.
  • No hacks, follows the best practices for creating represetable views in SwiftUI.
  • Configurable using view modifiers, can configure UISheetPresentationController from any child views in the presented sheet's content view.
  • Works with the interactiveDismissDisabled(_:Bool) modifier.
  • Exposes all of the UISheetPresentationController configuration options.
  • Track the currently selected detent using an Environment value.
  • Well documented API, following a similar approach to the Developer Documentation.
  • Small footprint, weighing only ~38.1kB when installed via SwiftPM.

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/PageSheet and tap Next. Select the PageSheet 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/PageSheet.git", .upToNextMajor(from: "1.0.0"))

Next, add PageSheet as a dependency for your targets:

.target(name: "AppTarget", dependencies: ["PageSheet"])

A completed example may look like this:

// swift-tools-version:5.5

import PackageDescription

let package = Package(
    name: "App",
    dependencies: [
        .package(
          url: "https://github.com/ericlewis/PageSheet.git", 
          .upToNextMajor(from: "1.0.0"))
    ],
    targets: [
        .target(
          name: "AppTarget", 
          dependencies: ["PageSheet"])
    ]
)

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

PageSheet works similarly to a typical sheet view modifier.

import SwiftUI
import PageSheet

struct ContentView: View {
  @State
  private var sheetPresented = false
  
  var body: some View {
    Button("Open Sheet") {
      sheetPresented = true
    }
    .pageSheet(isPresented: $sheetPresented) {
      Text("Hello!")
    }
  }
}

PageSheet also supports presentation via conditional Identifiable objects.

import SwiftUI
import PageSheet

struct ContentView: View {
  @State
  private var string: String?
  
  var body: some View {
    Button("Open Sheet") {
      string = "Hello!"
    }
    .pageSheet(item: $string) { unwrappedString in
      Text(unwrappedString)
    }
  }
}

extension String: Identifiable {
  public var id: String { self }
}

Customization

PageSheet can also be customized using a collection of view modifiers applied to the sheet's content.

import SwiftUI
import PageSheet

struct ContentView: View {
  @State
  private var sheetPresented = false
  
  var body: some View {
    Button("Open Sheet") {
      sheetPresented = true
    }
    .pageSheet(isPresented: $sheetPresented) {
      Text("Hello!")
        .preferGrabberVisible(true)
    }
  }
}

Documentation

Presentation Modifiers

These modifiers behave exactly the same way as the sheet presentation modifiers in SwiftUI.


func pageSheet<Content: View>(isPresented: Binding<Bool>, onDismiss: (() -> Void)? = nil, content: @escaping () -> Content) -> some View

Presents a configurable page sheet when a binding to a Boolean value that you provide is true.

Use this method when you want to present a configurable sheet view to the user when a Boolean value 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.

func pageSheet<Item: Identifiable, Content: View>(item: Binding<Item?>, onDismiss: (() -> Void)? = nil, content: @escaping () -> Content) -> some View

Presents a sheet using the given item as a data source for the sheet’s content.

Use this method when you need to present a customizable sheet view with content from a custom data source.

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.

Presentation Customization Modifiers

These modifiers only take effect when the modified view is inside of and visible within a presented PageSheet.

Note: You can apply these modifiers to any view in the sheet’s view hierarchy.

func preferGrabberVisible(_ isVisible: Bool) -> some View

Sets a Boolean value that determines whether the presenting sheet shows a grabber at the top.

The default value is false, which means the sheet doesn't show a grabber. A grabber is a visual affordance that indicates that a sheet is resizable. Showing a grabber may be useful when it isn't apparent that a sheet can resize or when the sheet can't dismiss interactively.

Set this value to true for the system to draw a grabber in the standard system-defined location. The system automatically hides the grabber at appropriate times, like when the sheet is full screen in a compact-height size class or when another sheet presents on top of it.

Parameters:

  • isVisible: Default value is false, set to true to display grabber.

Returns:

  • A view that wraps this view and sets the presenting sheet's grabber visiblity.

func detents(_ detents: PageSheet.Detents) -> some View

Sets an array of heights where the presenting sheet can rest.

The default value is an array that contains the value large(). The array must contain at least one element. When you set this value, specify detents in order from smallest to largest height.

Parameters:

  • detents: The default value is an array that contains the value large().

Returns:

  • A view that wraps this view and sets the presenting sheet's UISheetPresentationController/detents.

func largestUndimmedDetent(id identifier: PageSheet.Detent.Identifier?) -> some View

Sets the largest detent that doesn’t dim the view underneath the presenting sheet.

The default value is nil, which means the system adds a noninteractive dimming view underneath the sheet at all detents. Set this property to only add the dimming view at detents larger than the detent you specify. For example, set this property to medium to add the dimming view at the large detent.

Without a dimming view, the undimmed area around the sheet responds to user interaction, allowing for a nonmodal experience. You can use this behavior for sheets with interactive content underneath them.

Parameters:

  • id: An optional PageSheet.Detent.Identifier value, the default is nil.

Returns:

  • A view that wraps this view and sets the presenting sheet's largest undimmed Detent identifier.

func selectedDetent(id identifier: PageSheet.Detent.Identifier?) -> some View

Sets the identifier of the most recently selected detent on the presenting sheet.

This property represents the most recent detent that the user selects or that you set programmatically. The default value is nil, which means the sheet displays at the smallest detent you specify in detents.

Parameters:

  • id: An optional PageSheet.Detent.Identifier value, the default is nil.

Returns:

  • A view that wraps this view and sets the presenting sheet's selected Detent identifier.

func preferEdgeAttachedInCompactHeight(_ preference: Bool) -> some View

Sets a Boolean value that determines whether the presenting sheet attaches to the bottom edge of the screen in a compact-height size class.

The default value is false, which means the sheet defaults to a full screen appearance at compact height. Set this value to true to use an alternate appearance in a compact-height size class, causing the sheet to only attach to the screen on its bottom edge.

Parameters:

  • preference: Default value is false.

Returns:

  • A view that wraps this view and sets the presenting sheet's prefersEdgeAttachedInCompactHeight property.

func widthFollowsPreferredContentSizeWhenEdgeAttached(_ preference: Bool) -> some View

Sets a Boolean value that determines whether the presenting sheet's width matches its view's preferred content size.

The default value is false, which means the sheet's width equals the width of its container's safe area. Set this value to true to use your view controller's preferredContentSize to determine the width of the sheet instead.

This property doesn't have an effect when the sheet is in a compact-width and regular-height size class, or when prefersEdgeAttachedInCompactHeight is false.

Parameters:

  • preference: Default value is false.

Returns:

  • A view that wraps this view and sets the presenting sheet's prefersEdgeAttachedInCompactHeight property.

func preferScrollingExpandsWhenScrolledToEdge(_ preference: Bool) -> some View

Sets a Boolean value that determines whether scrolling expands the presenting sheet to a larger detent.

The default value is true, which means if the sheet can expand to a larger detent than selectedDetentIdentifier, scrolling up in the sheet increases its detent instead of scrolling the sheet's content. After the sheet reaches its largest detent, scrolling begins.

Set this value to false if you want to avoid letting a scroll gesture expand the sheet. For example, you can set this value on a nonmodal sheet to avoid obscuring the content underneath the sheet.

Parameters:

  • preference: Default value is true.

Returns:

  • A view that wraps this view and sets the presenting sheet's prefersScrollingExpandsWhenScrolledToEdge property.

func preferredSheetCornerRadius(_ cornerRadius: CGFloat?) -> some View

Sets the preferred corner radius on the presenting sheet.

The default value is nil. This property only has an effect when the presenting sheet is at the front of its sheet stack.

Parameters:

  • preference: Default value is nil.

Returns:

  • A view that wraps this view and sets the presenting sheet's cornerRadius.

PageSheetView

A SwiftUI wrapper view for presentation controllers that manages the appearance and behavior of a sheet.

This view makes it easier to embed PageSheetView in custom navigation solutions such as FlowStacks and is meant to be presented using a sheet modifier. Other ways of presenting may not work and are not officially supported.

Example
import SwiftUI
import SheetPage

struct ContentView: View {
  @State
  private var sheetPresented = false
  
  var body: some View {
    VStack {
      Button("Present Sheet") {
        sheetPresented = true
      }
    }
    .sheet(isPresented: $sheetPresented) {
      SheetPageView {
        Text("Hello World!")
          .preferGrabberVisible(true)
      }
    }
  }
}

Parameters:

  • content: A closure that returns the content of the sheet.

Returns:

  • A presentation controller wrapped SwiftUI view.

License

PageSheet is released under the MIT license. See LICENSE for details.

You might also like...
A simple, customizable popup dialog for iOS written in Swift. Replaces UIAlertController alert style.
A simple, customizable popup dialog for iOS written in Swift. Replaces UIAlertController alert style.

Introduction Popup Dialog is a simple, customizable popup dialog written in Swift. Features Easy to use API with hardly any boilerplate code Convenien

Fully customizable and extensible action sheet controller written in Swift
Fully customizable and extensible action sheet controller written in Swift

XLActionController By XMARTLABS. XLActionController is an extensible library to quickly create any custom action sheet controller. Examples The action

A customizable, full-feature, lightweight iOS framework to be used instead of UIAlertController.
A customizable, full-feature, lightweight iOS framework to be used instead of UIAlertController.

A customizable, full-feature, lightweight iOS framework to be used instead of UIAlertController.

💌 Easy to use and customizable messages/notifications for iOS à la Tweetbot

Notice: TSMessages is no longer being maintained/updated. We recommend everyone migrate to RMessage. This repository will be kept as is for those who

[iOS] Easy, customizable notifications displayed on top of the statusbar. With progress and activity. iPhone X ready.
[iOS] Easy, customizable notifications displayed on top of the statusbar. With progress and activity. iPhone X ready.

JDStatusBarNotification Show messages on top of the status bar. Customizable colors, font and animation. Supports progress display and can show an act

PMAlertController is a great and customizable alert that can substitute UIAlertController
PMAlertController is a great and customizable alert that can substitute UIAlertController

PMAlertController is a small library that allows you to substitute Apple's uncustomizable UIAlertController, with a beautiful and totally customizable

FCAlertView is a Flat Customizable AlertView for iOS (Written in Objective C)
FCAlertView is a Flat Customizable AlertView for iOS (Written in Objective C)

FCAlertView FCAlertView is a Flat Customizable AlertView, written in Objective C Quick Links 1. Swift 2. Installation 3. Example App 4. Adding FCAlert

A customizable framework to create draggable views
A customizable framework to create draggable views

CFNotify CFNotify is written in Swift. Using UIKit Dynamics as animator. It can make ANY UIView object draggable and throwable. This library mainly us

Customizable simple Alert and simple ActionSheet for Swift
Customizable simple Alert and simple ActionSheet for Swift

SimpleAlert It is simple and easily customizable alert. Can be used as UIAlertController. Appetize's Demo Requirements Swift 5.0 iOS 9.0 or later How

Comments
  • Improve ergonomics

    Improve ergonomics

    It's hard to remember all of the configuration methods. Sometimes they aren't so obvious in isolation (what is preferring a grabber visibility deep in this component hierarchy?). It's hard to discover the apis for quick configuration. I am leaning towards something similar to environment(_, _). Like this:

    Text("hello")
      .sheet(\.prefersGrabberVisible, true)
      .sheet(\.detents, [.medium()])
    

    The sheet method name makes sense, but other ideas should be considered as well:

    • .presentation(...)
    • .pageSheet(...)
    • .presentationPreference(...)
    • .sheetPreference(...)

    There isn't really much prior art around organized interaction with Preferences aside from creating bespoke methods like environment interactions.

    I will note that using preferences the KV way doesn't make it completely obvious that we are moving upwards like a typical preference setting might. Likely important to keep preference somewhere in the signature.

    Another useful tool may be a property wrapper to be able to read from preferences:

    @Preference(\.pref)
    var pref 
    

    This opens the potential for simplifying the entire configuration pipeline.

    Food for though 🍎

    enhancement help wanted 
    opened by ericlewis 1
  • chore: mark direct view modifiers as deprecated

    chore: mark direct view modifiers as deprecated

    Since we introduced consolidation & discoverability with sheetPreference(_:) there is no longer a good reason to keep the original view modifiers.

    opened by ericlewis 0
  • Programmatically setting detent results in clipped view.

    Programmatically setting detent results in clipped view.

    Programmatically changing from a large detent to a medium detent causes the content view to resize to the end height, resulting in a white box as the controller moves downward to its new size.

    bug 
    opened by ericlewis 0
Releases(1.2.5)
  • 1.2.5(Feb 2, 2022)

  • 1.2.4(Feb 2, 2022)

    Full Changelog: https://github.com/ericlewis/PageSheet/compare/1.2.3...1.2.4

    • Splits packages into PageSheetCore and PageSheetPlus, with PageSheet being a combo of both.
    Source code(tar.gz)
    Source code(zip)
  • 1.2.3(Feb 2, 2022)

  • 1.2.2(Feb 2, 2022)

  • 1.2.1(Feb 2, 2022)

  • 1.2.0(Feb 2, 2022)

    What's Changed

    • Update issue templates by @ericlewis in https://github.com/ericlewis/PageSheet/pull/16
    • Create CODE_OF_CONDUCT.md by @ericlewis in https://github.com/ericlewis/PageSheet/pull/15
    • Create CONTRIBUTING.md by @ericlewis in https://github.com/ericlewis/PageSheet/pull/17
    • perf: use inlinable on customization modifiers by @ericlewis in https://github.com/ericlewis/PageSheet/pull/18
    • feat: sheetPreference(_:) view modifier. by @ericlewis in https://github.com/ericlewis/PageSheet/pull/19
    • chore: mark direct view modifiers as deprecated by @ericlewis in https://github.com/ericlewis/PageSheet/pull/20

    Full Changelog: https://github.com/ericlewis/PageSheet/compare/1.1.5...1.2.0

    Source code(tar.gz)
    Source code(zip)
  • 1.1.5(Feb 1, 2022)

    What's Changed

    • fix: #12 by @ericlewis in https://github.com/ericlewis/PageSheet/pull/13

    Full Changelog: https://github.com/ericlewis/PageSheet/compare/1.1.4...1.1.5

    Source code(tar.gz)
    Source code(zip)
  • 1.1.4(Feb 1, 2022)

    What's Changed

    • fix: #8 by @ericlewis in https://github.com/ericlewis/PageSheet/pull/10

    Full Changelog: https://github.com/ericlewis/PageSheet/compare/1.1.3...1.1.4

    Source code(tar.gz)
    Source code(zip)
  • 1.1.3(Feb 1, 2022)

    What's Changed

    • fix: flickering when throwing view by @ericlewis in https://github.com/ericlewis/PageSheet/pull/7

    Full Changelog: https://github.com/ericlewis/PageSheet/compare/1.1.2...1.1.3

    Source code(tar.gz)
    Source code(zip)
  • 1.1.2(Feb 1, 2022)

    What's Changed

    • fix: The selectedDetentIdentifier value in Environment may not up… by @ericlewis in https://github.com/ericlewis/PageSheet/pull/6

    Full Changelog: https://github.com/ericlewis/PageSheet/compare/1.1.1...1.1.2

    Source code(tar.gz)
    Source code(zip)
  • 1.1.1(Jan 31, 2022)

  • 1.1.0(Jan 31, 2022)

    What's Changed

    • chore: move internals around by @ericlewis in https://github.com/ericlewis/PageSheet/pull/4
    • feat: add PageSheetView by @ericlewis in https://github.com/ericlewis/PageSheet/pull/5

    Full Changelog: https://github.com/ericlewis/PageSheet/compare/1.0.0...1.1.0

    Source code(tar.gz)
    Source code(zip)
  • 1.0.2(Jan 31, 2022)

  • 1.0.1(Jan 31, 2022)

  • 1.0.0(Jan 31, 2022)

    What's Changed

    • feat: v1 by @ericlewis in https://github.com/ericlewis/BottomSheetView/pull/3

    Full Changelog: https://github.com/ericlewis/BottomSheetView/compare/0.3.0...1.0.0

    Source code(tar.gz)
    Source code(zip)
  • 0.3.0(Jan 31, 2022)

    What's Changed

    • feat: selected detent id modifier by @ericlewis in https://github.com/ericlewis/BottomSheetView/pull/2

    Full Changelog: https://github.com/ericlewis/BottomSheetView/compare/0.2.0...0.3.0

    Source code(tar.gz)
    Source code(zip)
  • 0.2.0(Jan 30, 2022)

    What's Changed

    • feat: selectedDetentIdentifier environment value by @ericlewis in https://github.com/ericlewis/BottomSheetView/pull/1

    New Contributors

    • @ericlewis made their first contribution in https://github.com/ericlewis/BottomSheetView/pull/1

    Full Changelog: https://github.com/ericlewis/BottomSheetView/compare/0.1.0...0.2.0

    Source code(tar.gz)
    Source code(zip)
  • 0.1.0(Jan 30, 2022)

Owner
Eric Lewis
Developer & Father. Feel free to reach out on twitter if I don't get back to you here.
Eric Lewis
BottomSheet makes it easy to add custom bottom sheets to your SwiftUI apps.

BottomSheet About BottomSheet BottomSheet makes it easy to add custom bottom sheets to your SwiftUI apps. The result can look like this...or completel

Daniel Saidi 174 Jan 2, 2023
an extension library for SwiftUI sheets.

SheetKit SheetKit is an extension library for SwiftUI sheets. 中文版说明 with Picture What is SheetKit SheetKit is a library of extensions for SwiftUI moda

东坡肘子 65 Dec 31, 2022
SwiftUI native-like onboarding sheets

Welcome Sheet Welcome sheet for swiftUI enables incredibly easy way for creating onboarding screens, update notes, or whatever you imagine! The main i

Jakub Florek 43 Dec 29, 2022
It is a highly configurable iOS library which allows easy styling with built in styles as well as extra header and footer views so that you can make extremely unique alerts and action sheets.

 CFAlertViewController CFAlertViewController is a library that helps you display and customise Alerts, Action Sheets, and Notifications on iPad and i

Crowdfire Inc. 1.1k Dec 18, 2022
Share-sheet-example - A sample project that reproduces an issue with Share Sheets

Hello, DTS! This project demonstrates the issue I'm having with the Share Sheet.

Marcos Tanaka 0 Feb 11, 2022
A SwiftUI Partial Sheet fully customizable with dynamic height

A SwiftUI Partial Sheet fully customizable with dynamic height

Andrea Miotto 1.4k Jan 5, 2023
Highly customizable alertview and alert/notification/success/error/alarm popup written in Swift

CDAlertView is highly customizable alert popup written in Swift. Usage is similar to UIAlertController. Screenshots Animations Usage Basic usage witho

Candost Dagdeviren 1.1k Dec 30, 2022
A customizable framework to create draggable views

CFNotify CFNotify is written in Swift. Using UIKit Dynamics as animator. It can make ANY UIView object draggable and throwable. This library mainly us

Johnny Tsoi 491 Nov 20, 2022
The easiest way to display highly customizable in app notification banners in iOS

Written in Swift 5 NotificationBanner is an extremely customizable and lightweight library that makes the task of displaying in app notification banne

Dalton Hinterscher 4.5k Jan 9, 2023
PMAlertController is a great and customizable alert that can substitute UIAlertController

PMAlertController is a small library that allows you to substitute Apple's uncustomizable UIAlertController, with a beautiful and totally customizable

Paolo Musolino 2.5k Jan 3, 2023