Loading...
Loading...
This skill provides comprehensive guidance for implementing advanced SwiftUI animations, transitions, matched geometry effects, and Metal shader integration. Use when building animations, view transitions, hero animations, or GPU-accelerated effects in SwiftUI apps for iOS and macOS.
npx skill4agent add jamesrochabrun/skills swiftui-animation// Explicit animation (preferred)
withAnimation(.spring(response: 0.4, dampingFraction: 0.75)) {
isExpanded.toggle()
}
// iOS 17+ spring presets
withAnimation(.snappy) { ... } // Fast, small bounce
withAnimation(.smooth) { ... } // Gentle, no bounce
withAnimation(.bouncy) { ... } // More bounce// Basic
.transition(.opacity)
.transition(.scale)
.transition(.slide)
.transition(.move(edge: .bottom))
// Combined
.transition(.move(edge: .trailing).combined(with: .opacity))
// Asymmetric
.transition(.asymmetric(
insertion: .move(edge: .bottom),
removal: .opacity
))@Namespace var namespace
// Source view
ThumbnailView()
.matchedGeometryEffect(id: "hero", in: namespace)
// Destination view
DetailView()
.matchedGeometryEffect(id: "hero", in: namespace)// Color manipulation
.colorEffect(ShaderLibrary.invert())
// Pixel displacement
.distortionEffect(
ShaderLibrary.wave(.float(time)),
maxSampleOffset: CGSize(width: 20, height: 20)
)
// Full layer access
.layerEffect(ShaderLibrary.blur(.float(radius)), maxSampleOffset: .zero)references/struct ExpandableCard: View {
@State private var isExpanded = false
var body: some View {
VStack {
RoundedRectangle(cornerRadius: isExpanded ? 20 : 12)
.fill(.blue)
.frame(
width: isExpanded ? 300 : 150,
height: isExpanded ? 400 : 100
)
}
.onTapGesture {
withAnimation(.spring(response: 0.35, dampingFraction: 0.75)) {
isExpanded.toggle()
}
}
}
}ForEach(Array(items.enumerated()), id: \.element.id) { index, item in
ItemRow(item: item)
.transition(.asymmetric(
insertion: .move(edge: .trailing).combined(with: .opacity),
removal: .move(edge: .leading).combined(with: .opacity)
))
.animation(.spring().delay(Double(index) * 0.05), value: items)
}Circle()
.fill(.blue)
.frame(width: 20, height: 20)
.scaleEffect(isPulsing ? 1.2 : 1.0)
.opacity(isPulsing ? 0.6 : 1.0)
.onAppear {
withAnimation(.easeInOut(duration: 1.0).repeatForever(autoreverses: true)) {
isPulsing = true
}
}withAnimation.animation().spring(response: 0.35, dampingFraction: 0.8)withAnimationwithAnimationzIndex.metalmaxSampleOffset