A focused SwiftUI component that provides smooth, customizable slide-up sheet transitions with built-in progress tracking.
So I've been using Waterllama to track my daily water intake (highly recommend it, by the way), and they have this absolutely gorgeous sheet transition that caught my eye. You know how most apps just use the standard iOS sheet that slides up? Well, Waterllama does it way more cleanly, it smoothly shifts the entire main content up. It's such a small detail, but it makes the whole interaction feel so much more polished and engaging.
I found myself opening the app just to see that transition again (okay, maybe I also needed to log my water π§). As a developer, you know how it is when you see something nicely crafted - you start thinking "I wonder how they did that" and "I bet I could build something like this."
I spent a few hours reverse-engineering the effect and realized this kind of transition could be useful in other contexts. The main issue with SwiftUI's built-in .sheet() and .fullScreenCover() is that you can't interact with or modify the view you're presenting from during the transition - it's completely hands-off. But what if you could control that background content and also tap into the transition progress for custom effects?
So MiniLiftOff was born. It gives you full control over the background view during the slide-up transition, plus exposes the animation progress as a binding for whatever effects you want to create.
MiniLiftOff creates a fullscreen cover that slides up from the bottom, similar to iOS sheets but gives you full control over the background view during the transition. You can apply any effects you want to the content behind the sheet - scaling, blurring, rotation, or any transformation based on the real-time progress of the animation.
Perfect for:
- Recreating those polished app transitions you admire
- Custom sheet presentations with background effects
- Onboarding flows with smooth transitions
- Interactive modals with progress-based animations
- Any UI where you want that extra bit of visual flair
- π― Dead Simple API - Just add
.miniLiftOff()like you would.sheet() - β‘ Buttery Smooth - Built-in geometry calculations with safe area handling
- π Progress Tracking - Real-time transition progress (0.0 to 1.0) for your custom effects
- π¨ Your Creativity - Use any SwiftUI Animation curve and combine any effects
- π Just Works - Automatic state sync, no weird binding issues
- π± Feels Native - Follows SwiftUI conventions you already know and love
Just drag MiniLiftOff.swift into your Xcode project and you're ready to go. No SPM, no CocoaPods, no fuss.
import SwiftUI
struct ContentView: View {
@State private var showSheet = false
@State private var transitionProgress: Double = 0.0
var body: some View {
NavigationView {
VStack {
Text("Main Content")
.font(.largeTitle)
Button("Show Sheet") {
showSheet = true
}
.buttonStyle(.borderedProminent)
}
.navigationTitle("MiniLiftOff Demo")
}
.miniLiftOff(
isPresented: $showSheet,
progress: $transitionProgress
) {
SheetView(isPresented: $showSheet)
}
}
}
struct SheetView: View {
@Binding var isPresented: Bool
var body: some View {
NavigationView {
VStack {
Text("Sheet Content")
.font(.title)
Button("Dismiss") {
isPresented = false
}
.buttonStyle(.bordered)
}
.navigationTitle("Sheet")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button("Done") {
isPresented = false
}
}
}
}
}
}struct ContentView: View {
@State private var showSheet = false
@State private var transitionProgress: Double = 0.0
var body: some View {
NavigationView {
VStack {
Text("Main Content")
.font(.largeTitle)
Button("Show Sheet") {
showSheet = true
}
.buttonStyle(.borderedProminent)
}
.navigationTitle("Advanced Demo")
}
// Here's where the magic happens - create any effect you want!
.scaleEffect(1.0 - transitionProgress * 0.1) // Gentle scale down
.blur(radius: transitionProgress * 8) // Blur effect
.brightness(-transitionProgress * 0.2) // Subtle darkening
.miniLiftOff(
isPresented: $showSheet,
progress: $transitionProgress,
animation: .spring(response: 0.6, dampingFraction: 0.8),
onDismiss: {
print("Back to the main view!")
}
) {
SheetView(isPresented: $showSheet)
}
}
}func miniLiftOff<Content: View>(
isPresented: Binding<Bool>,
progress: Binding<Double>,
animation: Animation = .easeInOut,
onDismiss: (() -> Void)? = nil,
@ViewBuilder content: @escaping () -> Content
) -> some View| Parameter | Type | Description |
|---|---|---|
isPresented |
Binding<Bool> |
Controls sheet presentation state |
progress |
Binding<Double> |
Exposes transition progress (0.0 to 1.0) |
animation |
Animation |
Animation curve (default: .easeInOut) |
onDismiss |
(() -> Void)? |
Optional dismissal callback |
content |
@ViewBuilder |
Sheet content that slides in from bottom |
This is the secret sauce! The progress binding gives you real-time transition state:
0.0- Sheet fully hidden (your main content is chillin')0.5- Sheet halfway through transition (things are getting interesting)1.0- Sheet fully presented (main content is completely hidden behind the sheet)
Use this value to drive whatever custom effects you can dream up on your main content.
.scaleEffect(1.0 - transitionProgress * 0.15).blur(radius: transitionProgress * 10).opacity(1.0 - transitionProgress * 0.7).scaleEffect(1.0 - transitionProgress * 0.1)
.blur(radius: transitionProgress * 8)
.brightness(-transitionProgress * 0.3)Go wild! Try rotation, translation, color shifts, or whatever comes to mind.
- Geometry Handling: Automatically figures out screen height including safe areas (no math required on your end)
- Performance: Minimal view rebuilds during transitions with efficient offset calculations
- State Management: Prevents those annoying infinite update loops while keeping bindings in sync
- Compatibility: Works with iOS 13+ and plays nice with any SwiftUI views
- iOS 13.0+
- Swift 5.5+
MIT License - see LICENSE file for details.
Built this with β and curiosity by Petros Bantolas. If you create something cool with it, I'd love to see it!
