We’ve arrived at day 56 of the 100 Days of SwiftUI! Yesterday, we finished the implementation of the Bookworm app. Today, we wrap up this project by completing a few challenges. Let’s dive in!
Bookworm SwiftUI challenge #1
Let’s take a look at the first challenge!
Right now it’s possible to select no title, author, or genre for books, which causes a problem for the detail view. Please fix this, either by forcing defaults, validating the form, or showing a default picture for unknown genres – you can choose.
Hacking with Swift, Paul Hudson (@twostraws)
//
// StringIsEmpty.swift
// Bookworm
//
import Foundation
extension String {
var isReallyEmpty: Bool {
self.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
}
}
Section {
Button("Save") {
let newBook = Book(context: moc) // create a new book in our managed object context
// Set the properties for the new book
newBook.id = UUID()
newBook.title = title
newBook.author = author
newBook.rating = Int16(rating)
newBook.genre = genre
newBook.review = review
try? moc.save() // save the book to the managed object context
dismiss() // closes the view
}
}
`.disabled(title.isReallyEmpty || author.isReallyEmpty || genre.isReallyEmpty)
Bookworm SwiftUI challenge #2
The second challenge is to modify ContentView
so that books rated as 1 star are highlighted somehow, such as having their name shown in red.
VStack(alignment: .leading) {
Text(book.title ?? "Unknown Title")
.font(.headline)
.foregroundColor(book.rating == 1 ? .red : .primary)
Bookworm SwiftUI challenge #3
The third and final challenge of the day is to add a new “date” attribute to the Book entity, assigning Date.now
to it so it gets the current date and time, then format that nicely somewhere in DetailView
.
To do this, we first needed to add a date to our data model. Then, it was just a matter of putting it somewhere in our code, after setting it in the AddBookView
.
Section {
Button("Save") {
let newBook = Book(context: moc) // create a new book in our managed object context
// Set the properties for the new book
newBook.id = UUID()
newBook.title = title
newBook.author = author
newBook.rating = Int16(rating)
newBook.genre = genre
newBook.review = review
newBook.date = Date.now
try? moc.save() // save the book to the managed object context
dismiss() // closes the viewe
}
}
.disabled(title.isReallyEmpty || author.isReallyEmpty || genre.isReallyEmpty)
//
// DetailView.swift
// Bookworm
import SwiftUI
struct DetailView: View {
let book: Book
@Environment(\.managedObjectContext) var moc
@Environment(\.dismiss) var dismiss
@State private var showingDeleteAlert = false
var body: some View {
ScrollView {
ZStack(alignment: .bottomTrailing) {
Image(book.genre ?? "Fantasy")
.resizable()
.scaledToFit()
Text(book.genre?.uppercased() ?? "FANTASY")
.font(.caption)
.fontWeight(.black)
.padding(8)
.foregroundColor(.white)
.background(.black.opacity(0.75))
.clipShape(Capsule())
.offset(x: -5, y: -5)
}
Text(book.author ?? "Unknown Author")
.font(.title)
.foregroundColor(.secondary)
if let date = book.date {
Text(date.formatted(date: .abbreviated, time: .omitted))
}
Text(book.review ?? "No review")
.padding()
RatingView(rating: .constant(Int(book.rating)))
.font(.largeTitle)
}
.navigationTitle(book.title ?? "Unknown Book")
.navigationBarTitleDisplayMode(.inline)
.alert("Delete Book", isPresented: $showingDeleteAlert) {
Button("Delete", role: .destructive, action: deleteBook)
Button("Cancel", role: .cancel) {}
} message: {
Text("Are you sure?")
}
.toolbar {
Button {
showingDeleteAlert = true
} label: {
Label("Delete this book", systemImage: "trash")
}
}
}
func deleteBook() {
moc.delete(book)
// try? moc.save()
dismiss()
}
}
Screenshots



Wrap up
And that’s it for day 56! Tomorrow, we’ll jump right into the next project, which is a technique project where we’ll further explore Core Data. That should be very interesting, so stay tuned for that. For now, it’s time to recharge!
100 Days of SwiftUI – Day 56