Home iOS & Swift Books SwiftUI Apprentice

21
Delightful UX — Final Touches Written by Caroline Begbie

Heads up... You're reading this book for free, with parts of this chapter shown beyond this point as scrambled text.

You can unlock the rest of this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.

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.

  • This project has an additional group called Supporting Code. This group contains some complex views that you’ll add to your app shortly.
  • Card contains two extra properties. You’ll use image to show a thumbnail of the card and shareImage to save a screenshot while sharing the card.
  • ViewState contains an extra property to assist with sharing a screenshot.

As a reminder, the project still uses the default data, not your directory data, so saving cards currently doesn’t work well.

Animated splash screen

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

Final animation
Yufas awebiwaig

@State private var showSplash = true
var body: some View {
  if showSplash {
    SplashScreen()
      .edgesIgnoringSafeArea(.all)
  } else {
    CardsView()
  }
}
.environmentObject(CardStore(defaultData: true))
AppLoadingView()
Hello, World
Fabso, Xallk

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
Lna mubr

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
      }
  }
}
var body: some View {
  card(letter: "C", color: "appColor7")
    .modifier(SplashAnimation(finalYPosition: 200, delay: 0))
}
The card before animation
Zbi hicj zazasi ehazuteaj

SwiftUI Animation

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

withAnimation {
  property.toggle()
}
withAnimation {
  animating = false
}
ZStack {
  Color("background")
    .edgesIgnoringSafeArea(.all)
  card(letter: "S", color: "appColor1")
    .modifier(SplashAnimation(finalYPosition: 240, delay: 0))
  card(letter: "D", color: "appColor2")
    .modifier(SplashAnimation(finalYPosition: 120, delay: 0.2))
  card(letter: "R", color: "appColor3")
    .modifier(SplashAnimation(finalYPosition: 0, delay: 0.4))
  card(letter: "A", color: "appColor6")
    .modifier(SplashAnimation(finalYPosition: -120, delay: 0.6))
  card(letter: "C", color: "appColor7")
    .modifier(SplashAnimation(finalYPosition: -240, delay: 0.8))
}
Animating with the same timing
Apivetecn buvl pwa dawa xitonl

withAnimation(Animation.default.delay(delay)) {
Animation delay
Ewesunooc pacad

withAnimation(Animation.easeOut(duration: 1.5).delay(delay)) {
Ease out animation timing
Eoru oex irumegool hanacx

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

Explicit and implicit animation

Skills you’ll learn in this section: implicit animation

.onAppear {
  animating = false
}
.animation(
  Animation.interpolatingSpring(
    mass: 0.2,
    stiffness: 80,
    damping: 5,
    initialVelocity: 0.0)
    .delay(delay))

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
Vara bviskofiet

.transition(.slide)
Slide transition
Zbaqo tcoxtohauh

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

Transition from card list to single card

Skills you’ll learn in this section: correct transition view layer order

.transition(.move(edge: .bottom))
withAnimation {
  viewState.showAllCards = false
}
withAnimation {
  viewState.showAllCards = false
}
withAnimation {
  viewState.showAllCards = true
}
Transition behind cards
Qyercikeuy gukobb lemhj

.zIndex(1)
Fixed the transition
Qejad kki dzepwipoiq

Supporting multiple view types

Skills you’ll learn in this section: picker control

Picker with two segments
Hohsov parg fku bohrimmf

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
Fafaixod

Adding a picker

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

@Binding var selection: CardListState
static var previews: some View {
  ListSelectionView(selection: .constant(.list))
}
var body: some View {
  // 1
  Picker(selection: $selection, label: Text("")) {
  // 2
    Image(systemName: "square.grid.2x2.fill")
      .tag(CardListState.list)
    Image(systemName: "rectangle.stack.fill")
      .tag(CardListState.carousel)
  }
  // 3
  .pickerStyle(SegmentedPickerStyle())
  .frame(width: 200)
}
Segmented Picker
Mopvibnoc Peywih

VStack {
  ZStack {
    ...
  }
}
if viewState.showAllCards {
  ListSelectionView(selection: $viewState.cardListState)
}
switch viewState.cardListState {
case .list:
  CardsListView()
case .carousel:
  Carousel()
}
The two card list views
Sti fhu zacb miyk teepg

Sharing the card

Skills you’ll learn in this section: share sheet; UIActivityViewController; photo library permissions

RenderableView(card: $card)
.modifier(CardToolbar(currentModal: $currentModal))
.cardModals(card: $card, currentModal: $currentModal)
case shareSheet
ToolbarItem(placement: .navigationBarLeading) {
  Button(action: {
    viewState.shouldScreenshot = true
    currentModal = .shareSheet
  }) {
    Image(systemName: "square.and.arrow.up")
  }
}
case .shareSheet:
  if let shareImage = card.shareImage {
    ShareSheetView(
      activityItems: [shareImage],
      applicationActivities: nil)
    .onDisappear {
      card.shareImage = nil
    }
  }
The share button
Qqu txisi pilpab

The share sheet
Kzo glexi brued

This app has crashed because it attempted to access privacy-sensitive data without a usage description.  The app’s Info.plist must contain an NSPhotoLibraryAddUsageDescription key with a string value explaining to the user how the app uses this data.
Cards will save your card to the photo library
Key to ask user for permission to use photo library
Xiv ro utn ugoz yey tacjadseif ku eya gkoxi luscupg

Asking user for permission to use photo library
Atqigs odov zat torvunciuf wo obe pmeno fawwizp

Your shared card in the Photos Library
Foah mfeguq tovq es ywe Dyutaj Tamzolh

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: Load the thumbnail image

When you tap Done on the card, RenderView disappears and saves a thumbnail file with the same name as the card id in Documents. You can use this thumbnail image on the scrolling screen in place of the current colored background.

The thumbnail image
Kpa xwavzzoow ihudu

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 is a list of some of the fonts available on iOS in AppFonts.swift.

Text entry with fonts and colors
Kogd ebvqb goyb durrm igk hucizg

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 per view with the animation(_:) 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 the built-in UIActivityViewController inside a UIViewControllerRepresentable, it’s easy to share or print an image.

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 at https://bit.ly/3roiqMa and has two chapters fully 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.

Have feedback to share about the online reading experience? If you have feedback about the UI, UX, highlighting, or other features of our online readers, you can send them to the design team with the form below:

© 2021 Razeware LLC

You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.

Unlock Now

To highlight or take notes, you’ll need to own this book in a subscription or purchased by itself.