SwiftUI Nav with Data

SwiftUI + Nav + SwiftData project

YLabZ
5 min readJan 27, 2025
SwiftUI + SwiftData + Nav

Below is a step-by-step guide for setting up a SwiftUI + SwiftData project with three different data models, each displayed on a separate screen. This example is written for iOS 17+ (and will work similarly for future versions like iOS 18).

It demonstrates:

  • Three SwiftData models (Article, Book, Movie).
  • A Main Menu (ContentView) with three buttons, each navigating to a different screen for each model.
  • SwiftUI’s modern NavigationStack.
  • Basic List usage, @Query property wrappers, and data insertion to SwiftData.

You can easily expand this code to add detail views, editing capabilities, or relationships between models.

1) Models: Article, Book, and Movie

We’ll define three SwiftData models using the @Model attribute, one for each screen we want to display.

import SwiftUI
import SwiftData

@Model
class Article {
var title: String
var body: String
init(title: String, body: String) {
self.title = title
self.body = body
}
}
@Model
class Book {
var name: String
var author: String
init(name: String, author: String) {
self.name = name
self.author = author
}
}
@Model
class Movie {
var title: String
var releaseYear: Int
init(title: String, releaseYear: Int) {
self.title = title
self.releaseYear = releaseYear
}
}

Explanation

  • @Model tells SwiftData that these classes are persistable entities.
  • Each model has different properties.
  • Customize these as needed for your real-world data.

2) App Entry Point: Setting Up the SwiftData Container

Next, we create our App struct, where we register the models (Article, Book, and Movie) in our SwiftData container.

@main
struct MySwiftDataApp: App {
var body: some Scene {
WindowGroup {
ContentView()
// Register our three models so SwiftData can manage them
.modelContainer(for: [Article.self, Book.self, Movie.self])
}
}
}

Explanation

  • @main indicates this is our SwiftUI app’s entry point.
  • .modelContainer(for: [...]) sets up a container capable of storing Article, Book, and Movie objects.
  • Any child view inside ContentView() can access these models (via @Query or @Environment(\.modelContext)).

3) Main Menu (ContentView)

The ContentView is our home screen with three navigation buttons (one for each model). We use a NavigationStack to manage navigation in SwiftUI 4 (iOS 16+) or later.

import SwiftUI
import SwiftData

struct ContentView: View {
var body: some View {
NavigationStack {
VStack(spacing: 20) {
// Button -> ArticleScreen
NavigationLink("Go to Articles") {
ArticlesScreen()
}
.buttonStyle(.borderedProminent)
// Button -> BookScreen
NavigationLink("Go to Books") {
BooksScreen()
}
.buttonStyle(.borderedProminent)
// Button -> MovieScreen
NavigationLink("Go to Movies") {
MoviesScreen()
}
.buttonStyle(.borderedProminent)
}
.navigationTitle("Main Menu")
.padding()
}
}
}

Explanation

  • NavigationStack replaces the older NavigationView.
  • Each NavigationLink points to a different SwiftUI view (one per model).

4) Screen One: ArticlesScreen

We’ll define a List showing Article items from SwiftData and include a button to add a new article. The @Query property wrapper handles fetching all Article objects automatically.

import SwiftUI
import SwiftData

struct ArticlesScreen: View {
@Environment(\.modelContext) private var context
// Query all Article objects, sorted by their 'title' ascending
@Query(sort: \Article.title, order: .forward)
private var articles: [Article]

var body: some View {
VStack(spacing: 16) {
List(articles) { article in
// Wrap each row in a NavigationLink
NavigationLink(destination: ArticleDetailView(article: article)) {
VStack(alignment: .leading) {
Text(article.title)
.font(.headline)
Text(article.body)
.font(.subheadline)
.foregroundColor(.secondary)
}
}
}
.listStyle(.plain)

Button("Add Demo Article") {
let newArticle = Article(
title: "New Article \(Date().formatted())",
body: "This is a sample article body."
)
context.insert(newArticle)
try? context.save()
}
.buttonStyle(.borderedProminent)
}
.padding()
.navigationTitle("Articles")
}
}

Editable Detail Screen

import SwiftUI
import SwiftData

struct ArticleDetailView: View {
@Environment(\.modelContext) private var context
@Bindable var article: Article


var body: some View {
Form {
TextField("Title", text: $article.title)
TextField("Body", text: $article.body, axis: .vertical)
.lineLimit(3) // optional multi-line
}
.navigationTitle("Edit Article")
.onDisappear {
// Attempt to save changes when leaving
try? context.save()
}
}
}

Explanation

  • @Environment(\.modelContext) gives us access to the SwiftData context so we can insert/save.
  • @Query automatically fetches Article objects from the container. The results are updated whenever the underlying data changes.
  • Tapping Add Demo Article creates a new Article instance and saves it. The list automatically refreshes.
  • The Article screen has navigation to an editable detailed screen.

5) Screen Two: BooksScreen

Similarly, for the Book model:

import SwiftUI
import SwiftData

struct BooksScreen: View {
@Environment(\.modelContext) private var context
// Fetch all Books, sorted by name
@Query(sort: \.name, order: .forward)
private var books: [Book]
var body: some View {
VStack(spacing: 16) {
List(books) { book in
VStack(alignment: .leading) {
Text(book.name)
.font(.headline)
Text("by \(book.author)")
.font(.subheadline)
.foregroundColor(.secondary)
}
}
.listStyle(.plain)
Button("Add Demo Book") {
let newBook = Book(
name: "New Book \(Date().formatted())",
author: "Author Name"
)
context.insert(newBook)
try? context.save()
}
.buttonStyle(.borderedProminent)
}
.padding()
.navigationTitle("Books")
}
}

Explanation

  • Identical pattern to ArticlesScreen, but fetching Book objects from SwiftData.
  • The user can see existing books in a List and add a new one at the tap of a button.

6) Screen Three: MoviesScreen

Lastly, a screen for the Movie model:

import SwiftUI
import SwiftData

struct MoviesScreen: View {
@Environment(\.modelContext) private var context
// Fetch all Movies, sorted by their title
@Query(sort: \.title, order: .forward)
private var movies: [Movie]
var body: some View {
VStack(spacing: 16) {
List(movies) { movie in
VStack(alignment: .leading) {
Text(movie.title)
.font(.headline)
Text("Released in \(movie.releaseYear)")
.font(.subheadline)
.foregroundColor(.secondary)
}
}
.listStyle(.plain)
Button("Add Demo Movie") {
let newMovie = Movie(
title: "New Movie \(Date().formatted())",
releaseYear: 2023
)
context.insert(newMovie)
try? context.save()
}
.buttonStyle(.borderedProminent)
}
.padding()
.navigationTitle("Movies")
}
}

Explanation

  • Again, the structure is the same.
  • We fetch all Movie records and list them, with a button that creates a new item.

7) Summary & Next Steps

With this setup:

  1. Three Models: Article, Book, Movie, each stored in SwiftData.
  2. Navigation: One main menu with three buttons, navigating to separate screens for each model.
  3. Query: Each screen uses @Query to automatically fetch and display its respective model objects.
  4. Data Operations: Inserting new records is as simple as creating a new model instance, calling context.insert(...), and then try? context.save(). The UI refreshes automatically.

Possible Enhancements

  • Detail Views: Wrap each list row in a NavigationLink to a detail or editing view.
  • Sorting & Filtering: Change or add parameters to @Query for custom sorting or filtering.
  • Styling: Replace List with ScrollView + VStack, or add custom SwiftUI styling.
  • Relationships: If needed, you can reference one model from another by storing references (e.g., an Author model that multiple Book objects point to).

This example provides a clear foundation for managing multiple data models in SwiftUI using SwiftData on iOS 17 and later (and will work on iOS 18+). Feel free to tailor the models, queries, or screens to fit your app’s needs.

This extends our earlier article

iOS Base Code for all our projects. (Work in Progress)

Thanks,

~Ash

--

--

Responses (1)