Our first task in this project will be to design a SwitData model for our books, then creating a new view to add books to the database.
First, the model: create a new file called Book.swift, add an import for SwiftData, then give it this code:
@Model
class Book {
var title: String
var author: String
var genre: String
var review: String
var rating: Int
}
That class needs an initializer to provide values for all its properties, but here's a tip: if you just start typing "in" inside the class, Xcode should autocomplete the whole initializer for you.
That class is enough to store the title of the book, the name of whoever wrote the book, a genre, a brief overview of what the user thought of the book, and also the user’s numerical rating for it.
Now that we have our data model, we can ask SwiftData to create a model container for it. This means opening BookwormApp.swift, adding import SwiftData
to the top of the file, then adding this modifier to the WindowGroup
:
.modelContainer(for: Book.self)
Our next step is to write a form that can create new entries. This will combine so many of the skills you’ve learned so far: Form
, @State
, @Environment
, TextField
, TextEditor
, Picker
, sheet()
, and more, plus all your new SwiftData knowledge.
Start by creating a new SwiftUI view called “AddBookView”. In terms of properties, we need an environment property to get access to our model context:
@Environment(\.modelContext) var modelContext
As this form is going to store all the data required to make up a book, we need @State
properties for each of the book’s values. So, add these properties next:
@State private var title = ""
@State private var author = ""
@State private var rating = 3
@State private var genre = "Fantasy"
@State private var review = ""
Finally, we need one more property to store all possible genre options, so we can make a picker using ForEach
. Add this last property to AddBookView
now:
let genres = ["Fantasy", "Horror", "Kids", "Mystery", "Poetry", "Romance", "Thriller"]
We can now take a first pass at the form itself – we’ll improve it soon, but this is enough for now. Replace the current body
with this:
NavigationStack {
Form {
Section {
TextField("Name of book", text: $title)
TextField("Author's name", text: $author)
Picker("Genre", selection: $genre) {
ForEach(genres, id: \.self) {
Text($0)
}
}
}
Section("Write a review") {
TextEditor(text: $review)
Picker("Rating", selection: $rating) {
ForEach(0..<6) {
Text(String($0))
}
}
}
Section {
Button("Save") {
// add the book
}
}
}
.navigationTitle("Add Book")
}
When it comes to filling in the button’s action, we’re going to create an instance of the Book
class using all the values from our form, then insert the object into the model context.
Add this code in place of the // add the book
comment:
let newBook = Book(title: title, author: author, genre: genre, review: review, rating: rating)
modelContext.insert(newBook)
That completes the form for now, but we still need a way to show and hide it when books are being added.
Showing AddBookView
involves returning to ContentView.swift and following the usual steps for a sheet:
@State
property to track whether the sheet is showing.sheet()
modifier that shows AddBookView
when the property becomes true.Enough talk – let’s start writing some more code. Please start by adding an import for SwiftData to ContentView,swift, then add these properties to the ContentView
struct:
@Environment(\.modelContext) var modelContext
@Query var books: [Book]
@State private var showingAddScreen = false
That gives us a model context we can use later on to delete books, a query reading all the books we have (so we can test everything worked), and a Boolean that tracks whether the add screen is showing or not.
For the ContentView
body, we’re going to use a navigation stack so we can add a title plus a button in its top-right corner, but otherwise it will just hold some text showing how many items we have in the books
array – just so we can be sure everything is working. Remember, this is where we need to add our sheet()
modifier to show an AddBookView
as needed.
Replace the existing body
property of ContentView
with this:
NavigationStack {
Text("Count: \(books.count)")
.navigationTitle("Bookworm")
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button("Add Book", systemImage: "plus") {
showingAddScreen.toggle()
}
}
}
.sheet(isPresented: $showingAddScreen) {
AddBookView()
}
}
Tip: That explicitly specifies a trailing placement for the button so that we can add a second button later.
Bear with me – we’re almost done! We’ve now designed our SwiftData model, created a form to add data, then updated ContentView
so that it can present the form when needed. The final step is to make the form dismiss itself when the user adds a book.
We’ve done this before, so hopefully you know the drill. We need to start by adding another environment property to AddBookView
to be able to dismiss the current view:
@Environment(\.dismiss) var dismiss
Finally, add a call to dismiss()
to the end of your Save button’s action closure.
You should be able to run the app now and add an example book just fine. When AddBookView
slides away the count label should update itself to 1.
SAVE 50% All our books and bundles are half price for Black Friday, so you can take your Swift knowledge further without spending big! Get the Swift Power Pack to build your iOS career faster, get the Swift Platform Pack to builds apps for macOS, watchOS, and beyond, or get the Swift Plus Pack to learn advanced design patterns, testing skills, and more.
Link copied to your pasteboard.