Category: 05. SwiftUI Basics

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

  • SwiftUI Previews

    PreviewProvider

    Define a PreviewProvider to render your view in the canvas without running the app.

    Example

    PreviewProvider.swift

    import SwiftUI
    
    struct DemoView: View { var body: some View { Text("Hello") } }
    
    struct DemoView_Previews: PreviewProvider {
      static var previews: some View { DemoView() }
    }


    #Preview Macro

    Use the #Preview macro to declare previews inline next to your view code.

    Example

    #Preview.swift

    import SwiftUI
    
    #Preview { DemoView() }
  • SwiftUI Custom Modifiers

    SwiftUI Custom Modifiers

    Create reusable styling and behavior by defining your own ViewModifier types or extension helpers.


    Build a reusable ViewModifier

    Encapsulate styling in a custom modifier and expose it via a View extension for reuse.

    Demo.swift

    ContentView.swift

    App.swift

    import SwiftUI
    
    struct CardStyle: ViewModifier {
      func body(content: Content) -> some View {
    
    content
      .padding()
      .background(.blue.opacity(0.1))
      .cornerRadius(8)
    } } extension View { func card() -> some View { modifier(CardStyle()) } } struct CustomModifiersDemo: View { var body: some View { Text("Hello").card() } }
  • SwiftUI ViewBuilder

    SwiftUI ViewBuilder

    Use @ViewBuilder to compose multiple child views in a single return position.


    Compose rows with @ViewBuilder

    Extract a reusable row builder using @ViewBuilder and combine multiple child views without extra containers.

    Example

    Demo.swift

    ContentView.swift

    App.swift

    import SwiftUI
    
    @ViewBuilder
    func InfoRow(_ title: String, _ value: String) -> some View {
      HStack { Text(title).bold(); Spacer(); Text(value) }
    }
    
    struct ViewBuilderDemo: View {
      var body: some View {
    
    VStack(spacing: 8) {
      InfoRow("Name", "SwiftUI")
      InfoRow("Version", "5+")
    }
    .padding()
    } }
  • SwiftUI Modifiers

    Style views by chaining modifiers

    Apply multiple modifiers to a view to adjust typography, color, spacing, and backgrounds.

    Example

    Demo.swift

    ContentView.swift

    App.swift

    import SwiftUI
    
    struct ModifiersDemo: View {
      var body: some View {
    
    Text("Hello")
      .font(.title)
      .foregroundStyle(.blue)
      .padding()
      .background(.blue.opacity(0.1))
      .cornerRadius(8)
    } }

    The example above shows a text view styled with multiple modifiers.

  • SwiftUI Modifiers & ViewBuilder

    Modifiers

    Modifiers return new views with applied changes such as padding, font, and color.

    Example

    Demo.swift

    ContentView.swift

    App.swift

    import SwiftUI
    
    struct ModifiersDemo: View {
      var body: some View {
    
    Text("Hello")
      .font(.title)
      .foregroundStyle(.blue)
      .padding()
      .background(.blue.opacity(0.1))
      .cornerRadius(8)
    } }

    ViewBuilder

    Use @ViewBuilder to compose multiple child views in a single return position.

    Example

    Demo.swift

    ContentView.swift

    App.swift

    import SwiftUI
    
    @ViewBuilder
    func InfoRow(_ title: String, _ value: String) -> some View {
      HStack { Text(title).bold(); Spacer(); Text(value) }
    }
    
    struct ViewBuilderDemo: View {
      var body: some View {
    
    VStack(spacing: 8) {
      InfoRow("Name", "SwiftUI")
      InfoRow("Version", "5+")
    }
    .padding()
    } }
  • SwiftUI Gestures RotationGesture

    RotationGesture

    Rotate views with two fingers using RotationGesture and apply the angle via .rotationEffect.


    Syntax

    Rotation: Use RotationGesture() to read an Angle and apply it with .rotationEffect(angle).


    Rotate and Reset

    Rotate the image with two fingers. Releasing animates back to 0°.

    Example

    Demo.swift

    ContentView.swift

    App.swift

    import SwiftUI
    
    struct RotationDemo: View {
      @State private var angle = Angle.zero
      var body: some View {
    
    Image(systemName: "arrow.2.circlepath")
      .font(.system(size: 48))
      .rotationEffect(angle)
      .gesture(
        RotationGesture()
          .onChanged { value in angle = value }
          .onEnded { _ in withAnimation(.easeInOut) { angle = .zero } }
      )
    } }

    In the example above, the image can be rotated with two fingers.

    Releasing animates back to 0°.


    Accumulate Rotation

    Accumulate the rotation so each gesture adds to the total angle.

    Display the angle in degrees.

    Example

    Demo.swift

    ContentView.swift

    App.swift

    import SwiftUI
    
    struct RotationAccumDemo: View {
      @State private var total: Angle = .zero
      @State private var current: Angle = .zero
      var body: some View {
    
    VStack(spacing: 12) {
      Image(systemName: "arrow.triangle.2.circlepath")
        .font(.system(size: 48))
        .rotationEffect(total + current)
        .gesture(
          RotationGesture()
            .onChanged { value in current = value }
            .onEnded { value in total += value; current = .zero }
        )
      Text("Angle: \(Int((total + current).degrees))°")
    }
    .padding()
    } }
  • SwiftUI Gestures MagnificationGesture

    MagnificationGesture

    Pinch to zoom content using MagnificationGesture, often combined with .scaleEffect.


    Syntax

    Magnify: Use MagnificationGesture() to get a scale factor and apply it with .scaleEffect(scale).


    Pinch to Zoom

    Pinch the image to zoom in/out. Releasing snaps back to the original size.

    Example

    Demo.swift

    ContentView.swift

    App.swift

    import SwiftUI
    
    struct MagnifyDemo: View {
      @State private var scale: CGFloat = 1
      var body: some View {
    
    Image(systemName: "photo")
      .resizable().scaledToFit().frame(height: 120)
      .scaleEffect(scale)
      .gesture(
        MagnificationGesture()
          .onChanged { value in scale = value }
          .onEnded { _ in withAnimation(.spring()) { scale = 1 } }
      )
    } }

    In the example above, pinching the image zooms in/out.

    Releasing snaps back to the original size.


    Clamp Scale Range

    Limit the zoom between 0.5x and 2x to avoid miniaturization or excessive enlargement.

    Example

    Demo.swift

    ContentView.swift

    App.swift

    import SwiftUI
    
    struct MagnifyClampedDemo: View {
      @State private var scale: CGFloat = 1
      let minS: CGFloat = 0.5
      let maxS: CGFloat = 2.0
      func clamp(_ v: CGFloat) -> CGFloat { min(max(v, minS), maxS) }
      var body: some View {
    
    Image(systemName: "photo")
      .resizable().scaledToFit().frame(height: 120)
      .scaleEffect(scale)
      .gesture(
        MagnificationGesture()
          .onChanged { value in scale = clamp(value) }
          .onEnded { _ in withAnimation(.easeInOut) { scale = 1 } }
      )
    } }

    In the example above, the zoom is clamped between 0.5x and 2x.

  • SwiftUI Gestures Composing Gestures

    Composing Gestures

    Combine gestures using .simultaneousGesture or .highPriorityGesture and coordinate interactions.


    Syntax

    Compose: Add .simultaneousGesture() to run together, or .highPriorityGesture() to give precedence to one gesture.


    Simultaneous Tap + Drag

    Both gestures run: taps increment a counter while dragging changes color.

    Example

    Demo.swift

    ContentView.swift

    App.swift

    import SwiftUI
    
    struct ComposeSimultaneousDemo: 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 })
      .gesture(DragGesture().onChanged { _ in dragged = true }.onEnded { _ in dragged = false })
    } }

    In the example above, both the tap and drag gestures run.


    High Priority Drag Over Tap

    Drag takes precedence over tap: while dragging, taps do not increment the counter.

    Example

    Demo.swift

    ContentView.swift

    App.swift

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

    DragGesture

    Track finger movement and update view position or state in response to a DragGesture.


    Syntax

    Drag: Attach DragGesture() and handle .onChanged/.onEnded.

    Apply translation via .offset.


    Drag to Move

    Drag the label to move it around. Releasing snaps it back using a spring animation.

    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 } }
      )
    } }

    In the example above, the label is dragged and released.

    The .onEnded handler snaps it back using a spring animation.


    Constrain Drag Within Bounds

    Clamp the drag translation so the view stays inside a container.

    Example

    Demo.swift

    ContentView.swift

    App.swift

    import SwiftUI
    
    struct DragBoundsDemo: View {
      @State private var offset: CGSize = .zero
      let limit: CGFloat = 120
      func clamp(_ v: CGFloat) -> CGFloat { min(max(v, -limit), limit) }
      var body: some View {
    
    ZStack {
      RoundedRectangle(cornerRadius: 12)
        .stroke(.gray.opacity(0.4), lineWidth: 1)
        .frame(width: 280, height: 160)
      Circle()
        .fill(.blue.opacity(0.2))
        .frame(width: 44, height: 44)
        .offset(x: offset.width, y: offset.height)
        .gesture(
          DragGesture()
            .onChanged { value in
              offset = CGSize(width: clamp(value.translation.width), height: clamp(value.translation.height))
            }
            .onEnded { _ in withAnimation(.spring()) { offset = .zero } }
        )
    }
    .padding()
    } }

    In the example above, the circle is dragged and released.

  • SwiftUI Gestures LongPressGesture

    LongPressGesture

    Detect a sustained press using LongPressGesture to trigger state changes.


    Syntax

    Long press handler: .onLongPressGesture(minimumDuration: 0.5) { ... } or LongPressGesture(minimumDuration:) with .onEnded.


    Toggle on Long Press

    Hold for 0.5 seconds to toggle the label between “Hold me” and “Pressed”.

    Example

    Demo.swift

    ContentView.swift

    App.swift

    import SwiftUI
    
    struct LongPressDemo: View {
      @State private var pressed = false
      var body: some View {
    
    Text(pressed ? "Pressed" : "Hold me")
      .padding(12)
      .background(.blue.opacity(0.1))
      .cornerRadius(8)
      .onLongPressGesture(minimumDuration: 0.5) { pressed.toggle() }
    } }

    In the example above, the label toggles between “Hold me” and “Pressed” when held for 0.5 seconds.



    Show Progress During Long Press

    Track the in-progress state and scale the view while pressing using LongPressGesture with .updating.

    Example

    Demo.swift

    ContentView.swift

    App.swift

    import SwiftUI
    
    struct LongPressProgressDemo: View {
      @GestureState private var isPressing = false
      @State private var done = false
      var body: some View {
    
    Circle()
      .fill(done ? .green : .gray)
      .frame(width: 80, height: 80)
      .scaleEffect(isPressing ? 0.9 : 1)
      .gesture(
        LongPressGesture(minimumDuration: 0.6)
          .updating($isPressing) { value, state, _ in state = value }
          .onEnded { _ in done.toggle() }
      )
    } }