Half modal view for SwiftUI

Overview

ResizableSheet

ResizableSheeet is a half modal view library for SwiftUI. You can easily implement a half modal view.

Target

  • Swift5.5
  • iOS14+

Installation

Only SwiftPM

Features

  • 3 states are supported.
    • hidden
    • medium
    • large
  • The medium size is automatically calculated baesd on the content.
  • You can update view for each state.
  • ResizableSheet contains ResizableScrollView and TrackableScrollView.
    TrackableScrollView is a wrapper view of UIScrollView and the offset synchronizes with dragging of sheet.
    ResizableScroolView is a wrapper class of TrackableScrollView, and
  • ResizableSheet can be shonw on another ResizableSheet.

Simple Example

To use ResizableSheet, follow these steps.

  1. Create ResizableSheetCenter and embed it to your view in your root view like RootView.
struct RootView: View { 
    let windowScene: UIWindowScene?

    var resizableSheetCenter: ResizableSheetCenter? {
        windowScene.flatMap(ResizableSheetCenter.resolve(for:))
    }
    
    var body: some View { 
        YOUR_VIEW
            .environment(\.resizableSheetCenter, resizableSheetCenter)
    }
}
  1. Prepare ResizableSheetState with @State, and call resizableSheet. You can customize the resizableSheet by chaining some methods.
struct SomeView: View {

    @State var state: ResizableSheetState = .hidden

    var body: some View {
        Button("Show sheet") {
            state = .medium
        }
        .resizableSheet($state) { builder in
            builder.content { context in
                Text("text")
                    .padding()
            }
        }
    }
}

That's all!
You can show a half modal view by tapping the button "Show sheet", and you can expand or remove the sheet by dragging it.

View structure

ResizableSheet has some view components.
You can control each view components based on current status.

ResizableSheet
 └─ background
     ├─ outside
     └─ sheet background
         └─ content

Example

Complex Layout

You can update the view based on the current status.
The argument context has some informatios about the sheet, like state, view size, progress of dragging and diffY.

Based on the context, you can update the content.
Tips: Don't forget to add .allowsHitTesting(false) to Color view. If you don't add it, the dragging gesture is not recognized.

view.resizableSheet($state) { builder in
    builder.content { context in
        VStack {
            Text(context.state == .hidden ? "hidden" :
                    context.state == .medium ? "medium" : "large"
            )
            Color.gray
                .frame(height:
                        context.state == .medium ? max(0, context.diffY) :
                        context.state == .hidden ? 0 : nil
                )
                .opacity(context.state == .medium ? context.progress : 1.0 - abs(context.progress))
                .allowsHitTesting(false)
            Text("Buttom")
        }
        .padding()
    }
}

Supported status

ResizableSheet supports 3 statuses, .hidden, .medium and .large.
In default setting, the all statuses are supported, but you can stop to support any statuses.

view.resizableSheet($state) { builder in
    builder.content { context in
        Text("Text").frame(height: 300)
    }
    .supportedState([.medium])
}

Multi Sheets

ResizableSheet supports multiple sheets.
By adding id, ResizableSheet can show multiple sheets.

struct SomeSheet: View {

    @State var stateA: ResizableSheetState = .hidden
    @State var stateB: ResizableSheetState = .hidden

    var body: some View {
        Button("Show sheet A") {
            stateA = .medium
        }
        .resizableSheet($stateA, id: "A") { builder in
            builder.content { context in
                Button("Show sheet B") {
                    stateB = .medium
                }.frame(height: 300)
            }
        }
        .resizableSheet($stateB, id: "B") { builder in
            builder.content { context in
                Button("remove all sheet") {
                    stateA = .hidden
                    stateB = .hidden
                }.frame(height: 200)
            }
        }
    }
}

ResizableScroolView (TrackableScrollView)

ResizableSheet includes ResizableScrollView.
The view synchronises the offset with ResizableSheet.
Tips: Using ResizableScroolView is recommended because you don't need to calculate the medium size.

view.resizableSheet($state) { builder in
    builder.content { context in
        ResizableScrollView(context: context) {
            // These views are shown in medium size and large size.
            ForEach(0..<5) { index in
                Text("\(index)")
                    .padding()
            }
        } additional: {
            // These views are shown in only large size.
            ForEach(5..<100) { index in
                Text("\(index)")
                    .padding()
            }
        }
    }
}

EmptyBackground

By passing EmptyView as background, user can control both the parent view and the sheet.

struct SomeView: View {
    @State var counter = 0
    @State var state: ResizableSheetState = .hidden
    var body: some View {
        VStack {
            Text("\(counter)")
                .font(.largeTitle)
            Button("count") {
                counter += 1
            }
            Spacer()
            Button("Show sheet") {
                state = .medium
            }
            Spacer()
        }
        .resizableSheet($state) { builder in
            builder.content { context in
                Content(counter: $counter).frame(height: 300)
            }
            .background { _ in EmptyView() } // add this line
        }
    }

    struct Content: View {
        @Binding var counter: Int
        var body: some View {
            VStack {
                Spacer()
                Text("\(counter)")
                    .font(.largeTitle)
                Button("reset") {
                    counter = 0
                }
                Spacer()
            }
        }
    }
}

You might also like...
What's New In SwiftUI for iOS 16 - Xcode 14 -  SwiftUI 4.0
What's New In SwiftUI for iOS 16 - Xcode 14 - SwiftUI 4.0

SwiftUI4 What's New In SwiftUI for iOS 16 - Xcode 14 - SwiftUI 4.0 (Work in progress....) Swift Charts Presentation Detents(Half Sheet & Small Sheets)

LayoutKit is a fast view layout library for iOS, macOS, and tvOS.
LayoutKit is a fast view layout library for iOS, macOS, and tvOS.

🚨 UNMAINTAINED 🚨 This project is no longer used by LinkedIn and is currently unmaintained. LayoutKit is a fast view layout library for iOS, macOS, a

Programmatic view layout for the rest of us.
Programmatic view layout for the rest of us.

Façade Important Facade is no longer under active development, and as such if you create any issues or submit pull requests, it's not very likely to b

LayoutKit is a fast view layout library for iOS, macOS, and tvOS.
LayoutKit is a fast view layout library for iOS, macOS, and tvOS.

🚨 UNMAINTAINED 🚨 This project is no longer used by LinkedIn and is currently unmaintained. LayoutKit is a fast view layout library for iOS, macOS, a

MyLayout is a simple and easy objective-c framework for iOS view layout
MyLayout is a simple and easy objective-c framework for iOS view layout

MyLayout is a powerful iOS UI framework implemented by Objective-C. It integrates the functions with Android Layout,iOS AutoLayout,SizeClass, HTML CSS float and flexbox and bootstrap. So you can use LinearLayout,RelativeLayout,FrameLayout,TableLayout,FlowLayout,FloatLayout,PathLayout,GridLayout,LayoutSizeClass to build your App 自动布局 UIView UITableView UICollectionView RTL

Circle Loading View Pod

CircleLoadingViewPod Example To run the example project, clone the repo, and run pod install from the Example directory first. Requirements Installati

TicTacToe Game Collection View With Swift

TicTacToe---Collection-View Game Rules A game will consist of a sequence of the following actions: Initially, the "X" marks will play first (we call h

A Code challenge I solved leveraging a lot on Composite collection view layout written in swift

AsthmApp Mobile app designed as a support aid for people with Asthma Accounts Google and Firebase [email protected] dICerytiMPSI Facebook asthmp.ap

A Code challenge I solved leveraging a lot on Composite collection view layout...written in swift

Space44 Code Challenge Space44 Code Challenge iOS application for Space 44 hiring process, it leverages on Image download and composite collection vie

Comments
  • Sheet background does not respect app's theme

    Sheet background does not respect app's theme

    Hola. Thanks for provide the community with such an elegant sheet. No major issues, just something I've noticed.

    The sheet's background changes when the system's theme changes: dark mode gives the sheet a dark color and light mode... white sheet. Apps can specify a theme in which the sheet does not respect. An example how an app could change the theme:

    class Utilities: ObservableObject {
    
        // The default is to use the system's default.
        @AppStorage("theme") var theme: String = ""
        
        var userInterfaceStyle: ColorScheme? = .dark
    
        func overrideDisplayMode() {
            var userInterfaceStyle: UIUserInterfaceStyle
    
            if theme == "On" {
                userInterfaceStyle = .dark
            } else if theme == "Off" {
                userInterfaceStyle = .light
            } else {
                userInterfaceStyle = .unspecified
            }
            
            let scenes = UIApplication.shared.connectedScenes
            let windowScene = scenes.first as? UIWindowScene
            let window = windowScene?.windows.first
            
            window?.overrideUserInterfaceStyle = userInterfaceStyle
        }
    }
    

    Should the system is set to dark mode and the app to light, the content sheet's background is still dark. Ok I could use the .sheetBackground modifier you've provided us, with a combination with @Environment(\.colorScheme) var colorScheme. Seems a bit messy if we have many sheets within the app. Is there a way to make the sheet respect the app's theme? I'd rather not having to keep repeating this:

    view.resizableSheet($state) { builder in
                builder.content { context in
                    VStack {
                        Text("Demo")
                        Spacer()
                    }
                        .padding()
                        .frame(height: 300)
                }.sheetBackground { _ in
                    Color(colorScheme == .light ? "custom-white" : "custom-secondary")
                }
            }
    
    
    opened by SylarRuby 4
  • Change access modifier of ResizableSheetCenter's window

    Change access modifier of ResizableSheetCenter's window

    In #9, UIWindow's issue was reported. To update user interface style, we have to update overrideUserInterfaceStyle, but now we cannot access window in ResizableSheetCenter. So I updated the access level in this PR.

    opened by mtj0928 0
  • Bugs on Muti-line Text View

    Bugs on Muti-line Text View

    Thank you for open source. I think ResizableSheet is a more SwiftUI way implementation. But when I add Multi-line Text in builder.content, the scrollView's content size is incorrect.

    opened by alfredcc 0
  • Add example project

    Add example project

    Unfortunately I couldn't even present a simple Text("text") based on your documents in a timely manner.

    Simple example project would've been very helpful

    opened by alekseevpg 1
Releases(0.0.5)
Owner
matsuji
iOS App Engineer
matsuji
SwiftUI package to present a Bottom Sheet interactable view with the desired Detents. Also known as Half sheet.

BottomSheetSUI BottomSheetSUI is a package that gives you the ability to show a Bottom sheet intractable, where you can add your own SwiftUI view. You

Aitor Pagán 8 Nov 28, 2022
Modern-collection-view - Modern collection view for swift

Modern collection view Sample application demonstrating the use of collection vi

Nitanta Adhikari 1 Jan 24, 2022
Flow layout / tag cloud / collection view in SwiftUI.

SwiftUIFlowLayout A Flow Layout is a container that orders its views sequentially, breaking into a new "line" according to the available width of the

Gordan Glavaš 115 Dec 28, 2022
NStack is a SwiftUI view that allows you to hoist navigation state into a Coordinator

An NStack allows you to manage SwiftUI navigation state with a single stack property. This makes it easy to hoist that state into a high-level view, such as a coordinator. The coordinator pattern allows you to write isolated views that have zero knowledge of their context within the navigation flow of an app.

John Patrick Morgan 469 Dec 27, 2022
✨ Super sweet syntactic sugar for SwiftUI.View initializers.

ViewCondition ✨ Super sweet syntactic sugar for SwiftUI.View initializers. At a Glance struct BorderTextView: View { var color: Color? @ViewBuild

Yoon Joonghyun 76 Dec 17, 2022
A grid layout view for SwiftUI

Update July 2020 - latest SwiftUI now has built-in components to do this, which should be used instead. FlowStack FlowStack is a SwiftUI component for

John Susek 147 Nov 10, 2022
Horizontal and Vertical collection view for infinite scrolling that was designed to be used in SwiftUI

InfiniteScroller Example struct ContentView: View { @State var selected: Int = 1 var body: some View { InfiniteScroller(direction: .ve

Serhii Reznichenko 5 Apr 17, 2022
ReadabilityModifier - UIKits readableContentGuide for every SwiftUI View, in the form of a ViewModifier

ReadabilityModifier UIKits readableContentGuide for every SwiftUI View, in the form of a ViewModifier What it is Displaying multiple lines of text in

YAZIO 15 Dec 23, 2022
A SwiftUI proof-of-concept, and some sleight-of-hand, which adds rain to a view's background

Atmos A SwiftUI proof-of-concept, and some sleight-of-hand, which adds rain to a view's background. "Ima use this in my app..." Introducing Metal to S

Nate de Jager 208 Jan 2, 2023
A SwiftUI ScrollView that runs a callback when subviews are scrolled in and out of view.

VisibilityTrackingScrollView This package provides a variant of ScrollView that you can use to track whether views inside it are actually visible. Usa

Elegant Chaos 3 Oct 10, 2022