Contents

wiedem/fullscreen-cover

**FullScreenCover** presents full-screen modal views in SwiftUI, building on [`.fullScreenCover`](https://developer.apple.com/documentation/swiftui/view/fullscreencover(ispresented:ondismiss:content:)) and adding what's missing:

Usage

Add import FullScreenCover to your source code. Wrap your view with a PresentationCoordinator and pass its proxy to the fullScreenCover(presentation:animation:content:) modifier.

Custom Transition Animations

Use any SwiftUI transition:

import FullScreenCover
import SwiftUI

struct DemoView: View {
    var body: some View {
        PresentationCoordinator { proxy in
            Button("Present Modal") {
                Task { try await proxy.present() }
            }
            .fullScreenCover(presentation: proxy, animation: .spring(duration: 0.5)) {
                ZStack {
                    Color.black.opacity(0.5)
                        .ignoresSafeArea()

                    VStack(spacing: 16) {
                        Text("Custom modal content")
                            .font(.title)

                        Button("Dismiss") {
                            Task { try await proxy.dismiss() }
                        }
                    }
                }
                .transition(.scale(scale: 0.8).combined(with: .opacity))
            }
        }
    }
}

Use the transition(_:) modifier on your modal content to define your custom animation. The animation parameter on fullScreenCover controls the timing.

The presentation background is automatically set to transparent so that custom transitions render correctly. You can override it by applying .presentationBackground(_:) to your modal content.

Async Coordination

Both present() and dismiss() are async and return only after the transition has completed. This makes it safe to chain sequential operations:

Button("Show Confirmation") {
    Task {
        // present() returns once the modal content has appeared.
        try await proxy.present()

        // Do some work while the modal is visible...
        try await performNetworkRequest()

        // dismiss() returns after the dismiss animation finishes.
        try await proxy.dismiss()

        // Safe to continue, e.g. navigate or show another modal.
        navigateToNextScreen()
    }
}

Both methods throw CancellationError if the calling task is cancelled. The transition itself continues unaffected - only the caller stops waiting.

Presentation Phase

The proxy exposes a phase property of type PresentationPhase that tracks the current lifecycle state: idle, presenting, presented, or dismissing. Use it to adapt your UI during transitions:

PresentationCoordinator { proxy in
    Button("Present") {
        Task { try await proxy.present() }
    }
    .disabled(proxy.phase != .idle)
}

Accessing the Proxy in Child Views

The PresentationProxy is automatically injected as an EnvironmentObject. Child views can access it without passing it through manually:

struct DismissButton: View {
    @EnvironmentObject private var proxy: PresentationProxy

    var body: some View {
        Button("Close") {
            Task { try await proxy.dismiss() }
        }
    }
}

Installation

Add the package to the dependencies in your Package.swift file:

.package(url: "https://github.com/wiedem/fullscreen-cover", .upToNextMajor(from: "2.0.0")),

Then include "FullScreenCover" as a dependency for your target:

dependencies: [
    .product(name: "FullScreenCover", package: "fullscreen-cover"),
]

Requirements

  • iOS 16.4+
  • Swift 6.1+
  • Xcode 16.3+

Contributing

Contributions are welcome! Please feel free to:

  • Report bugs or request features via GitHub Issues
  • Submit pull requests with improvements
  • Improve documentation or add examples
  • Share feedback on API design

License

FullScreenCover is available under the MIT License. See LICENSE.txt for more information.

Package Metadata

Repository: wiedem/fullscreen-cover

Default branch: main

README: README.md