Contents

aswinter90/SwiftyDrawer

Customisable SwiftUI drawer component available on iOS 15+

🔩 Installation

You can add SwiftyDrawer to an Xcode project by adding it as a package dependency. The required minimum platform version is iOS 15.

From the File menu, select Add Package Dependencies... Enter "https://github.com/aswinter90/SwiftyDrawer" into the package repository URL text field.

Or add it to your Swift package by referencing it in your package manifest:

let package = Package(
    name: "MyLibrary",
    platforms: [.iOS(.v15)],
    products: [
        .library(
            name: "MyLibrary",
            targets: ["MyLibrary"]),

    ],
    dependencies: [
        .package(url: "https://github.com/aswinter90/SwiftyDrawer", from: "1.0.0")
    ],
    targets: [
        .target(
            name: "MyLibrary",
            dependencies: ["SwiftyDrawer"]
        ),
    ]
)

📱 Examples

The project contains multiple demo applications with showcases for displaying the SwiftyDrawer in different usage scenarios.

Minimal setup

<img width="350" height="761" alt="image" src="https://github.com/user-attachments/assets/b192581e-9a37-41d3-9898-7ccf1f99b9ed" />

import SwiftUI
import SwiftyDrawer

struct ContentView: View {
    @State private var drawerState = DrawerState(case: .partiallyOpened)

    var body: some View {
        MySwiftLogo()
            .drawerOverlay(
                state: $drawerState,
                content: {
                    VStack {
                        ForEach(0..<30) { index in
                            Text("Item \(index)")
                                .frame(maxWidth: .infinity, alignment: .leading)
                                .padding()

                            Divider()
                        }
                    }
                    .padding(.top, 8)
                }
            )
    }
}

With sticky header and changing alignments

As shown in the video the drawer can be modified with a sticky header, which stays on top of the safe area or the tab bar when the drawer is closed. The default drag handle can also be replaced with any other given view. Finally the DrawerState is mutable and changing it from the outside will update the drawer position automatically.

https://github.com/user-attachments/assets/107b48e3-2f99-47ac-a7a9-5430d77b444a

State based drawer content

This is a demonstration for how the drawer content can be updated by observing a ViewModel state.

https://github.com/user-attachments/assets/0b127664-3b4d-40a6-8a0a-98061e0b6680

Customization

There are several modifiers for changing the drawer appearance and behaviors:

MySwiftLogo()
    .drawerOverlay(
        state: $drawerState,
        content: {...}
    )
    .drawerStyle(drawerStyle: DrawerStyle) // Color, corner radius, shadows, etc.
    .drawerFloatingButtonsConfiguration(configuration: DrawerFloatingButtonsConfiguration) // Circular buttons that are shown over the drawer
    .drawerLayoutStrategy(layoutStrategy: DrawerContentLayoutStrategy) // Defines which collection-view–based layout approach the drawer uses—either a robust classic flow layout or a smoother but occasionally glitchy modern compositional layout.
    .drawerAnimation(animation: Animation) // Define the animation type for opening and closing animations
    .isDrawerHapticFeedbackEnabled(isEnabled: Bool) // If true the device vibrates after the drawer finished its opening and closing animation
    .isApplyingRenderingOptimizationToDrawerHeader(Bool) // Set to false to prevent the drawer header being rendered as an offscreen image (using the `drawingGroup` modifier). This can be helpful if the header contains interactable content, like a horizontal scrollview
    .drawerContentOffsetController(DrawerContentOffsetController?) // Used for reading or changing the content's scroll offset
    .drawerOriginObservable(DrawerOriginObservable?) // Used for reading the drawer position

💥 Known issues

  • Bugs and glitches on older iOS versions like 15 must be expected. In some scenarios the drawer content is not scrollable in other the content's scroll level resets randomly.
  • The drawer always requires a modifyable state binding, meaning that passing a constant binding will not work: .drawerOverlay(state: Binding.constant(.init(case: .fullyOpened)), ...)
  • Your content gets updated but it is not reflected in the drawer? Make sure to define your content as a dedicated view, which obsorves changes of a Binding or an ObservableObject or take a look at the SwiftyDrawer-Map-Demo.

Package Metadata

Repository: aswinter90/SwiftyDrawer

Stars: 6

Forks: 1

Open issues: 0

Default branch: main

Primary language: swift

License: MIT

README: README.md