100 Days of SwiftUI Day 54

100 Days of SwiftUI – Day 54

It’s day 54 of the 100 Days of SwiftUI! Today, we’re starting with the implementation of our Bookworm app. Yesterday, we were introduced to Core Data, Swift’s framework for databases. That’ll be on display today as well, so let’s dive in!

Creating books with Core Data in SwiftUI

The first task in this project is to design a Core Data model for our books. With that model in place, we can create a SwiftUI view to create new books in our database.

The data model for the Bookworm app
//
//  AddBookView.swift
//  Bookworm
//


import SwiftUI

struct AddBookView: View {
    
    @Environment(\.managedObjectContext) var moc
    @Environment(\.dismiss) var dismiss
    
    @State private var title = ""
    @State private var author = ""
    @State private var rating = 3
    @State private var genre = ""
    @State private var review = ""
    
    let genres = ["Fantasy", "Horror", "Kids", "Mystery", "Poetry", "Romance", "Thriller"]
    
    var body: some View {
        NavigationView {
            Form {
                Section {
                    TextField("Name of book", text: $title)
                    TextField("Name of author", 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") {
                        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 viewe
                    }
                }
                
            }
            .navigationTitle("Add Book")
        }
    }
}

struct AddBookView_Previews: PreviewProvider {
    static var previews: some View {
        AddBookView()
    }
}
//
//  ContentView.swift
//  Bookworm


import SwiftUI

struct ContentView: View {
    
    @Environment(\.managedObjectContext) var moc // moc to store view context for our data model which we can use to delete books
    @FetchRequest(sortDescriptors: []) var books: FetchedResults<Book> // reads out all the books we've added so far
    
    @State private var showingAddScreen = false // Used to show a sheet where we add books
    
    var body: some View {
        NavigationView {
            Text("Count is: \(books.count)")
                .navigationTitle("Bookworm")
                .toolbar {
                    ToolbarItem(placement: .navigationBarTrailing) {
                        Button {
                            showingAddScreen.toggle()
                        } label: {
                            Label("Add Book", systemImage: "plus")
                        }
                    }
                }
            
                .sheet(isPresented: $showingAddScreen) {
                    AddBookView()
                }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

Adding a custom star rating component

Now that we can add a book to our database, we need a proper way for our user to rate a book. To accomplish this, we are going to build a star rating component that allows users to give a book a 1 to 5 star rating.

//
//  RatingView.swift
//  Bookworm
//
//  Created by Darryl Soerdjpal on 19/08/2022.
//

import SwiftUI

struct RatingView: View {
    
    @Binding var rating: Int
    
    var label = ""
    var maximumRating = 5 // maximum number of stars
    
    var offImage: Image? // will use the same as on image
    var onImage = Image(systemName: "star.fill") // a filled star image
    
    var offColor = Color.gray // gray stars when not highlighted
    var onColor = Color.yellow // yellow stars when highlighted
    
    var body: some View {
        HStack {
            if label.isEmpty == false {
                Text(label)
            }
            
            ForEach(1..<maximumRating + 1, id: \.self) { number in
                image(for: number)
                    .foregroundColor(number > rating ? offColor : onColor)
                    .onTapGesture {
                        rating = number
                    }
            }
        }
    }
    
    func image(for number: Int) -> Image {
        if number > rating {
            return offImage ?? onImage
        } else {
            return onImage
        }
    }
}

struct RatingView_Previews: PreviewProvider {
    static var previews: some View {
        RatingView(rating: .constant(4))
    }
}

Building a list with @FetchRequest

Our ContentView has a @FetchRequest that requests all the books in the database at once. We’re going to use this to fill our ContentView with all the books, along with their rating and author.

To flex our coding muscles, we first created a new EmojiRatingView that will display an emoji based on our rating on the main screen of the app.

//
//  EmojiRatingView.swift
//  Bookworm


import SwiftUI

struct EmojiRatingView: View {
    
    let rating: Int16
    
    var body: some View {
        switch rating {
        case 1:
            return Text("????")
        case 2:
            return Text("????")
        case 3:
            return Text("????")
        case 4:
            return Text("????")
        default:
            return Text("????")
        }
    }
}

struct EmojiRatingView_Previews: PreviewProvider {
    static var previews: some View {
        EmojiRatingView(rating: 3)
    }
}
//
//  ContentView.swift
//  Bookworm


import SwiftUI

struct ContentView: View {
    
    @Environment(\.managedObjectContext) var moc // moc to store view context for our data model which we can use to delete books
    @FetchRequest(sortDescriptors: []) var books: FetchedResults<Book> // reads out all the books we've added so far
    
    @State private var showingAddScreen = false // Used to show a sheet where we add books
    
    var body: some View {
        NavigationView {
            List {
                ForEach(books) { book in
                    NavigationLink {
                        Text(book.title ?? "Unknown Title")
                    } label: {
                        HStack {
                            EmojiRatingView(rating: book.rating)
                                .font(.largeTitle)
                            
                            VStack(alignment: .leading) {
                                Text(book.title ?? "Unknown Title")
                                    .font(.headline)
                                
                                Text(book.author ?? "Unknown Author")
                                    .foregroundColor(.secondary)
                            }
                        }
                    }
                }
            }
            .navigationTitle("Bookworm")
            .toolbar {
                ToolbarItem(placement: .navigationBarTrailing) {
                    Button {
                        showingAddScreen.toggle()
                    } label: {
                        Label("Add Book", systemImage: "plus")
                    }
                }
            }
            
            .sheet(isPresented: $showingAddScreen) {
                AddBookView()
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

Wrap up

And that’s it for day 54! Tomorrow, we’ll finish the implementation of the Bookworm app by learning how to show details from our Core Data model, in this case from books, as well as sorting and deleting @FetchRequests. Stay tuned for all of that and more tomorrow!

Darryl

Hi! My name is Darryl and this is my personal blog where I write about my journey as I learn programming! You'll also find articles about other things that interest me including games, tech and anime.

Post navigation

Leave a Comment

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

100 Days of SwiftUI – Day 100 – Review

100 Days of SwiftUI – Day 11 – Structs, Part 2

100 Days of SwiftUI – Day 36 – iExpense

100 Days of SwiftUI – Day 81