UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

Project 11 Bookworm- average review ratings?

Forums > 100 Days of SwiftUI

So I've been exploring how we could average ratings if we were using this app in a library or classroom. I want to look at book titles, add a count button but give an average rating. Any ideas? I've failed all day. Is this something I will learn as I continue in this course?

2      

Karen doesn't give herself credit:

I want to look at book titles, add a count button but give an average rating.
Any ideas? I've failed all day.

Please! If you've tried all day, then I'd say you've been learning, not failing.

I have written elsewhere about eating elephants.
See -> Eating Elephants

@twoStraws is building up your skills; you may be jumping ahead of yourself, but let's consider your question.

My approach is to look at the entire elephant, and decide which part to eat first.

Somewhere in your application you have a collection (array) of Book objects. LOTS of books. This is the elephant.

Now you might have a smaller subselection such as Mysteries, or Horror. This is a smaller piece, but is still a collection.

Now let's cut this section up into bite-sized pieces!

  1. How might you determine how many books are in your subcollection of mystery books? (Hint: it's an array function.)
  2. How might you add up the total of all the reviews in your subcollection? (Hint: similar answer.)
  3. How do you calculate the average? (Hint: Maths)
  4. How do you display the results? (Hint: create a separate view, please, to keep your logic and view models separate!)

If you systematically break your big problems into smaller problems, you'll take huge steps forward in solving your upcoming challenges.

Keep coding!

Please return and share your code. Let us know if this helped or not.

2      

You are too kind. Thank you. I am not wanting to average genres, which I think I could do... (maybe)... I want to average books with the same title.

For instance, wouldn't it be nice if my review for The Giver and your review for The Giver could be averaged in the ContentView list, but when I go to the Detail View for The Giver, my stars are averaged (I think I'd have to round) and we could have mutliple reviews there for a book of the same title?

2      

I followed the recipe above. But instead of defining a subcollection as a genre, I defined it as a book title.

Try this in playgrounds.

import SwiftUI
import PlaygroundSupport
import Foundation

struct Book: Identifiable {
    let id =     UUID() // unique it
    let title:   String
    let rating:  Int
    let ratedBy: String
}

class BookCollection: ObservableObject {
    @Published var allBooks: [Book] // an array of ALL your books

    init() { allBooks = BookCollection.sampleData }

    // extract just the Titles from vast library
    public var bookTitles: [String] {
        return Array( Set( BookCollection.sampleData.map{ $0.title })).sorted()
    }

    // Subcollection: return all books with this title
    public func reviews(for title: String) -> [Book] {
        BookCollection.sampleData.filter { $0.title.contains(title) }  // extract a subcollection!
    }

    // Not available outside this class
    private func calcAverageRating(for title: String) -> Double {
        let subCollection = reviews(for: title)
        let sumOfReview   = subCollection.map { $0.rating }.reduce(0, +)
        return Double(sumOfReview) / Double(subCollection.count)
    }

    // Anyone can call this function and get a string!
    public func averageRating(for title: String) -> String {
        let formatter                   = NumberFormatter()
        formatter.numberStyle           = .decimal
        formatter.maximumFractionDigits = 1 // to one place
        return formatter.string(for: calcAverageRating(for: title)) ?? "0"
    }

    static var sampleData = [
        Book(title: "It",             rating:  7, ratedBy: "Karen"),
        Book(title: "It",             rating:  9, ratedBy: "twoStraws"),
        Book(title: "It",             rating: 10, ratedBy: "Obelix"),
        Book(title: "It",             rating:  2, ratedBy: "pinkerStraws"),
        Book(title: "It",             rating: 10, ratedBy: "capriel"),
        Book(title: "It",             rating:  9, ratedBy: "ada"),
        Book(title: "Carrie",         rating:  4, ratedBy: "Karen"),
        Book(title: "Carrie",         rating:  2, ratedBy: "twoStraws"),
        Book(title: "Carrie",         rating: 10, ratedBy: "Obelix"),
        Book(title: "Carrie",         rating:  2, ratedBy: "Ada"),
        Book(title: "Needful Things", rating: 10, ratedBy: "Obelix")
    ]
}

// Simple view to display ONE book in the collection, or subcollection
struct Review: View {
    var book: Book
    var body: some View {
        Text("\(book.ratedBy) rated \(book.title): \(book.rating) / 10")
            .font(.body)
    }
}

struct BookReviews: View {
    @ObservedObject var bookCollection = BookCollection()
    @State private var theBook = "It"
    var body: some View {
        VStack {
            // Just the titles
            Picker("Titles", selection: $theBook) {
                ForEach(bookCollection.bookTitles, id:\.self) {
                    Text("\($0)")
                }
            }.pickerStyle(.segmented)
                .padding()
                .frame(width: 500)
            // SwiftUI is declarative. DECLARE what you want to see!
            Text("Average rating for")
            Text("\(theBook): \(bookCollection.averageRating(for: theBook))")
                .font(.title)
                .padding(.bottom)
            ForEach (bookCollection.reviews(for: theBook)) {
                Review(book: $0)
            }
            Spacer()
        }.frame(width: 500, height: 500)
    }
}

// Playgrounds lets you inspect the contents of a view, and execute functions
let myView = BookReviews()        // <-- create a view, but don't display it.
myView.bookCollection.bookTitles  // <-- take a look inside
print("reviews for It")
// See? See how you DECLARE your intentions?
myView.bookCollection.reviews(for: "It") // <-- test drive a book collection function

let thisBook = "Carrie"
print("Average rating for \(thisBook) \(myView.bookCollection.averageRating(for: thisBook)) ")

// Run this in playgrounds. Live action! Click for magic!
PlaygroundPage.current.setLiveView( BookReviews() )

3      

Oh my goodness... as I went ahead and dived into Project12, I really felt like I was starting to get a few more options to try... your code is so simple to follow and makes sense... I have to work through the last few lines, but I really appreciate this.

Thank you - Thank you!

2      

Pardon the change in topic, but am a bit star struck....

THE Dallas Diamonds, four time national champions, Pro Bowl Quarterback? That Karen??!?

PS: Just added The Giver to my Libby 'must read next' list!

3      

In the words of Dani Rojas, "Football is life!" ... thanks for taking so much time for me. I'm your fan!

You won't be disappointed in that book!

2      

Hacking with Swift is sponsored by RevenueCat

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure your entire paywall view without any code changes or app updates.

Learn more here

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.