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 fetchesArticle
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:
- Three Models:
Article
,Book
,Movie
, each stored in SwiftData. - Navigation: One main menu with three buttons, navigating to separate screens for each model.
- Query: Each screen uses
@Query
to automatically fetch and display its respective model objects. - Data Operations: Inserting new records is as simple as creating a new model instance, calling
context.insert(...)
, and thentry? 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
withScrollView
+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 multipleBook
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