Skip to content

Instantly share code, notes, and snippets.

@maxgribov
Created April 9, 2023 11:47
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save maxgribov/99d6778c33df0557b8ff455d1759395d to your computer and use it in GitHub Desktop.
Save maxgribov/99d6778c33df0557b8ff455d1759395d to your computer and use it in GitHub Desktop.
Custom SwiftUI transitions playground.
import SwiftUI
import PlaygroundSupport
struct ContentView: View {
@State private var isPresented = false
var body: some View {
ZStack {
Color.blue
.frame(width: 375, height: 800)
.zIndex(0)
if isPresented {
// alert transition example
VStack {
Text("Alert")
.font(.title)
Text("Some message here...")
}
.transition(.alert)
.zIndex(1)
// modal transition example
/*
Text("Modal view content")
.transition(.sheet(onDismiss: { isPresented = false }))
.zIndex(1)
*/
}
}
.onTapGesture {
withAnimation(.easeInOut) {
isPresented.toggle()
}
}
}
}
extension AnyTransition {
static var alert: AnyTransition {
.modifier(active: AlertViewModifier(transitionProgress: 0, isPresenting: false),
identity: AlertViewModifier(transitionProgress: 1, isPresenting: true))
}
static func sheet(onDismiss: @escaping () -> Void) -> AnyTransition {
.modifier(active: SheetViewModifier(transitionProgress: 0, onDismiss: onDismiss),
identity: SheetViewModifier(transitionProgress: 1, onDismiss: onDismiss))
}
}
struct AlertViewModifier: ViewModifier {
var transitionProgress: Double
var isPresenting: Bool
func body(content: Content) -> some View {
ZStack {
// dimm view
Color.clear
.background(.regularMaterial)
.opacity(transitionProgress)
.animation(.easeInOut, value: transitionProgress)
.ignoresSafeArea()
.zIndex(0)
// content
content
.padding(40)
.background(
RoundedRectangle(cornerRadius: 30)
.foregroundColor(.white)
)
// SwiftUI don't like scale == 0
.scaleEffect(x: transitionProgress + 0.01, y: transitionProgress + 0.01)
.animation(isPresenting ? .interactiveSpring(response: 0.5, dampingFraction: 0.5, blendDuration: 0.25) : .easeOut(duration: 0.3), value: transitionProgress)
.zIndex(1)
}
}
}
struct SheetViewModifier: ViewModifier {
var transitionProgress: Double
let onDismiss: () -> Void
@GestureState private var translation: CGFloat = 0
func body(content: Content) -> some View {
GeometryReader { proxy in
ZStack {
// dimm view
Color.gray
.opacity(transitionProgress)
.animation(.easeInOut, value: transitionProgress)
.ignoresSafeArea()
.zIndex(0)
SheetView(content: content)
.padding(.top, 40)
.offset(y: proxy.size.height * (1 - transitionProgress))
.offset(y: max(translation, 0))
.animation(.easeInOut, value: transitionProgress)
.animation(.easeOut, value: translation)
.gesture(DragGesture()
.updating($translation) { value, state, transaction in
state = value.translation.height
}
.onEnded { value in
if value.translation.height > proxy.size.height / 3 {
onDismiss()
}
})
.zIndex(1)
}
}
}
struct SheetView<Content: View>: View {
let content: Content
var body: some View {
VStack(spacing: 0) {
// header
RoundedRectangle(cornerRadius: 10)
.foregroundColor(.white)
.frame(height: 20)
Spacer()
// content
content
Spacer()
}
.background(Color.white)
}
}
}
PlaygroundPage.current.setLiveView(ContentView())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment