Data Management & Optimization

Apr 4 2025 · Swift 5.10, iOS 17.0, Xcode 15.4

Lesson 02: Data Transformation & Migration

Demo

Episode complete

Play next episode

Next
Transcript

Demo

In this demo, you’ll update TheMet app to migrate the query storage away from App Storage and instead use Swift Data.

Open TheMet app in the Starter folder.

You’re going to work through a hypothetical scenario. The business has decided it would be a good business case to let users see their previous searches. After doing a review of the current architecture, it became apparent moving away from AppStorage and to Swift Data would be required.

Do that work now. First, create a new file called MetQuery to store the Swift Data model and give it a property to store the query string:

import Foundation
import SwiftData

@Model
class MetQuery {
  var query: String

  init(query: String) {
    self.query = query
  }
}

Next, open TheMetApp and add a .modelContainer to the WindowGroup so the group knows how to handle MetQuery objects.

import SwiftUI
import SwiftData

@main
struct TheMetApp: App {
  var body: some Scene {
    WindowGroup {
      MetView().modelContainer(for: MetQuery.self)
    }
  }
}

Next, update MetView so it reads and writes queries to Swift Data. Open the file, and at the top, add three new properties and comment out the lastQuery property:

@Environment(\.modelContext) private var modelContext

@Query private var lastMetQueries: [MetQuery]

@State private var currentMetQuery = ""

//@AppStorage("lastquery") private var lastQuery = "rhino"

You’ll see that Xcode begins to show errors. You’ll begin to fix them now. On line 53, update the text to use currentMetQuery to populate the text component.

Text("You searched for '\(currentMetQuery)'")
  .padding(5)
  .background(Color.metForeground)
  .cornerRadius(10)

On line 76, inside the button action handler, add a call to write a MetQuery using the model context using currentMetQuery:

Button("Search the Met") {
  modelContext.insert(MetQuery(query: currentMetQuery))
  showQueryField = true
}

On lines 90 and 96, update both fields to use currentMetQuery.

.alert(
  "Search the Met",
  isPresented: $showQueryField,
  actions: {
    TextField("Search the Met", text: $currentMetQuery)
    Button("Search") {
      fetchObjectsTask?.cancel()
      fetchObjectsTask = Task {
        do {
          store.objects = []
          try await store.fetchObjects(for: currentMetQuery)
        } catch {}
      }
    }
})

On line 116, inside the .task modifier, update the fetchObjects function to use currentMetQuery.

.task {
  do {
    try await store.fetchObjects(for: currentMetQuery)
  } catch {}
}

Finally, underneath the .task modifier, add an .onAppear modifier. This will run when MetView first appears, trying to retrieve the last saved query from Swift Data.

A default string is provided if no saved queries are available.

.onAppear {
  currentMetQuery = lastMetQueries.last?.query ?? "rhino"
}

With those changes, you have now migrated the app from App Storage to Swift Data. If you run the app, type in a couple of queries, then kill the app. You’ll see that the app saves the last query when relaunched.

If you recall, the business wants the user to see a list of saved queries. Do that now.

At the top of MetView, add a new property to store if the query history is being shown.

@State private var isShowingQueryHistory = false

In the VStack on line 59, before the List. Add a Button to show the query list:

Button("Show History Query") {
  isShowingQueryHistory = true
}

Next, on line 90, add a .sheet modifier to show a sheet containing the query list.

.sheet(isPresented: $isShowingQueryHistory) {
  LastQueriesView()
}

Xcode will show an error, as LastQueriesView doesn’t exist yet. That’s ok though, you’ll fix that now.

Create a new SwiftUI file called lastQueriesView. Then, inside the file, create a property to know when the sheet is dismissed, and a Query to get the list of saved queries from Swift Data.

import SwiftUI
import SwiftData

struct LastQueriesView: View {
  @Environment(\.dismiss) var dismiss

  @Query private var lastMetQueries: [MetQuery]
}

Finally, add a body property which creates a list using lastMetQueries.

var body: some View {
  NavigationStack {
    VStack {
      List(lastMetQueries, id: \.id) { query in
        Text(query.query)
      }
      .navigationTitle("Query History")
      .toolbar {
        Button("Close") {
          dismiss()
        }
      }
    }
  }
}

With that done, run the app again, then tap Show History Query. You’ll be able to see your previous search queries!

Now, do some final clean up to remove AppStorage. Back in MetView, uncomment lastQuery and have it default to an empty string.

@AppStorage("lastquery") private var lastQuery = ""

Any data stored in there is removed by default and your app can continue to work using Swift Data. The business will be happy, and your users won’t notice that the data storage has changed!

See forum comments
Cinema mode Download course materials from Github
Previous: Data Transformation & Migration Next: Conclusion