swiftlistviewswiftuistatefavorites

SwiftUI - Display the views marked as favorites in a list


Just in case, the Book struct is below.

struct Book: Identifiable {
var id = UUID().uuidString
var title: String
var description: String
var imageURL: URL
var sourceOfDesc: String
var descSourceCitation: String
}

My goal is to display a list of BookDetailView marked as favorites. A class to add and remove books from the favorites has been created.

class Favorites: ObservableObject {
// The actual books the user marked as favorite.
@Published var books: [String]

// The key to be used to read/write in the UserDefaults
private let saveKey = "Favorites"

init() {
    // Load saved data
    books = []
}

// Returns true if the set contains this book
func contains(_ book: Book) -> Bool {
    books.contains(book.id)
}

func add(_ book: Book) {
    objectWillChange.send()
    books.insert(book.id, at: 0)
    save()
}

func remove(_ book: Book) {
    objectWillChange.send()
    books.removeAll { $0 == book.id }
    save()
}

func save() {
    // Write data
  }
}

Updated FavoritesView below.

struct FavoritesView: View {
@ObservedObject var favoriteList: Favorites
var book: Book

var body: some View {
    List(favoriteList.books) { book in
        NavigationLink {
            WorksListTemplateView(books: book)
        } label: {
            Text(book.title)
        }
     }
   }
}

I get multiple error messages on FavoritesView, the ones that jump out to me are these 2:

  1. Cannot convert value of type '[String]' to expected argument type 'Binding'

  2. Generic parameter 'Data' could not be inferred


Solution

  • List is excepting a RandomAccessCollection, Set doesn't not conform to it. So you should convert your set to an array: Array(favoritesList.books).

    However, since String does not conform to Identifiable, you also need to add an id to the list:

     List(Array(favoritesList.books), id: \.self) { book in
    

    Remarks:

    1. As mentioned in the comments, in Favorites you should mark books using @Published in order for ObservableObject to take effect:

      @Published var books: Set<String> //or an array [String]
      
    2. In FavoritesView, favoriteList should be:

      @ObservedObject var favoriteList: Favorites 
      //ObservedObject & not StateObject because the object is passed 
      //down from another View & not directly initialized in the View.
      
    3. In BookDetailView, you don't need .environmentObject(favorites). You inject it when you first Initialize it, i.e where you don't have @EnvironmentObject var favorites: Favorites or when you are presenting another View that needs it.

    4. Also in BookDetailView, if you need to mutate book from its View mark it with @State. If you need to mutate book from another View, in that View mark it with @Binding.