Chapters

Hide chapters

SwiftUI Apprentice

Second Edition · iOS 16 · Swift 5.7 · Xcode 14.2

Section I: Your First App: HIITFit

Section 1: 12 chapters
Show chapters Hide chapters

Section II: Your Second App: Cards

Section 2: 9 chapters
Show chapters Hide chapters

21. Delightful UX — Final Touches
Written by Caroline Begbie

Heads up... You’re accessing parts of this content for free, with some sections shown as jksylwtij text.

Heads up... You’re accessing parts of this content for free, with some sections shown as ftgitfvok text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

An iOS app is not complete without some snazzy animation. SwiftUI makes it amazingly easy to animate events that occur when you change property values. Transition animations are a breeze.

To get the best result when testing animations, you should run the app on a device. Animations often won’t work in preview but, if you don’t want to use the device, they will generally work in Simulator.

The Starter Project

➤ Open the starter project for this chapter.

The project has an additional group called Supporting Code. This group contains some complex views that you’ll add to your app shortly.

Animated Splash Screen

Skills you’ll learn in this section: set up properties for animation

Heads up... You’re accessing parts of this content for free, with some sections shown as qqfunzfat text.

Heads up... You’re accessing parts of this content for free, with some sections shown as wrxewwzed text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now
Final animation
Zalos efodidoay

@State private var showSplash = true
var body: some View {
  if showSplash {
    SplashScreen()
      .ignoresSafeArea()
  } else {
    CardsListView()
  }
}
.environmentObject(CardStore(defaultData: true))

Heads up... You’re accessing parts of this content for free, with some sections shown as qdxyddxom text.

Heads up... You’re accessing parts of this content for free, with some sections shown as xdxozzbop text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now
AppLoadingView()
Hello, World
Joxba, Gekzm

func card(letter: String, color: String) -> some View {
  ZStack {
    RoundedRectangle(cornerRadius: 25)
      .shadow(radius: 3)
      .frame(width: 120, height: 160)
      .foregroundColor(.white)
    Text(letter)
      .fontWeight(.bold)
      .scalableText()
      .foregroundColor(Color(color))
      .frame(width: 80)
  }
}
card(letter: "C", color: "appColor7")
The card
Bne miry

Heads up... You’re accessing parts of this content for free, with some sections shown as kbkijzwyt text.

Heads up... You’re accessing parts of this content for free, with some sections shown as grhatgzez text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now
private struct SplashAnimation: ViewModifier {
  @State private var animating = true
  let finalYPosition: CGFloat
  let delay: Double

  func body(content: Content) -> some View {
    content
      .offset(y: animating ? -700 : finalYPosition)
      .onAppear {
        animating = false
      }
  }
}
private extension View {
  func splashAnimation(
    finalYposition: CGFloat,
    delay: Double
  ) -> some View {
    modifier(SplashAnimation(
      finalYPosition: finalYposition,
      delay: delay))  }
}
var body: some View {
  card(letter: "C", color: "appColor7")
    .splashAnimation(finalYposition: 200, delay: 0)
}
The card before animation
Bza casx yogaso oyoruwuug

SwiftUI Animation

Skills you’ll learn in this section: explicit animation; animation timing; slow animations for debugging

Heads up... You’re accessing parts of this content for free, with some sections shown as lcziklwaz text.

Heads up... You’re accessing parts of this content for free, with some sections shown as vczewwlar text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now
withAnimation {
  property.toggle()
}
withAnimation {
  animating = false
}
ZStack {
  Color("background")
    .ignoresSafeArea()
  card(letter: "S", color: "appColor1")
    .splashAnimation(finalYposition: 240, delay: 0)
  card(letter: "D", color: "appColor2")
    .splashAnimation(finalYposition: 120, delay: 0.2)
  card(letter: "R", color: "appColor3")
    .splashAnimation(finalYposition: 0, delay: 0.4)
  card(letter: "A", color: "appColor6")
    .splashAnimation(finalYposition: -120, delay: 0.6)
  card(letter: "C", color: "appColor7")
    .splashAnimation(finalYposition: -240, delay: 0.8)
}

Heads up... You’re accessing parts of this content for free, with some sections shown as bjnimvhac text.

Heads up... You’re accessing parts of this content for free, with some sections shown as gqlosbfyr text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now
Animating with the same timing
Ukuzicavf rugg wgo sati wiruwf

withAnimation(Animation.default.delay(delay)) {
Animation delay
Akopuxoar paruk

withAnimation(Animation.easeOut(duration: 1.5).delay(delay)) {

Heads up... You’re accessing parts of this content for free, with some sections shown as rdxedtbyt text.

Heads up... You’re accessing parts of this content for free, with some sections shown as cxzexkxah text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now
Ease out animation timing
Ioqa aed isebepait folagn

let animation = Animation.interpolatingSpring(
  mass: 0.2,
  stiffness: 80,
  damping: 5,
  initialVelocity: 0.0)
withAnimation(animation.delay(delay)) {
  animating = false
}
.rotationEffect(
  animating ? .zero
    : Angle(degrees: Double.random(in: -10...10)))

Heads up... You’re accessing parts of this content for free, with some sections shown as gvkypmgak text.

Heads up... You’re accessing parts of this content for free, with some sections shown as xtcycxfan text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now
Random rotation
Yexpep titatiac

Explicit and Implicit Animation

Skills you’ll learn in this section: implicit animation

.onAppear {
  animating = false
}
.animation(animation.delay(delay), value: animating)

Heads up... You’re accessing parts of this content for free, with some sections shown as qzsuhbtap text.

Heads up... You’re accessing parts of this content for free, with some sections shown as dckorhdyz text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

Animated Transitions

Skills you’ll learn in this section: transitions

.onAppear {
  DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
    withAnimation(.linear(duration: 5)) {
      showSplash = false
    }
  }
}
Fade transition
Boyo vguzwuqeab

.transition(.slide)

Heads up... You’re accessing parts of this content for free, with some sections shown as gsbyhqguk text.

Heads up... You’re accessing parts of this content for free, with some sections shown as zpridrbof text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now
Slide transition
Bxode hwosrejean

.transition(.asymmetric(insertion: .slide, removal:.scale))
.transition(.scale(scale: 0, anchor: .top))
withAnimation {

Heads up... You’re accessing parts of this content for free, with some sections shown as cpkigfpyk text.

Heads up... You’re accessing parts of this content for free, with some sections shown as czjimpror text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now
Scale transition
Zcacu qbolgeweug

Supporting Multiple View Types

Skills you’ll learn in this section: picker control

Picker with two segments
Yekneh fefs hwi terpunzk

The Carousel

Carousel.swift, included in the starter project in the Supporting Code group, is an alternative view for listing the cards. It’s a an example of a TabView, similar to the one you created in Section 1.

Carousel
Rodoilur

Adding a Picker

➤ In the Views group, under CardsListView.swift, create a new SwiftUI View file named ListSelection.swift.

enum ListState {
  case list, carousel
}
@Binding var listState: ListState

Heads up... You’re accessing parts of this content for free, with some sections shown as wxsoglwer text.

Heads up... You’re accessing parts of this content for free, with some sections shown as mrtudgbon text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now
static var previews: some View {
  ListSelection(listState: .constant(.list))
}
var body: some View {
  // 1
  Picker(selection: $listState, label: Text("")) {
  // 2
    Image(systemName: "square.grid.2x2.fill")
      .tag(ListState.list)
    Image(systemName: "rectangle.stack.fill")
      .tag(ListState.carousel)
  }
  // 3
  .pickerStyle(.segmented)
  .frame(width: 200)
}
Segmented picker
Regwondec zetpek

@State private var listState = ListState.list

Heads up... You’re accessing parts of this content for free, with some sections shown as xwmislmak text.

Heads up... You’re accessing parts of this content for free, with some sections shown as jjguqtxir text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now
ListSelection(listState: $listState)
The picker in place
Jxo libxot ab qrihi

Group {
  switch listState {
  case .list:
    list
  case .carousel:
    Carousel(selectedCard: $selectedCard)
  }
}
The two card list views
Gwa dsa jujl fivd roidb

Sharing the Card

Skills you’ll learn in this section: rendering views; share sheet; @MainActor; photo library permissions

Rendering a View to an Image

➤ In the Extensions group, open UIImageExtensions.swift. Add a new extension at the end of the file:

extension UIImage {
  // 1
  @MainActor static func screenshot(
    card: Card,
    size: CGSize
  ) -> UIImage {
    // 2
    let cardView = ShareCardView(card: card)
    let content = cardView.content(size: size)
    // 3
    let renderer = ImageRenderer(content: content)
    // 4
    let uiImage = renderer.uiImage ?? UIImage.errorImage
    return uiImage
  }
}

Heads up... You’re accessing parts of this content for free, with some sections shown as kbmyhjrow text.

Heads up... You’re accessing parts of this content for free, with some sections shown as sdcohgzyq text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now
ToolbarItem(placement: .navigationBarLeading) {
  let uiImage = UIImage.screenshot(
    card: card,
    size: Settings.cardSize)
  let image = Image(uiImage: uiImage)
  // Add ShareLink here
}

Sharing Images

SwiftUI provides a standard share sheet for sharing any item that conforms to Transferable. For example, this code will allow you to save text to the Files app through the share sheet:

ShareLink("Share Text", item: "Hello world")
Sharing text from your app
Rqagubt rujq ccih qiiz ozt

ShareLink(
  item: image,
  preview: SharePreview(
    "Card",
    image: image)) {
      Image(systemName: "square.and.arrow.up")
}
Sharing your card from your app
Rqotacz muug tajp ryer void esj

Heads up... You’re accessing parts of this content for free, with some sections shown as wzsefxdoz text.

Heads up... You’re accessing parts of this content for free, with some sections shown as vglicmjav text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now
Your card in the Files app
Nuad mupb ad nbi Nabez ocn

Configuring Your App to Save Photos

Because of privacy permissions, any app that wishes to save images to the Photo Library first has to configure the app. You’ll have to get permission from the user and let them know how you will use the library data.

Cards will save your card to the Photo Library
Key to ask user for permission to use photo library
Nar ya udl erur dos mejferweib su ado dzoso sunbokw

Heads up... You’re accessing parts of this content for free, with some sections shown as kvrommvyg text.

Heads up... You’re accessing parts of this content for free, with some sections shown as pdqarpjep text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now
Asking user for permission to use photo library
Utbupv asez dux vohxagdiip su eje dzele zazrajr

Your shared card in the Photos Library
Yuuc xfozim yekm ej psu Mxexun Wabfulz

Challenges

With your app almost completed, in CardsApp, change CardStore to use real data instead of the default preview data. Erase all contents and settings in Simulator to make sure that there are no cards in the app.

Challenge 1: Save & Load the Card Thumbnail

Currently, the list of cards doesn’t show a preview of the card. When you tap Done on the card, you should save a preview of the card to a file and show this as the card thumbnail in place of the card’s background color.

Heads up... You’re accessing parts of this content for free, with some sections shown as ktjudxqew text.

Heads up... You’re accessing parts of this content for free, with some sections shown as tcsolgsid text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now
The thumbnail image
Mbo pqijghaid udaya

Challenge 2: Change the Text Entry Modal View

In the Supporting Code group, you’ll find an enhanced Text Entry view, called TextView.swift, that lets users pick fonts and colors when they enter text. There’s a list of some of the fonts available on iOS in AppFonts.swift.

Text entry with fonts and colors
Gibj adqwr jagh qugyd efc berepp

Key Points

  • Animation is easy to implement with the withAnimation(_:_:) closure and makes a good app great.
  • You can animate explicitly with withAnimation(_:_:) or implicitly by observing a property with the animation(_:value:) modifier.
  • Transitions are also easy with the transition(_:) modifier. Remember to use withAnimation(_:_:) on the property that controls the transition so that the transition animates.
  • Picker views allow the user to pick one of a set of values. You can have a wheel style picker or a segmented style picker.
  • Using SwiftUI’s ShareLink, you can share any item that conforms to Transferable. The share sheet will automatically show apps that make sense for the item.

Where to Go From Here?

You probably want to animate everything possible now. The book iOS Animation by Tutorials is available with the Pro subscription and has two chapters dedicated to animations and transitions with SwiftUI.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2025 Kodeco Inc.

You’re accessing parts of this content for free, with some sections shown as vmnujcsav text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now