Slide Scale Transition
A custom transition that combines an offset with a scale, available as .slideScale.

- Transitions
- iOS 18.0+
- beginner
- Origin
- Original work
- Found by
- transition · slide · scale · offset · carousel · custom transition · swap · transición · deslizar · escala · desplazamiento · carrusel · intercambio
Description
Section titled “Description”A transition that slides and scales a view: it enters from one side and leaves by the other.
With Reduce Motion on, the view only fades.
struct SlideScaleTransitionDemo: View { @State private var index = 0
private let cards: [(title: String, systemImage: String, colors: [Color])] = [ ("Run", "figure.run", [.orange, .red]), ("Swim", "figure.pool.swim", [.cyan, .blue]), ("Climb", "figure.climbing", [.mint, .green]), ]
private var card: (title: String, systemImage: String, colors: [Color]) { cards[index % cards.count] }
var body: some View { ZStack { Color(.systemGroupedBackground) .ignoresSafeArea()
VStack(spacing: 20) { Image(systemName: card.systemImage) .font(.system(.largeTitle, weight: .semibold)) .imageScale(.large) .accessibilityHidden(true)
Text(card.title) .font(.system(.largeTitle, design: .rounded, weight: .bold)) } .foregroundStyle(.white) .frame(maxWidth: .infinity) .frame(height: 340) .background( LinearGradient(colors: card.colors, startPoint: .topLeading, endPoint: .bottomTrailing), in: .rect(cornerRadius: 36) ) .padding(32) .id(index) .transition(.slideScale) } .task { while (try? await Task.sleep(for: .seconds(1.3))) != nil { withAnimation(.smooth(duration: 0.55)) { index += 1 } } } }}API reference
Section titled “API reference”| Parameter | Type | Default |
|---|---|---|
axis | Axis | .horizontal |
distance | CGFloat | 220 |
scale | CGFloat | 0.8 |
Copy this code into your project, or download the file.
// SPDX-License-Identifier: MIT// Copyright (c) 2026 Oscar Canton and SwiftUI Blocks contributors
import SwiftUI
/// A transition that slides and scales a view: it enters from one side and leaves by the other.////// With Reduce Motion on, the view only fades.struct SlideScaleTransition: Transition { var axis: Axis = .horizontal var distance: CGFloat = 220 var scale: CGFloat = 0.8
func body(content: Content, phase: TransitionPhase) -> some View { content.modifier(SlideScaleEffect(axis: axis, distance: distance, scale: scale, phase: phase)) }}
extension Transition where Self == SlideScaleTransition { static var slideScale: SlideScaleTransition { SlideScaleTransition() }
static func slideScale(axis: Axis, distance: CGFloat = 220, scale: CGFloat = 0.8) -> SlideScaleTransition { SlideScaleTransition(axis: axis, distance: distance, scale: scale) }}
// The effect lives in a view modifier so it can read the environment.private struct SlideScaleEffect: ViewModifier { let axis: Axis let distance: CGFloat let scale: CGFloat let phase: TransitionPhase
@Environment(\.accessibilityReduceMotion) private var reduceMotion
// `phase.value` is -1 before appearing, 0 at rest and 1 after disappearing. private var offset: CGFloat { reduceMotion ? 0 : -phase.value * distance }
func body(content: Content) -> some View { content .scaleEffect(phase.isIdentity || reduceMotion ? 1 : scale) .offset(x: axis == .horizontal ? offset : 0, y: axis == .vertical ? offset : 0) .opacity(phase.isIdentity ? 1 : 0) }}
// MARK: - Demo
struct SlideScaleTransitionDemo: View { @State private var index = 0
private let cards: [(title: String, systemImage: String, colors: [Color])] = [ ("Run", "figure.run", [.orange, .red]), ("Swim", "figure.pool.swim", [.cyan, .blue]), ("Climb", "figure.climbing", [.mint, .green]), ]
private var card: (title: String, systemImage: String, colors: [Color]) { cards[index % cards.count] }
var body: some View { ZStack { Color(.systemGroupedBackground) .ignoresSafeArea()
VStack(spacing: 20) { Image(systemName: card.systemImage) .font(.system(.largeTitle, weight: .semibold)) .imageScale(.large) .accessibilityHidden(true)
Text(card.title) .font(.system(.largeTitle, design: .rounded, weight: .bold)) } .foregroundStyle(.white) .frame(maxWidth: .infinity) .frame(height: 340) .background( LinearGradient(colors: card.colors, startPoint: .topLeading, endPoint: .bottomTrailing), in: .rect(cornerRadius: 36) ) .padding(32) .id(index) .transition(.slideScale) } .task { while (try? await Task.sleep(for: .seconds(1.3))) != nil { withAnimation(.smooth(duration: 0.55)) { index += 1 } } } }}
#Preview { SlideScaleTransitionDemo()}