Our first task in this project will be to design a Core Data model for our books, then creating a new view to add books to the database.
First, the model: open Bookworm.xcdatamodeld and add a new entity called “Book” – we’ll create one new object in there for each book the user has read. In terms of what constitutes a book, I’d like you to add the following attributes:
Most of those should make sense, but the last one is an odd one: “integer 16”. What is the 16? And how come there are also Integer 32 and Integer 64? Well, just like Float
and Double
the difference is how much data they can store: Integer 16 uses 16 binary digits (“bits”) to store numbers, so it can hold values from -32,768 up to 32,767, whereas Integer 32 uses 32 bits to store numbers, so it hold values from -2,147,483,648 up to 2,147,483,647. As for Integer 64… well, that’s a really large number – about 9 quintillion.
The point is that these values aren’t interchangeable: you can’t take the value from a 64-bit number and try to store it in a 16-bit number, because you’d probably lose data. On the other hand, it’s a waste of space to use 64-bit integers for values we know will always be small. As a result, Core Data gives us the option to choose just how much storage we want.
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 Core Data knowledge.
Start by creating a new SwiftUI view called “AddBookView”. In terms of properties, we need an environment property to store our managed object context:
@Environment(\.managedObjectContext) var moc
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 except id
, which we can generate dynamically. So, add these properties next:
@State private var title = ""
@State private var author = ""
@State private var rating = 3
@State private var genre = ""
@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:
NavigationView {
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 {
TextEditor(text: $review)
Picker("Rating", selection: $rating) {
ForEach(0..<6) {
Text(String($0))
}
}
} header: {
Text("Write a review")
}
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 our managed object context, copy in all the values from our form (converting rating
to an Int16
to match Core Data), then save the managed object context.
Most of this work is just copying one value into another, with the only vaguely interesting thing being how we convert from an Int
to an Int16
for the rating. Even that is pretty guessable: Int16(someInt)
does it all.
Add this code in place of the // add the book
comment:
let newBook = Book(context: moc)
newBook.id = UUID()
newBook.title = title
newBook.author = author
newBook.rating = Int16(rating)
newBook.genre = genre
newBook.review = review
try? moc.save()
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 these three properties to ContentView
:
@Environment(\.managedObjectContext) var moc
@FetchRequest(sortDescriptors: []) var books: FetchedResults<Book>
@State private var showingAddScreen = false
That gives us a managed object context we can use later on to delete books, a fetch request 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 view 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:
NavigationView {
Text("Count: \(books.count)")
.navigationTitle("Bookworm")
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button {
showingAddScreen.toggle()
} label: {
Label("Add Book", systemImage: "plus")
}
}
}
.sheet(isPresented: $showingAddScreen) {
AddBookView()
}
}
Tip: That explicitly specifies a trailing navigation bar placement so that we can add a second button later.
Bear with me – we’re almost done! We’ve now designed our Core Data model, created a form to add data, then updated ContentView
so that it can present the form and pass in its managed object context. The final step is to 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 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.
SPONSORED An iOS conference hosted in Buenos Aires, Argentina – join us for the third edition from November 29th to December 1st!
Sponsor Hacking with Swift and reach the world's largest Swift community!
Link copied to your pasteboard.