Chapters

Hide chapters

SwiftUI by Tutorials

Fifth Edition · iOS 16, macOS 13 · Swift 5.8 · Xcode 14.2

Before You Begin

Section 0: 4 chapters
Show chapters Hide chapters

20. View Transitions & Charts
Written by Bill Morefield

In Chapter 19: “Animations”, you explored adding animation to your app. You probably noticed one distinct element you did not animate — views. Open this chapter’s starter project, tap Flight Status, and then tap any flight. When you tap the Show Terminal Map button, the view appears, and the animations of the airplane shapes occur.

The display of the view doesn’t show any animation. In SwiftUI, views use a subset of animation called view transitions. This chapter teaches you to apply animations to your app views.

Animating View Transitions

Note: Transitions sometimes render incorrectly in the preview. If you don’t see what you expect, try running the app in the simulator or a device.

The first thing you should understand is the difference between a state change and a view transition. A state change occurs when an element on a view changes. A transition involves changing the visibility or presence of a view.

In the starter project for this chapter, open FlightInfoPanel.swift and look for the Text view between the icons in the button that shows the terminal map.

Right now, it looks like this:

Text(showTerminal ? "Hide Terminal Map" : "Show Terminal Map")

This code shows a state change. While the text displayed by the view can change, it remains in the same view. Change the code to the following:

if showTerminal {
  Text("Hide Terminal Map")
} else {
  Text("Show Terminal Map")
}

Now you have a view transition. One view is replaced by a different one when the showTerminal state variable changes.

Transitions are specific animations that occur when showing and hiding views. You can confirm this by running the app, tapping Flight Status, then tapping any flight. Tap the button to show and hide the terminal map a few times, and notice how the view disappears and reappears. By default, views transition on and off the screen by fading in and out.

Much of what you’ve already learned about animations work with transitions. As with animation, the default transition is only a single possible animation.

Change the code that shows the button text to:

Group {
  if showTerminal {
    Text("Hide Terminal Map")
  } else {
    Text("Show Terminal Map")
  }
}
.transition(.slide)

You use the Group View to wrap the view change. You then apply the transition to the group. Run the app, go back to the page, and you’ll see … something odd. The old view slides away but doesn’t disappear for a few seconds. Since transitions are a type of animation, you must use the withAnimation(_:value:) function around the state change, or SwiftUI won’t show the specified transition. You already did this back in Chapter 19: “Animations” as the action for the button is:

Button {
  withAnimation(
    .spring(
      response: 0.55,
      dampingFraction: 0.45,
      blendDuration: 0
    )
  ) {
    showTerminal.toggle()
  }
} label: {

As a result, SwiftUI applies both animation and transition. You’ll often run into this type of issue when working with animations and transitions, which makes keeping animations with the UI element to change more manageable. For now, change the button to use the withAnimation method without an animation type.

Button {
  withAnimation {
    showTerminal.toggle()
  }
} label: {

There’s no animation specified in the withAnimation(_:value:) call. It’s not needed since you set it at the individual elements in the view. To keep the animations on the plane icons, add the following code after each Image view:

.animation(
  .spring(
    response: 0.55,
    dampingFraction: 0.45,
    blendDuration: 0
  ),
  value: showTerminal
)

Run the app, and bring up the details for a flight. Now tap to show the terminal map, and you’ll see that the view now slides in from the leading edge. When you tap the button again, you’ll see the view slide off the trailing edge. These transitions handle cases where the text direction reads right-to-left for you.

The animation occurs when SwiftUI adds the view. The framework creates the view and slides it in from the leading edge. It also animates the view off the trailing edge and removes it to no longer take up resources.

You could create a similar result with animations, but you need to handle these extra steps yourself. The built-in transitions make it much easier to deal with view animations.

View Transition Types

The default transition type changes the opacity of the view when adding or removing it. The view goes from transparent to opaque on insertion and from opaque to transparent on removal. You can create a more customized version using the .opacity transition.

You also used a slide transition that inserts a view from the leading edge and removes it off the trailing edge. When added or removed, the .move(edge:) transition moves the view from or to a specified edge. To see the view move to and from the bottom, change the transition to:

.transition(.move(edge: .bottom))

Run the app and tap Flight Status. Now tap any flight and then tap Show Terminal Map. You’ll see the new view slide in from the bottom and the vanishing view slide off toward the bottom.

A move transition to the bottom
A move transition to the bottom

The other edges are .top, .leading and .trailing.

Beyond moving, transitions can also animate views to appear on the screen. A .scale() transition causes the view to expand when inserted from a single point or to collapse when removed to a single point at the center. You can optionally specify a scale factor parameter for the transition. The scale factor defines the ratio of the size of the initial view. A scale of zero provides the default transition to a single point. A value less than one causes the view to expand from that scaled size when inserted or collapse to it when removed. Values greater than one work the same. However, the view at the end of the transition is larger than the final view.

You can also specify an anchor parameter for the point on the view where the transition centers. An enumeration provides constants for the view’s corners, sides and center. You can also provide a custom offset.

The final transition type allows you to specify an offset either as a CGSize or a pair of Length values. The view moves from that offset when inserted and toward it when removed.

Exercise: As with animations, the best way to see how transitions work is to try them. Take each transition and use it in place of .slide transition in the FlightTerminalMap. Toggle the view on and off to see how the animation works as the view appears and leaves.

Extracting Transitions From the View

You can extract your transitions from the view as you did with animations. You don’t add it at the struct level as with an animation but at the file scope. At the top of FlightInfoPanel.swift add the following:

extension AnyTransition {
  static var buttonNameTransition: AnyTransition {
    .slide
  }
}

This extension declares your transition as a static property of AnyTransition. Now use it on the TerminalMapView. Change the conditional showing TerminalMapView to:

if showTerminal {
  TerminalMapView(flight: flight)
    .transition(.buttonNameTransition)
}

Run the app and tap Flight Status. Now tap any flight and then tap Show Terminal Map. You’ll see it works as the first transition example did, except it moves in from the leading edge and out to the trailing edge.

Horizontal slide transition
Horizontal slide transition

Async Transitions

SwiftUI lets you specify different transitions when adding and removing a view. Change the static property to:

extension AnyTransition {
  static var buttonNameTransition: AnyTransition {
    let insertion = AnyTransition.move(edge: .trailing)
      .combined(with: .opacity)
    let removal = AnyTransition.scale(scale: 0.0)
      .combined(with: .opacity)
    return .asymmetric(insertion: insertion, removal: removal)
  }
}

You combine the two transitions with the combined(with:) modifier. Preview this new transition. You’ll see the view moves in from the trailing edge as it fades in. When SwiftUI removes the view, it will shrink to a point while fading out.

An asymmetric transition
An asymmetric transition

Now that you’ve learned about animation and transitions, you’ll see how to link transitions into more complex animations.

Linking View Transitions

The second release of SwiftUI added many features. You’ll use the matchedGeometryEffect method in this section. It allows you to synchronize the animations of multiple views. Think of it as a way to tell SwiftUI to connect the animations between two objects.

Open AwardsView.swift under the AwardsView group. This view displays awards using the grid you developed in Chapter 16: “Grids”. When you tap on an award, it transitions to a new view displaying its details. You’ll change it to pop-up the award details over the grid.

Add the following code to the top of the view after the flightNavigation EnvironmentObject:

@State var selectedAward: AwardInformation?

When the user taps an award, you’ll store it in this optional state variable. Otherwise, the property will be nil. Since that tap action takes place in a subview, you’ll need to pass this into that subview.

Open AwardGrid.swift. Add the following binding after the awards property:

@Binding var selected: AwardInformation?

Using this binding, you’ll pass the state from the AwardsView to the AwardGrid. Change the contents of the ForEach loop to:

AwardCardView(award: award)
  .foregroundColor(.black)
  .aspectRatio(0.67, contentMode: .fit)
  .onTapGesture {
    selected = award
  }

You’ve removed the navigation link and instead added an onTapGesture(count:perform:) modifier to set the binding to the tapped award.

You also need to update the preview to add the new binding parameter. Change it to:

AwardGrid(
  title: "Test",
  awards: AppEnvironment().awardList,
  selected: .constant(nil)
)

Now, go back to AwardsView.swift and change the view to:

ZStack {
  // 1
  if let award = selectedAward {
    // 2
    AwardDetails(award: award)
      .background(Color.white)
      .shadow(radius: 5.0)
      .clipShape(RoundedRectangle(cornerRadius: 20.0))
      // 3
      .onTapGesture {
        selectedAward = nil
      }
      // 4
      .navigationTitle(award.title)
  } else {
    ScrollView {
      LazyVGrid(columns: awardColumns) {
        AwardGrid(
          title: "Awarded",
          awards: activeAwards,
          selected: $selectedAward
        )
        AwardGrid(
          title: "Not Awarded",
          awards: inactiveAwards,
          selected: $selectedAward
        )
      }
    }
    .navigationTitle("Your Awards")
  }
}
.background(
  Image("background-view")
    .resizable()
    .frame(maxWidth: .infinity, maxHeight: .infinity)
)

You now have a ZStack that shows one of two views depending on the if statement results. The code inside the else condition didn’t change other than passing the binding to the selectedAward state variable. There are some changes worth noting:

  1. The first change is that you attempt to unwrap the state selectedAward state variable. If that fails, you show the grid as before in the else part of the statement.
  2. If the unwrapping succeeded, you display the AwardDetails view that was previously the NavigationLink target.
  3. When the user taps the view, you set the selectedAward state variable back to nil. This change removes the AwardDetails view and displays the grid.
  4. You set the title to the name of the current award.

Run the app. Tap Your Awards and then tap any award in the grid. You’ll see the view flips to the large details display for the award. Tap AwardDetails view, and the grid appears again.

The transition is abrupt. You know you can fix that by adding a view transition. Find the onTapGesture modifier in AwardsView, under comment three, and change it to:

.onTapGesture {
  withAnimation {
    selectedAward = nil
  }
}

Hopefully, you remember from earlier in this chapter this tells SwiftUI to animate events caused by the state change in the closure. For the other end of the transition in AwardGrid.swift, find the onTapGesture modifier in AwardGrid and change it to:

.onTapGesture {
  withAnimation {
    selected = award
  }
}

Run the app, and you’ll find the change works better. Now you have a nice fade-out/fade-in effect that smooths the previously harsh transitions between the views. There’s still no sense connecting the changes. The two views that you’re transitioning between are separate with no connection. That’s where matchedGeometryEffect(id:in:properties:anchor:isSource:) comes in. It lets you connect the two view transitions.

You must specify the first two parameters. The id works much like other ids you’ve encountered in SwiftUI. It uniquely identifies a connection, so giving two items the same id links their animations. You pass a Namespace to the in property. The namespace groups related items and the two together define unique links between views.

Creating a namespace is simple. At the top of AwardsView, add the following code after the selectedAward state variable.

@Namespace var cardNamespace

Now you have a unique namespace for the method.

After the onTapGesture modifier is attached to AwardDetails, add the following:

.matchedGeometryEffect(
  id: award.hashValue,
  in: cardNamespace,
  anchor: .topLeading
)

You use the existing hashValue property as the identifier along with your created namespace. You can use any identifier as long as it’s unique within the namespace and consistent. You also specify the anchor parameter to specify a location in the view used to produce the shared values. It’s not always needed, but in this case, it improves the animation.

Now you have one side set up but need to link the state change in the subview. To do so, you need to pass the namespace into that view. Change the AwardGrids inside the LazyVGrid to add it as a parameter:

AwardGrid(
  title: "Awarded",
  awards: activeAwards,
  selected: $selectedAward,
  namespace: cardNamespace
)
AwardGrid(
  title: "Not Awarded",
  awards: inactiveAwards,
  selected: $selectedAward,
  namespace: cardNamespace
)

Now open AwardGrid. First, you need to add a property to capture the passed namespace. After the selected binding, add the following code:

var namespace: Namespace.ID

When you pass a namespace into a view, it comes in as a Namespace.ID type.

You also need to update to preview to pass this new parameter. Add the following to the top of AwardGrid_Previews struct:

@Namespace static var namespace

Update the preview body to:

AwardGrid(
  title: "Test",
  awards: AppEnvironment().awardList,
  selected: .constant(nil),
  namespace: namespace
)

Now, add the following code after the onTapGesture(count:perform:) call:

.matchedGeometryEffect(
  id: award.hashValue,
  in: namespace,
  anchor: .topLeading
)

Notice this uses the namespace you passed in and therefore is the same namespace as the parent view. Again, you also use the hashValue property on the award, the same as used in the parent view. With the two parameters matching, SwiftUI knows to link the transitions.

Run the app now. When you tap an award in the small grid, it shifts and expands while changing to the AwardDetails view. Similarly, when you tap AwardDetails view, it appears to shrink and move back to the smaller view inside the grid.

In motion matched geometry effect
In motion matched geometry effect

Adding matchedGeometryEffect() only arranges for the geometry of the views to be linked. The usual transition mechanisms applied to the views still take place during the transition.

Displaying Charts

In Chapter 18: “Drawing & Custom Graphics”, you created a pie chart using SwiftUI path components. Earlier editions of this book also walked through creating charts using shapes and paths. SwiftUI provides an easier way to visualize data for your uses — Swift Charts.

Swift Charts allows you to use the declarative syntax of SwiftUI to create charts. Like most SwiftUI components, it supports dynamic font sizes, various screen sizes and accessibility.

Run the app and tap Search Flights, then tap the name of any flight. From the flight summary, tap On-Time History. You’ll see a list showing the recent history of how punctual the flight has been for the last ten days.

Note: The first flight, US 810 to Denver, will provide a suitable range of delays for this section.

Flight delay history
Flight delay history

Looking at a few data points can be enlightening, but staring at a long list of numbers isn’t the best way to gain insight. A list of numbers isn’t the easiest way to display how warm a particular month was or to determine the driest months.

Most people have an easier time grasping information presented graphically. A chart can provide a graphic representation of data designed to inform the viewer. In this section, you’ll display this information in a bar chart using Swift Charts.

Creating a Bar Chart

A bar chart provides a bar for each data point. Each bar’s length represents the numerical value and can run horizontally or vertically to suit your needs.

Create a new SwiftUI view named HistoryChartView.swift under the SearchFlights group. First, add a second import to the top of the file:

import Charts

You must import the Swift Charts module in addition to SwiftUI. Now add a new property to the view:

var flightHistory: [FlightHistory]

This property will store an array with the flight history you display the chart for. Update the body of the preview to:

HistoryChartView(
  flightHistory: FlightData.generateTestFlight(date: Date()).history
)

This code provides sample history data for the preview.

SwiftUI creates charts out of SwiftUI views. As you might expect, you begin with a Chart view.

Change the body of the view to:

// 1
Chart {
  // 2
  ForEach(flightHistory, id: \.self) { history in
    // 3
    BarMark(
      // 4
      x: .value("Days Ago", history.day),
      y: .value("Minutes", history.timeDifference)
    )
  }
}

Since this is your first chart, here’s what these parts do:

  1. The Chart view defines the chart. You’ll provide details on the chart within the view’s closure.
  2. You have a data set to chart in the flightHistory array and use a ForEach loop to go through it.
  3. The BarkMark represents data you want to display in a bar chart.
  4. You provide the x and y components of the chart. Here, the x parameter indicates how many days ago the current history displays. The y parameter shows the delay, which can be a negative number. The x component displays horizontally across the chart, while the y component displays vertically.

Now go to FlightTimeHistory.swift and replace the ScrollView and its closure with:

HistoryChartView(flightHistory: flight.history)
  .foregroundColor(.black)
  .background {
    Color.white
  }

You use the new view you created, passing in the history of the current flight. You apply a black foreground and white background because the default colors don’t work well with a darker background like this view has. Don’t worry; you’ll come back to that late in the chapter.

Run the app, tap Search Flights and then tap a flight. You’ll see the new bar chart.

Your first bar chart
Your first bar chart

You can see the days increasing to the right across the chart with longer delays creating long vertical bars.

Traditional bar charts plot horizontally like this with vertical bars. Because mobile devices limit horizontal space, shifting to a vertical bar chart with the bars drawn horizontally often better uses the available space.

You may think that you can do this by simply swapping the parameters. Try it, change the parameters of BarkMark to:

x: .value("Minutes", history.timeDifference),
y: .value("Days Ago", history.day)

Now run the app and view the history chart for a flight. You’ll see … something different from what you expected.

Incorrect vertical bar chart
Incorrect vertical bar chart

There are three types of data supported by Swift Charts: quantitative, categorical, and temporal. Doubles and Ints both fall into the first quantitative category. To get the desired effect, the y parameter must be a categorical value such as a String. Change the y property above to:

y: .value("Days Ago", "\(history.day) day(s) ago")

Note: Temporal values relate to time, but you won’t use those in this chapter.

The change produces a String that describes the day instead of the Int. Run the app, and you’ll see what you probably expected earlier.

Vertical bar chart
Vertical bar chart

Now that you have a bar chart, you can customize it to provide additional information to the user.

Bar Chart Colors and Annotations

Swift charts will automatically provide colors for the chart unless you specify one. You set the bars to black by applying the .foregroundColor(.black) modifier onto the HistoryChartView view in FlightTimeHistory.swift. Adding color to the bars provides an excellent way to convey additional information. In this section, you’ll adjust the bars so the color reflects the length of the delay.

In HistoryChartView.swift, add the following modifier to the BarMark view:

.foregroundStyle(history.delayColor)

You set the bar’s color to the delayColor property from the history. Run the app and view a flight history.

Bar chart with color reflecting the length of the delay
Bar chart with color reflecting the length of the delay

The bars now transition from green, yellow, orange and red as the delays get longer. A solid color fill works well for many cases, but you can apply any SwiftUI style to the bar.

Since the colors have a defined transition, making it a good place to use a gradient. Add the following method after the flightHistory property:

func barGradientColors(_ history: FlightHistory) -> Gradient {
  if history.status == .canceled {
    return Gradient(
      colors: [
        Color.green,
        Color.yellow,
        Color.red,
        Color(red: 0.5, green: 0, blue: 0)
      ] )
  }
  if history.timeDifference <= 0 {
    return Gradient(colors: [Color.green])
  }
  if history.timeDifference <= 15 {
    return Gradient(colors: [Color.green, Color.yellow])
  }
  return Gradient(
    colors: [Color.green, Color.yellow, Color.red]
  )
}

This method returns a gradient consisting of colors from green through the other colors to the color matching the length of the delay. Now you can use the gradient by updating the foregroundStyle(_:) modifier on the BarMark to:

.foregroundStyle(
  // 1
  LinearGradient(
    gradient: barGradientColors(history),
    // 2
    startPoint: .leading,
    endPoint: .trailing
  )
)

You use the barGradientColors(_:) method to get the colors for the gradient and the following:

  1. A linear gradient provides a smooth transition between colors along a straight line through an object, in this case, the rectangle. SwiftUI provides other gradients that change from a central point or sweep around a central point.
  2. The values for startPoint and endPoint use a UnitPoint struct. This struct scales a range of values into a zero to one range, making it easier to define a range without worrying about the exact values. UnitPoints origin coordinate is at (0, 0) in the top-left corner of the shape and increases to the right and downward to (1.0, 1.0). The .leading and .trailing static types correspond to points at (0, 0.5) and (1.0, 0.5).

Run the app, and your bars now transition from green to the appropriate color to match the delay. The information is the same, but the gradient makes it feel more dynamic and better conveys the progression of longer delays.

Each bar now has a gradient
Each bar now has a gradient

With the colors matching the delay length, you’ll now add an annotation to each bar of the chart displaying the length of the delay. After the closing parenthesis of the foregroundStyle(_:) modifier.

.annotation(position: .overlay) {
  Text(history.flightDelayDescription)
    .font(.caption)
}

The annotation(position:alignment:spacing:content:) modifier allows you to add an annotation to each bar. Here you specify the overlay position, showing the annotation on top of the bar. You define the annotation inside the closure. Here that displays the flightDelayDescription property for the current bar in the caption font.

Run the app, and your new annotations will appear with each bar.

Text annotations added to each bar in the chart
Text annotations added to each bar in the chart

Defining Chart Axes

Swift Charts usually provides a good scale for your chart. In this case, there’s a lot of wasted space, especially for negative minute lengths that will never happen. Add the following code after the Chart closure to fix this.

// 1
.chartXAxis {
  // 2
  AxisMarks(values: [-10, 0, 10, 20, 30, 40, 50, 60]) { value in
    // 3
    AxisGridLine(
      centered: true,
      stroke: StrokeStyle(lineWidth: 1.0, dash: [5.0, 5.0])
    )
  }
}

Here’s how this code defines the axis:

  1. You use the chartXAxis(content:) to customize the x-axis of a chart. SwiftUI will no longer provide any default axis, so you must specify all elements for the axis.
  2. This AxisMarks view tells SwiftUI to draw axis marks. You use the values parameter to pass an array of the values where you want to draw a grid line. The closure will be called for each value to show a grid line, in this case, for each value in the values array. The value parameter will be passed to the closure containing the current axis mark. You’ll see how to use this later.
  3. The AxisGridLine lets you define the characteristics of the grid line. Setting centered to true centers the grid line between the two axis values. You specify the attributes for the line drawn to the stroke parameter. Here, you draw a one-point wide dashed line alternating between five points long painted and unpainted segments.

Run the app, and you’ll see the changes to the chart.

The chart after specifying a custom axis
The chart after specifying a custom axis

The chart now tightens on the data with less empty space to help slight differences stand out. Showing more marks helps identify values from the bars without crowding the chart.

Now that you’re looking at customization, this makes a good point to address that many default colors don’t work well on a dark background. Add the following modifier to AxisGridLine:

.foregroundStyle(.white.opacity(0.8))

This modifier will change the text to white. The opacity(_:) modifier mutes the color and helps differentiate between the chart data and the grid line.

Now go to FlightTimeHistory.swift and remove the background(alignment:content:) and foregroundColor(_:) modifiers on HistoryChartView. Change the view to:

HistoryChartView(flightHistory: flight.history)
  .frame(height: 600)

This change sets a fixed height on the chart to space it out. Now wrap the HistoryChartView and HistoryPieChart inside a ScrollView to keep the bar and pie charts visible on smaller devices.

Run the app, and you’ll see the new layout. Notice how adding the foregroundStyle(_:) modifier lets it appear against the dark background, unlike the defaults.

Start of adjust chart to light background
Start of adjust chart to light background

In the next section, you’ll finish customizing the chart to show up on this dark background.

Customizing the Chart Colors

You changed the color of the grid lines but not the labels on those grid lines. Add the following code after the foregroundStyle(_:) modifier to AxisGridLine inside the closure for AxisMarks:

AxisValueLabel() {
  // 1
  if let value = value.as(Int.self) {
    // 2
    Text(value, format: .number)
      .foregroundColor(Color.white.opacity(0.8))
  }
}

This AxisValueLabel tells SwiftUI you want to customize the label drawn for the value. Here’s how you do that:

  1. You attempt to unwrap the current value as an Int.
  2. If step one succeeds, you create a Text view to show the value, and it displays in the same slightly transparent white that you did to the axis line.

Run the app to see the grid labels now appear.

Chart with grid labels
Chart with grid labels

Now, you’ll make the changes to the Y axis. Add the following code after the chartXAxis view:

.chartYAxis {
  // 1
  AxisMarks(values: .automatic) { value in
    AxisGridLine(centered: false, stroke: StrokeStyle(lineWidth: 1.0))
      .foregroundStyle(Color.white.opacity(0.8))
    AxisValueLabel() {
      // 2
      if let value = value.as(String.self) {
        Text(value)
          .font(.footnote)
          .foregroundColor(Color.white.opacity(0.8))
      }
    }
  }
}

The code is almost identical to what you’ve already seen. But there are two differences to note:

  1. You pass .automatic to values to let SwiftUI automatically provide axis values, which is the default behavior.
  2. You unwrap the value as a String and use the footnote font.

Next, you’ll add a label for the Y axis. Add the following code after chartYAxis:

.chartYAxisLabel {
  Text("Delay in Minutes")
    .foregroundColor(.white)
    .font(.callout)
}

Using the’ callout’ font, you create a white label for the Y axis. Run the app to see that the chart shows clearly against the dark background.

The chart customized to work on a dark background
The chart customized to work on a dark background

The chart looks good, but because the axis lines you specified are near the minimum and maximum values, the bars bump right against the chart’s edges. To give them some space, add the following code after chartYAxisLabel:

.chartXScale(domain: -18...63)

The chartXScale(domain:type:) modifier specifies a scale for the X axis. You set a range from three below the minimum and maximum values expected for the chart. If you have data points beyond this range, Swift Charts will cut them off the chart.

Note: As of Xcode 14.2, a bug causes this modifier to break the preview for HistoryChartView. It’ll show correctly on the FlightTimeHistory preview, the simulator, or a device.

Adding a margin to the sides of the chart using the range
Adding a margin to the sides of the chart using the range

Key Points

  • Transitions are a subset of animations applied when SwiftUI shows or hides a view.
  • Using matchedGeometryEffect lets you link view transitions into a single animation.
  • You create charts using the Swift Charts module and the Chart view.
  • You can customize the axis for a chart using the chartXAxis and chartYAxis views.
  • Within these axis methods, you can use AxisMarks to define the grid line values, use AxisGridLine to set the properties of the line and use AxisValueLabel to control the axis label’s appearance.
  • You can use chartXScale and chartYScale to define the range shown on the chart.

Where to Go From Here?

Most references in Chapter 19: “Animations” also apply to view transitions.

Chapter 3: “Transitions” of SwiftUI Animations by Tutorials (https://www.kodeco.com/books/swiftui-animations-by-tutorials/) discussed transitions in more detail.

Swift Charts Tutorial: Getting Started (https://www.kodeco.com/36025169-swift-charts-tutorial-getting-started) covers vertical bar charts and line charts.

For examples of drawing charts without Swift Charts, you can view Chapter 18 in earlier editions of this book or SwiftUI Tutorial for the tutorial iOS: Creating Charts (https://www.kodeco.com/6398124-swiftui-tutorial-for-ios-creating-charts).

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.
© 2026 Kodeco Inc.