Category: 05. SwiftUI Basics

https://cdn3d.iconscout.com/3d/premium/thumb/ui-ux-3d-icon-png-download-9831982.png

  • SwiftUI Gestures

    SwiftUI Gestures

    Handle taps, drags, and other interactions with .gesture and built-in gesture recognizers.


    Tap and Long Press

    Handle taps and long presses with .onTapGesture and .onLongPressGesture.


    Syntax:

    • .onTapGesture { ... }
    • .onLongPressGesture(minimumDuration:perform:)

    Example

    Demo.swift

    ContentView.swift

    App.swift

    import SwiftUI
    
    struct TapLongPressDemo: View {
      @State private var on = false
      @State private var pressed = false
      var body: some View {
    
    Circle()
      .fill(on ? .green : .gray)
      .frame(width: 80, height: 80)
      .scaleEffect(pressed ? 0.9 : 1)
      .onTapGesture { on.toggle() }
      .onLongPressGesture(minimumDuration: 0.5) { pressed.toggle() }
    } }

    This example toggles color on tap and briefly scales the circle on a long-press.


    DragGesture

    Track finger movement by attaching a DragGesture to update an offset.

    Syntax: .gesture(DragGesture().onChanged { value in ... }.onEnded { ... })

    Example

    Demo.swift

    ContentView.swift

    App.swift

    import SwiftUI
    
    struct DragDemo: View {
      @State private var offset: CGSize = .zero
      var body: some View {
    
    Text("Drag me")
      .padding(12)
      .background(.blue.opacity(0.1))
      .cornerRadius(8)
      .offset(offset)
      .gesture(
        DragGesture()
          .onChanged { value in offset = value.translation }
          .onEnded { _ in withAnimation(.spring()) { offset = .zero } }
      )
    } }

    This example follows the finger while dragging and springs back to the origin on release.



    Combining Gestures

    Use .simultaneousGesture or .highPriorityGesture to coordinate multiple gestures.

    Syntax:

    • .simultaneousGesture(TapGesture())
    • .highPriorityGesture(DragGesture())

    Example

    Demo.swift

    ContentView.swift

    App.swift

    import SwiftUI
    
    struct CombineGesturesDemo: View {
      @State private var tapped = 0
      @State private var dragged = false
      var body: some View {
    
    Rectangle()
      .fill(dragged ? .orange : .purple)
      .frame(height: 120)
      .overlay(Text("taps: \(tapped)"))
      .simultaneousGesture(TapGesture().onEnded { tapped += 1 })
      .highPriorityGesture(DragGesture().onChanged { _ in dragged = true }.onEnded { _ in dragged = false })
    } }

    This example counts taps while also reacting to drags, demonstrating gesture composition.

  • SwiftUI Animations Animation Curves

    Animation Curves

    Choose timing functions like .easeIn.easeOut.easeInOut, and .linear to shape motion.


    Syntax

    Timing curves: withAnimation(.easeInOut(duration: 1)) { state = ... }.

    Choose .easeIn.easeOut.easeInOut, or .linear to change acceleration.


    EaseIn vs easeOut

    Tap buttons to move the dot with different curves. .easeIn starts slow and speeds up; .easeOut starts fast and slows down.

    Example

    Demo.swift

    ContentView.swift

    App.swift

    import SwiftUI
    
    struct CurvesDemo: View {
      @State private var x: CGFloat = 0
      var body: some View {
    
    VStack(spacing: 12) {
      HStack { Circle().frame(width: 24, height: 24).offset(x: x); Spacer() }
      HStack(spacing: 12) {
        Button("EaseIn") { withAnimation(.easeIn(duration: 1)) { x = 240 } }
        Button("EaseOut") { withAnimation(.easeOut(duration: 1)) { x = 0 } }
      }
    }
    .padding()
    } }

    In the example above, the dot moves with different curves using .easeIn and .easeOut.



    Linear vs easeInOut

    Compare a constant-speed (.linear) motion with a smooth accelerate/decelerate (.easeInOut).

    Example

    Demo.swift

    ContentView.swift

    App.swift

    import SwiftUI
    
    struct CurvesCompareDemo: View {
      @State private var go = false
      var body: some View {
    
    VStack(spacing: 16) {
      HStack { Circle().frame(width: 16, height: 16).offset(x: go ? 240 : 0); Spacer() }
        .animation(.linear(duration: 1), value: go)
      HStack { Circle().frame(width: 16, height: 16).offset(x: go ? 240 : 0); Spacer() }
        .animation(.easeInOut(duration: 1), value: go)
      Button(go ? "Reset" : "Animate") { go.toggle() }
    }
    .padding()
    } }
  • SwiftUI Animations Spring Animations

    SwiftUI Animations: Spring Animations

    Use .spring() animations to simulate mass-spring-damper motion with configurable stiffness and damping.


    Syntax

    Spring: withAnimation(.spring(response: 0.4, dampingFraction: 0.6)) { state.toggle() }response controls speed, dampingFraction controls overshoot/bounce.


    Basic Spring Toggle

    Tap the button to toggle the circle’s size with a spring animation configured by response and dampingFraction.

    Example

    Demo.swift

    ContentView.swift

    App.swift

    import SwiftUI
    
    struct SpringDemo: View {
      @State private var on = false
      var body: some View {
    
    VStack(spacing: 12) {
      Circle()
        .fill(on ? .green : .gray)
        .frame(width: on ? 120 : 60, height: on ? 120 : 60)
      Button("Toggle") {
        withAnimation(.spring(response: 0.4, dampingFraction: 0.6)) { on.toggle() }
      }
    }
    .padding()
    } }


    Tuned Spring Parameters

    Experiment with a bouncier spring by lowering damping and changing response. Multiple shapes animate with different timing.

    Example

    Demo.swift

    ContentView.swift

    App.swift

    import SwiftUI
    
    struct SpringTunedDemo: View {
      @State private var on = false
      var body: some View {
    
    VStack(spacing: 16) {
      RoundedRectangle(cornerRadius: 12)
        .fill(.blue.opacity(0.2))
        .frame(width: on ? 220 : 140, height: 40)
        .animation(.spring(response: 0.25, dampingFraction: 0.45), value: on)
      Circle()
        .fill(.orange.opacity(0.4))
        .frame(width: on ? 80 : 40, height: on ? 80 : 40)
        .animation(.spring(response: 0.5, dampingFraction: 0.65), value: on)
      Button(on ? "Reset" : "Bounce") { on.toggle() }
    }
    .padding()
    } }

  • SwiftUI Animations MatchedGeometryEffect

    MatchedGeometryEffect

    Animate view identity changes across layouts using .matchedGeometryEffect.


    Syntax

    Matched IDs: Apply .matchedGeometryEffect(id: "id", in: namespace) to two views; SwiftUI animates layout/size/position between them.


    Dot Expands to Large Circle

    Two circles share the same matched geometry ID.

    Toggling state animates size and position between the small and large versions.

    Example

    Demo.swift

    ContentView.swift

    App.swift

    import SwiftUI
    
    struct MatchedDemo: View {
      @Namespace private var ns
      @State private var showDetail = false
      var body: some View {
    
    VStack(spacing: 16) {
      if showDetail {
        Circle().matchedGeometryEffect(id: "dot", in: ns).frame(width: 120, height: 120)
      } else {
        Circle().matchedGeometryEffect(id: "dot", in: ns).frame(width: 40, height: 40)
      }
      Button("Toggle") { withAnimation(.spring()) { showDetail.toggle() } }
    }
    .padding()
    } }

    In the example above, the two circles share the same matched geometry ID.

    Toggling state animates size and position between the small and large versions.



    Card Expands to Detail (Shared ID)

    A small card animates into a larger detail view using the same matched geometry ID.

    The transition preserves visual continuity.

    Example

    Demo.swift

    ContentView.swift

    App.swift

    import SwiftUI
    
    struct CardsMatchedDemo: View {
      @Namespace private var ns
      @State private var expanded = false
      var body: some View {
    
    VStack(spacing: 16) {
      if expanded {
        RoundedRectangle(cornerRadius: 16)
          .fill(.blue.opacity(0.2))
          .matchedGeometryEffect(id: "card", in: ns)
          .frame(height: 160)
          .overlay(Text("Detail").font(.headline))
      } else {
        RoundedRectangle(cornerRadius: 12)
          .fill(.blue.opacity(0.2))
          .matchedGeometryEffect(id: "card", in: ns)
          .frame(height: 60)
          .overlay(Text("Card").font(.subheadline))
      }
      Button(expanded ? "Close" : "Open") {
        withAnimation(.spring()) { expanded.toggle() }
      }
    }
    .padding()
    } }

    In the example above, the small card animates into a larger detail view using the same matched geometry ID.

  • SwiftUI Animations Transitions

    Transitions

    Animate views as they insert/remove using .transition and withAnimation.


    Syntax

    Transition: .transition(.opacity) on the inserted/removed view.

    Wrap state change in withAnimation to animate the transition.


    Opacity + Scale Transition

    Tap the button to insert/remove a view. The text uses a combined .opacity and .scale transition.

    Example

    Demo.swift

    ContentView.swift

    App.swift

    import SwiftUI
    
    struct TransitionDemo: View {
      @State private var show = false
      var body: some View {
    
    VStack(spacing: 12) {
      Button(show ? "Hide" : "Show") {
        withAnimation(.easeInOut) { show.toggle() }
      }
      if show {
        Text("Hello")
          .padding(12)
          .background(.blue.opacity(0.1))
          .cornerRadius(8)
          .transition(.opacity.combined(with: .scale))
      }
    }
    .padding()
    } }

    In the example above, the text uses a combined .opacity and .scale transition.



    Asymmetric Transitions

    Use .asymmetric(insertion:removal:) to customize how the view appears vs disappears.

    Example

    Demo.swift

    ContentView.swift

    App.swift

    import SwiftUI
    
    struct AsymmetricTransitionDemo: View {
      @State private var show = false
      var body: some View {
    
    VStack(spacing: 12) {
      Button(show ? "Hide" : "Show") {
        withAnimation(.easeInOut) { show.toggle() }
      }
      if show {
        Text("Panel")
          .padding(12)
          .frame(maxWidth: .infinity)
          .background(.green.opacity(0.15))
          .cornerRadius(8)
          .transition(.asymmetric(insertion: .move(edge: .bottom).combined(with: .opacity),
                                  removal: .move(edge: .top).combined(with: .opacity)))
      }
    }
    .padding()
    } }
  • SwiftUI Animations Explicit

    Explicit

    Wrap state changes in withAnimation to control when and how to animate.


    Syntax

    withAnimation: withAnimation(.easeInOut) { state.toggle() }. All view modifier changes inside the block animate together.


    Rotate on Tap

    Tapping the button triggers withAnimation to rotate the icon by 180 degrees.

    Example

    Demo.swift

    ContentView.swift

    App.swift

    import SwiftUI
    
    struct ExplicitAnimDemo: View {
      @State private var angle = 0.0
      var body: some View {
    
    VStack(spacing: 12) {
      Image(systemName: "arrow.2.circlepath").rotationEffect(.degrees(angle))
      Button("Rotate") { withAnimation(.easeInOut) { angle += 180 } }
    }
    } }

    In the example above, the arrow icon rotates 180 degrees when the angle state changes.



    Animate Multiple Properties

    Use withAnimation to animate rotation, scale, color, and opacity in a single state change.

    Example

    Demo.swift

    ContentView.swift

    App.swift

    import SwiftUI
    
    struct ExplicitMultiDemo: View {
      @State private var on = false
      var body: some View {
    
    VStack(spacing: 16) {
      Image(systemName: "star.fill")
        .font(.system(size: 48))
        .foregroundStyle(on ? .yellow : .gray)
        .scaleEffect(on ? 1.3 : 1.0)
        .rotationEffect(.degrees(on ? 180 : 0)) 
        .opacity(on ? 1 : 0.6)
      Button(on ? "Reset" : "Animate") {
        withAnimation(.easeInOut(duration: 0.6)) { on.toggle() }
      }
    }
    } }
  • SwiftUI Animations Implicit

    Implicit

    Attach an animation to a view so changes to a bound value animate automatically.


    Syntax

    Attach animation: .animation(.easeInOut, value: state).

    Any modifier that depends on state will animate when state changes.


    Tap to Grow (Implicit)

    Attaching .animation(..., value: on) to the circle animates its size and color whenever on toggles.

    Example

    Demo.swift

    ContentView.swift

    App.swift

    import SwiftUI
    
    struct ImplicitAnimDemo: View {
      @State private var on = false
      var body: some View {
    
    VStack(spacing: 12) {
      Circle()
        .fill(on ? .green : .gray)
        .frame(width: on ? 120 : 60, height: on ? 120 : 60)
        .animation(.spring(), value: on)
      Button(on ? "Reset" : "Grow") { on.toggle() }
    }
    .padding()
    } }

    In the example above, the circle’s size and color animate when the on state changes.



    Implicitly Animate Multiple Modifiers

    Attach a single .animation and change several properties; scale, opacity, and color animate when the state toggles.

    Example

    Demo.swift

    ContentView.swift

    App.swift

    import SwiftUI
    
    struct ImplicitMultiDemo: View {
      @State private var on = false
      var body: some View {
    
    VStack(spacing: 16) {
      Image(systemName: "star.fill")
        .font(.system(size: 48))
        .foregroundStyle(on ? .yellow : .gray)
        .scaleEffect(on ? 1.25 : 1.0)
        .opacity(on ? 1 : 0.6)
        .animation(.easeInOut(duration: 0.6), value: on)
      Button(on ? "Reset" : "Animate") { on.toggle() }
    }
    .padding()
    } }
  • SwiftUI Animations & Transitions

    SwiftUI Animations & Transitions

    Animate view changes with withAnimation and apply .transition for insert/remove effects.


    Implicit Animations

    Attach an animation to a state-driven view modifier so changes animate automatically.

    Syntax:

    • .animation(.easeInOut, value: state)
    • .animation(.spring(), value: state)

    Example

    Demo.swift

    ContentView.swift

    App.swift

    import SwiftUI
    
    struct ImplicitDemo: View {
      @State private var on = false
      var body: some View {
    
    Circle()
      .fill(on ? .green : .gray)
      .frame(width: on ? 120 : 60, height: on ? 120 : 60)
      .animation(.spring(), value: on)
      .onTapGesture { on.toggle() }
    } }

    This example animates color and size whenever the boolean state changes, using a spring animation.


    Explicit Animations

    Wrap state changes in withAnimation to control when and how to animate.

    Syntax: withAnimation(.easeInOut) { state.toggle() }

    Example

    Demo.swift

    ContentView.swift

    App.swift

    import SwiftUI
    
    struct ExplicitDemo: View {
      @State private var angle = 0.0
      var body: some View {
    
    VStack(spacing: 12) {
      Image(systemName: "arrow.2.circlepath")
        .rotationEffect(.degrees(angle))
      Button("Rotate") {
        withAnimation(.easeInOut) { angle += 180 }
      }
    }
    } }

    This example explicitly animates rotation when the button is pressed.



    Transitions

    Animate views as they insert/remove with .transition and .animation.

    Syntax:

    • .transition(.slide)
    • .transition(.opacity.combined(with: .scale))

    Example

    Demo.swift

    ContentView.swift

    App.swift

    import SwiftUI
    
    struct TransitionDemo: View {
      @State private var show = false
      var body: some View {
    
    VStack(spacing: 12) {
      Button(show ? "Hide" : "Show") {
        withAnimation(.easeInOut) { show.toggle() }
      }
      if show {
        Text("Hello")
          .padding(12)
          .background(.blue.opacity(0.1))
          .cornerRadius(8)
          .transition(.opacity.combined(with: .scale))
      }
    }
    } }
  • SwiftUI Lists & Forms List Styles

    SwiftUI Lists & Forms: List Styles

    Customize list appearance with styles like .insetGrouped.plain, and .sidebar.


    Example: Inset Grouped

    Example

    Demo.swift

    ContentView.swift

    App.swift

    import SwiftUI
    
    struct ListStylesInsetDemo: View {
      var body: some View {
    
    List { Text("A"); Text("B"); Text("C") }
      .listStyle(.insetGrouped)
    } }


    Example: Plain with hidden separators

    Example

    Demo.swift

    ContentView.swift

    App.swift

    import SwiftUI
    
    struct ListStylesPlainDemo: View {
      var body: some View {
    
    List { Text("A"); Text("B"); Text("C") }
      .listStyle(.plain)
      .listRowSeparator(.hidden)
    } }
  • SwiftUI Lists & Forms Edit Mode

    Edit Mode

    Enable reordering and deletion in lists by toggling EditMode.


    Example: Built-in EditButton

    Example

    Demo.swift

    ContentView.swift

    App.swift

    import SwiftUI
    
    struct EditButtonDemo: View {
      @State private var items = ["A","B","C"]
      var body: some View {
    
    NavigationStack {
      List {
        ForEach(items, id: \.self) { Text($0) }
          .onDelete { items.remove(atOffsets: $0) }
          .onMove { items.move(fromOffsets: $0, toOffset: $1) }
      }
      .navigationTitle("Edit Mode")
      .toolbar { EditButton() }
    }
    } }


    Example: Custom toggle

    Example

    Demo.swift

    ContentView.swift

    App.swift

    import SwiftUI
    
    struct EditCustomDemo: View {
      @State private var items = ["A","B","C"]
      @State private var editMode: EditMode = .inactive
      var body: some View {
    
    VStack {
      List {
        ForEach(items, id: \.self) { Text($0) }
          .onDelete { items.remove(atOffsets: $0) }
          .onMove { items.move(fromOffsets: $0, toOffset: $1) }
      }
      .environment(\.editMode, $editMode)
      Button(editMode.isEditing ? "Done" : "Edit") { editMode.toggle() }
    }
    } } private extension EditMode { mutating func toggle() { self = (isEditing ? .inactive : .active) } }