From 13b668967addc367cde0db8647a055c6334cd265 Mon Sep 17 00:00:00 2001 From: Guilherme Antunes Date: Fri, 30 Aug 2019 17:57:40 -0300 Subject: [PATCH] feat(favorites): adding core data logic to CoreDataClient class --- iOSBooks/Podfile | 1 - iOSBooks/iOSBooks.xcodeproj/project.pbxproj | 21 ++++++ iOSBooks/iOSBooks/AppDelegate.swift | 48 +++++++++++- .../BookDetail/View/BookDetail.storyboard | 13 ++++ .../View/BookDetailViewController+View.swift | 15 +++- .../View/BookDetailViewController.swift | 8 ++ .../ViewModel/BookDetailViewModel.swift | 19 +++++ .../View/BooksListViewController+View.swift | 19 ++++- .../View/BooksListViewController.swift | 10 ++- .../BooksList/View/Subviews/BooksCell.swift | 26 ++++++- .../ViewModel/BooksListViewModel.swift | 27 ++++++- .../AppCoordinatorDependencyInjector.swift | 1 + .../Book.xcdatamodel/contents | 14 ++++ .../iOSBooks/Data/Clients/BooksClient.swift | 9 ++- .../Data/Clients/CoreDataClient.swift | 75 +++++++++++++++++++ iOSBooks/iOSBooks/Data/Models/Book.swift | 18 +++-- .../iOSBooks/Data/Models/ImageLinks.swift | 9 ++- iOSBooks/iOSBooks/Data/Models/Item.swift | 12 ++- iOSBooks/iOSBooks/Data/Models/SalesInfo.swift | 9 ++- 19 files changed, 327 insertions(+), 27 deletions(-) create mode 100644 iOSBooks/iOSBooks/Data/Clients/Book.xcdatamodeld/Book.xcdatamodel/contents create mode 100644 iOSBooks/iOSBooks/Data/Clients/CoreDataClient.swift diff --git a/iOSBooks/Podfile b/iOSBooks/Podfile index a563546..4f3cc9d 100644 --- a/iOSBooks/Podfile +++ b/iOSBooks/Podfile @@ -7,7 +7,6 @@ target 'iOSBooks' do # Pods for iOSBooks pod 'PromiseKit' - pod 'IQKeyboardManagerSwift' pod 'Kingfisher', "~>5.1" target 'iOSBooksTests' do diff --git a/iOSBooks/iOSBooks.xcodeproj/project.pbxproj b/iOSBooks/iOSBooks.xcodeproj/project.pbxproj index 7161d36..c58777e 100644 --- a/iOSBooks/iOSBooks.xcodeproj/project.pbxproj +++ b/iOSBooks/iOSBooks.xcodeproj/project.pbxproj @@ -37,6 +37,8 @@ 74B9185223199E8F00ADDEA4 /* BookDetailViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74B9185123199E8F00ADDEA4 /* BookDetailViewModel.swift */; }; 74B9185423199F4500ADDEA4 /* BookDetailViewController+View.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74B9185323199F4500ADDEA4 /* BookDetailViewController+View.swift */; }; 74B918562319AE0300ADDEA4 /* BookDetailViewController+Safari.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74B918552319AE0300ADDEA4 /* BookDetailViewController+Safari.swift */; }; + 74B918582319B2CF00ADDEA4 /* CoreDataClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74B918572319B2CF00ADDEA4 /* CoreDataClient.swift */; }; + 74B9185B2319B2DD00ADDEA4 /* Book.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 74B918592319B2DD00ADDEA4 /* Book.xcdatamodeld */; }; 768F74E39672B1F50C632913 /* Pods_iOSBooks.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2D2FDB2EC5907498F6A3C173 /* Pods_iOSBooks.framework */; }; /* End PBXBuildFile section */ @@ -89,6 +91,8 @@ 74B9185123199E8F00ADDEA4 /* BookDetailViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookDetailViewModel.swift; sourceTree = ""; }; 74B9185323199F4500ADDEA4 /* BookDetailViewController+View.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BookDetailViewController+View.swift"; sourceTree = ""; }; 74B918552319AE0300ADDEA4 /* BookDetailViewController+Safari.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BookDetailViewController+Safari.swift"; sourceTree = ""; }; + 74B918572319B2CF00ADDEA4 /* CoreDataClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataClient.swift; sourceTree = ""; }; + 74B9185A2319B2DD00ADDEA4 /* Book.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Book.xcdatamodel; sourceTree = ""; }; E6741F045EBC12BB315EE1CB /* Pods-iOSBooks.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOSBooks.debug.xcconfig"; path = "Target Support Files/Pods-iOSBooks/Pods-iOSBooks.debug.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -212,6 +216,8 @@ isa = PBXGroup; children = ( 7447D3652316E3C100E01BD3 /* BooksClient.swift */, + 74B918572319B2CF00ADDEA4 /* CoreDataClient.swift */, + 74B918592319B2DD00ADDEA4 /* Book.xcdatamodeld */, ); path = Clients; sourceTree = ""; @@ -504,9 +510,11 @@ 74ADE9BE23196CE3006D2644 /* AppCoordinatorDependencyInjector.swift in Sources */, 7447D3702316F14D00E01BD3 /* SalesInfo.swift in Sources */, 7447D37A2316F71200E01BD3 /* BooksListViewModel.swift in Sources */, + 74B918582319B2CF00ADDEA4 /* CoreDataClient.swift in Sources */, 7447D36E2316F13A00E01BD3 /* ImageLinks.swift in Sources */, 7447D37C2316F71D00E01BD3 /* BooksListViewController.swift in Sources */, 74ADE9C7231973B5006D2644 /* BooksListViewController+CollectionView.swift in Sources */, + 74B9185B2319B2DD00ADDEA4 /* Book.xcdatamodeld in Sources */, 74ADE9C52319730F006D2644 /* BooksCell.swift in Sources */, 74ADE9D223199D97006D2644 /* BookDetailViewController.swift in Sources */, 74ADE9B823196A7A006D2644 /* AppCoordinator.swift in Sources */, @@ -786,6 +794,19 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCVersionGroup section */ + 74B918592319B2DD00ADDEA4 /* Book.xcdatamodeld */ = { + isa = XCVersionGroup; + children = ( + 74B9185A2319B2DD00ADDEA4 /* Book.xcdatamodel */, + ); + currentVersion = 74B9185A2319B2DD00ADDEA4 /* Book.xcdatamodel */; + path = Book.xcdatamodeld; + sourceTree = ""; + versionGroupType = wrapper.xcdatamodel; + }; +/* End XCVersionGroup section */ }; rootObject = 0E3964CC2314BB470093738B /* Project object */; } diff --git a/iOSBooks/iOSBooks/AppDelegate.swift b/iOSBooks/iOSBooks/AppDelegate.swift index 4e75ef8..f821be6 100644 --- a/iOSBooks/iOSBooks/AppDelegate.swift +++ b/iOSBooks/iOSBooks/AppDelegate.swift @@ -6,7 +6,7 @@ // Copyright © 2019 Guilherme Antunes. All rights reserved. // -import IQKeyboardManagerSwift +import CoreData import UIKit @UIApplicationMain @@ -22,8 +22,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate { print("window is unexpectedly nil") return false } - IQKeyboardManager.shared.enable = true - IQKeyboardManager.shared.shouldShowToolbarPlaceholder = false coordinator = AppCoordinator(window: window) coordinator?.start() @@ -51,7 +49,51 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func applicationWillTerminate(_ application: UIApplication) { window = nil } + + // MARK: - Core Data stack + + lazy var persistentContainer: NSPersistentContainer = { + /* + The persistent container for the application. This implementation + creates and returns a container, having loaded the store for the + application to it. This property is optional since there are legitimate + error conditions that could cause the creation of the store to fail. + */ + let container = NSPersistentContainer(name: "Book") + container.loadPersistentStores(completionHandler: { (storeDescription, error) in + if let error = error as NSError? { + // Replace this implementation with code to handle the error appropriately. + // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. + + /* + Typical reasons for an error here include: + * The parent directory does not exist, cannot be created, or disallows writing. + * The persistent store is not accessible, due to permissions or data protection when the device is locked. + * The device is out of space. + * The store could not be migrated to the current model version. + Check the error message to determine what the actual problem was. + */ + fatalError("Unresolved error \(error), \(error.userInfo)") + } + }) + return container + }() + // MARK: - Core Data Saving support + + func saveContext () { + let context = persistentContainer.viewContext + if context.hasChanges { + do { + try context.save() + } catch { + // Replace this implementation with code to handle the error appropriately. + // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. + let nserror = error as NSError + fatalError("Unresolved error \(nserror), \(nserror.userInfo)") + } + } + } } diff --git a/iOSBooks/iOSBooks/BookDetail/View/BookDetail.storyboard b/iOSBooks/iOSBooks/BookDetail/View/BookDetail.storyboard index c032e64..0b0379b 100644 --- a/iOSBooks/iOSBooks/BookDetail/View/BookDetail.storyboard +++ b/iOSBooks/iOSBooks/BookDetail/View/BookDetail.storyboard @@ -54,14 +54,26 @@ + + + @@ -72,6 +84,7 @@ + diff --git a/iOSBooks/iOSBooks/BookDetail/View/BookDetailViewController+View.swift b/iOSBooks/iOSBooks/BookDetail/View/BookDetailViewController+View.swift index ca0703d..d3072da 100644 --- a/iOSBooks/iOSBooks/BookDetail/View/BookDetailViewController+View.swift +++ b/iOSBooks/iOSBooks/BookDetail/View/BookDetailViewController+View.swift @@ -21,8 +21,11 @@ extension BookDetailViewController: BookDetailViewControllerPresentable { } func setupView() { + if let button = favoriteButton { + toggleFavoriteButton(button) + } title = viewModel?.getScreenTitle() - bookImageView?.kf.setImage(with: viewModel?.getBookImageLink()) + bookImageView?.kf.setImage(with: viewModel?.getBookImageLink(), options: [.cacheOriginalImage]) bookTitleLabel?.text = viewModel?.getBookTitle() bookAuthorsLabel?.text = viewModel?.getBookAuthors() buyLinkLabel?.text = viewModel?.getBookBuyLink() @@ -34,4 +37,14 @@ extension BookDetailViewController: BookDetailViewControllerPresentable { setBackButton(#selector(dismissScreen)) } + func toggleFavoriteButton(_ button: UIButton) { + button.layer.cornerRadius = 10 + guard let viewModel = viewModel else { return } + if viewModel.savedBook { + button.backgroundColor = .yellow + return + } + button.backgroundColor = .white + } + } diff --git a/iOSBooks/iOSBooks/BookDetail/View/BookDetailViewController.swift b/iOSBooks/iOSBooks/BookDetail/View/BookDetailViewController.swift index 93da8ac..b803c3c 100644 --- a/iOSBooks/iOSBooks/BookDetail/View/BookDetailViewController.swift +++ b/iOSBooks/iOSBooks/BookDetail/View/BookDetailViewController.swift @@ -16,6 +16,7 @@ class BookDetailViewController: UIViewController { @IBOutlet weak var bookAuthorsLabel: UILabel? @IBOutlet weak var buyLinkLabel: UILabel? @IBOutlet weak var descriptionTextView: UITextView? + @IBOutlet weak var favoriteButton: UIButton? // MARK: - Properties var viewModel: BookDetailViewModelProtocol? @@ -36,4 +37,11 @@ class BookDetailViewController: UIViewController { @objc func dismissScreen() { viewModel?.presentPreviousStep() } + + // MARK: - Actions + @IBAction func saveBook(_ sender: UIButton) { + viewModel?.saveBookIfNeeded() + toggleFavoriteButton(sender) + } + } diff --git a/iOSBooks/iOSBooks/BookDetail/ViewModel/BookDetailViewModel.swift b/iOSBooks/iOSBooks/BookDetail/ViewModel/BookDetailViewModel.swift index 712e942..9c0ea44 100644 --- a/iOSBooks/iOSBooks/BookDetail/ViewModel/BookDetailViewModel.swift +++ b/iOSBooks/iOSBooks/BookDetail/ViewModel/BookDetailViewModel.swift @@ -17,6 +17,8 @@ protocol BookDetailViewModelProtocol { func presentPreviousStep() func getScreenTitle() -> String? func getBookBuyLinkURL() -> URL? + func saveBookIfNeeded() + var savedBook: Bool { get } } class BookDetailViewModel: BookDetailViewModelProtocol { @@ -25,6 +27,8 @@ class BookDetailViewModel: BookDetailViewModelProtocol { var selectedBook: Item? weak var view: BookDetailViewControllerPresentable? var coordinator: AppCoordinatorProtocol? + var service = CoreDataClient() + var savedBook: Bool = false // MARK: - ViewModel Protocol Methods func getBookTitle() -> String { @@ -72,4 +76,19 @@ class BookDetailViewModel: BookDetailViewModelProtocol { return nil } + + func saveBookIfNeeded() { + if savedBook { + deleteBook() + return + } + savedBook = true + service.saveBook(selectedBook) + } + + func deleteBook() { + savedBook = false + service.deleteBook(selectedBook) + + } } diff --git a/iOSBooks/iOSBooks/BooksList/View/BooksListViewController+View.swift b/iOSBooks/iOSBooks/BooksList/View/BooksListViewController+View.swift index 75e57a0..13658e0 100644 --- a/iOSBooks/iOSBooks/BooksList/View/BooksListViewController+View.swift +++ b/iOSBooks/iOSBooks/BooksList/View/BooksListViewController+View.swift @@ -15,6 +15,9 @@ protocol BooksListViewControllerPresentable: class { extension BooksListViewController: BooksListViewControllerPresentable { func reloadView() { + if let viewModel = viewModel { + title = viewModel.showFavorites ? "Favorites iOS Books" : "iOS Books" + } booksCollectionView?.reloadData() } @@ -23,7 +26,6 @@ extension BooksListViewController: BooksListViewControllerPresentable { } func setupCollectionView() { - title = "iOS Books" booksCollectionView?.dataSource = self booksCollectionView?.delegate = self } @@ -31,4 +33,19 @@ extension BooksListViewController: BooksListViewControllerPresentable { func setupNavigationBar() { navigationController?.navigationBar.prefersLargeTitles = true } + + @objc func fillWithSavedBooks() { + guard var viewModel = viewModel else { return } + if !viewModel.showFavorites { + viewModel.showFavorites.toggle() + viewModel.loadSavedBooks(shouldShowOnScreen: true) + return + } + viewModel.showFavorites.toggle() + reloadView() + } + + func setupFavoritesFilterButton() { + navigationItem.rightBarButtonItem = button + } } diff --git a/iOSBooks/iOSBooks/BooksList/View/BooksListViewController.swift b/iOSBooks/iOSBooks/BooksList/View/BooksListViewController.swift index e3a99da..733a08c 100644 --- a/iOSBooks/iOSBooks/BooksList/View/BooksListViewController.swift +++ b/iOSBooks/iOSBooks/BooksList/View/BooksListViewController.swift @@ -9,19 +9,25 @@ import UIKit class BooksListViewController: UIViewController { + + // MARK: - Outlets + @IBOutlet weak var booksCollectionView: UICollectionView? // MARK: - Properties var viewModel: BooksListViewModelProtocol? - @IBOutlet weak var booksCollectionView: UICollectionView? + lazy var button = UIBarButtonItem(barButtonSystemItem: .bookmarks, target: self, action: #selector(fillWithSavedBooks)) // MARK: - View Life Cycle override func viewDidLoad() { setupCollectionView() - viewModel?.loadBooks() + setupFavoritesFilterButton() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) setupNavigationBar() + viewModel?.loadSavedBooks(shouldShowOnScreen: false) + viewModel?.showFavorites = false + viewModel?.loadBooks() } } diff --git a/iOSBooks/iOSBooks/BooksList/View/Subviews/BooksCell.swift b/iOSBooks/iOSBooks/BooksList/View/Subviews/BooksCell.swift index 515a938..45011f0 100644 --- a/iOSBooks/iOSBooks/BooksList/View/Subviews/BooksCell.swift +++ b/iOSBooks/iOSBooks/BooksList/View/Subviews/BooksCell.swift @@ -10,20 +10,42 @@ import Kingfisher import UIKit class BooksCell: UICollectionViewCell { + + // MARK: - Outlets @IBOutlet weak var bookImageView: UIImageView? + // MARK: - Properties + var selectedBook: Book? + lazy var imageCache = ImageCache(name: selectedBook?.title ?? "") + func setup(withBook book: Book?) { - if let book = book, let bookURLString = book.imageLinks?.smallThumbnail, let url = URL(string: bookURLString) { - bookImageView?.kf.setImage(with: url) + selectedBook = book + if let image = imageCache.retrieveImageInMemoryCache(forKey: selectedBook?.subtitle ?? "") { + bookImageView?.image = image + } else { + if let book = book, let bookURLString = book.imageLinks?.smallThumbnail, let url = URL(string: bookURLString) { + bookImageView?.kf.setImage(with: url, options: [.cacheOriginalImage], completionHandler: { (result) in + switch result { + case .success(let result): + self.imageCache.store(result.image, forKey: self.selectedBook?.subtitle ?? "") + case .failure(let error): + print(error.localizedDescription) + } + }) + + } } + } override func prepareForReuse() { super.prepareForReuse() + bookImageView?.image = nil bookImageView?.kf.cancelDownloadTask() } deinit { + bookImageView?.image = nil bookImageView?.kf.cancelDownloadTask() } diff --git a/iOSBooks/iOSBooks/BooksList/ViewModel/BooksListViewModel.swift b/iOSBooks/iOSBooks/BooksList/ViewModel/BooksListViewModel.swift index 81dfa05..a439fe0 100644 --- a/iOSBooks/iOSBooks/BooksList/ViewModel/BooksListViewModel.swift +++ b/iOSBooks/iOSBooks/BooksList/ViewModel/BooksListViewModel.swift @@ -10,39 +10,57 @@ import UIKit protocol BooksListViewModelProtocol { func loadBooks() + func loadSavedBooks(shouldShowOnScreen reloadView: Bool) func selectBook(atIndexPath indexPath: IndexPath) func presentPreviousStep() func numberOfSections() -> Int func numberOfItemsInSection() -> Int func cellForItem(inCollectionView collectionView: UICollectionView, atIndexPath indexPath: IndexPath) -> UICollectionViewCell + var showFavorites: Bool { get set } } class BooksListViewModel: BooksListViewModelProtocol { var items: [Item] = [] + var savedItems: [Item] = [] var service = BooksClient() var startingIndex = 0 weak var view: BooksListViewControllerPresentable? var selectedBook: Item? + var selectedBookIsFavorite = false var coordinator: AppCoordinatorProtocol? + var showFavorites: Bool = false func numberOfSections() -> Int { return 1 } func numberOfItemsInSection() -> Int { - return items.count + return showFavorites ? savedItems.count : items.count } func cellForItem(inCollectionView collectionView: UICollectionView, atIndexPath indexPath: IndexPath) -> UICollectionViewCell { let cell: BooksCell = collectionView.dequeueReusableCell(for: indexPath) - cell.setup(withBook: items[indexPath.item].book) - if indexPath.item == (items.count - 1) { + let book: Book? + if showFavorites { + book = savedItems[indexPath.item].book + } else { + book = items[indexPath.item].book + } + cell.setup(withBook: book) + if indexPath.item == (items.count - 1) && !showFavorites { loadBooks() } return cell } + func loadSavedBooks(shouldShowOnScreen reloadView: Bool = false) { + savedItems = service.fetchSavedBooks() + if reloadView { + view?.reloadView() + } + } + func loadBooks() { service.fetchBooksList(startingIndex: startingIndex).done { [weak self] (list) in guard let self = self, let items = list.items else { return } @@ -61,7 +79,8 @@ class BooksListViewModel: BooksListViewModelProtocol { func selectBook(atIndexPath indexPath: IndexPath) { selectedBook = nil - selectedBook = items[indexPath.item] + selectedBook = showFavorites ? savedItems[indexPath.item] : items[indexPath.item] + selectedBookIsFavorite = savedItems.contains(where: { $0.id == selectedBook?.id }) coordinator?.presentNextStep() } diff --git a/iOSBooks/iOSBooks/Coordinators/AppCoordinator/AppCoordinatorDependencyInjector.swift b/iOSBooks/iOSBooks/Coordinators/AppCoordinator/AppCoordinatorDependencyInjector.swift index cd5e575..da9f5b8 100644 --- a/iOSBooks/iOSBooks/Coordinators/AppCoordinator/AppCoordinatorDependencyInjector.swift +++ b/iOSBooks/iOSBooks/Coordinators/AppCoordinator/AppCoordinatorDependencyInjector.swift @@ -43,5 +43,6 @@ class AppCoordinatorDependencyInjector { func injectNewBook() { bookDetailViewModel.selectedBook = booksListViewModel.selectedBook + bookDetailViewModel.savedBook = booksListViewModel.selectedBookIsFavorite } } diff --git a/iOSBooks/iOSBooks/Data/Clients/Book.xcdatamodeld/Book.xcdatamodel/contents b/iOSBooks/iOSBooks/Data/Clients/Book.xcdatamodeld/Book.xcdatamodel/contents new file mode 100644 index 0000000..5392189 --- /dev/null +++ b/iOSBooks/iOSBooks/Data/Clients/Book.xcdatamodeld/Book.xcdatamodel/contents @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/iOSBooks/iOSBooks/Data/Clients/BooksClient.swift b/iOSBooks/iOSBooks/Data/Clients/BooksClient.swift index 224d3e3..9ccfee7 100644 --- a/iOSBooks/iOSBooks/Data/Clients/BooksClient.swift +++ b/iOSBooks/iOSBooks/Data/Clients/BooksClient.swift @@ -12,12 +12,19 @@ import PromiseKit class BooksClient { let apiClient: APIClient + let coreDataClient: CoreDataClient - init(apiClient: APIClient = APIClient()) { + init(apiClient: APIClient = APIClient(), coreData: CoreDataClient = CoreDataClient()) { self.apiClient = apiClient + self.coreDataClient = coreData } func fetchBooksList(startingIndex index: Int) -> Promise { return apiClient.request(model: BooksList.self, BooksAPI.list(startingIndex: index).request) } + + func fetchSavedBooks() -> [Item] { + return coreDataClient.fetchAllSavedBooks() + } + } diff --git a/iOSBooks/iOSBooks/Data/Clients/CoreDataClient.swift b/iOSBooks/iOSBooks/Data/Clients/CoreDataClient.swift new file mode 100644 index 0000000..e4ce74a --- /dev/null +++ b/iOSBooks/iOSBooks/Data/Clients/CoreDataClient.swift @@ -0,0 +1,75 @@ +// +// CoreDataClient.swift +// iOSBooks +// +// Created by Guilherme Antunes Ferreira on 30/08/19. +// Copyright © 2019 Guilherme Antunes. All rights reserved. +// + +import CoreData +import Foundation +import UIKit + +class CoreDataClient { + func saveBook(_ item: Item?) { + guard let item = item, let context = context, let entity = NSEntityDescription.entity(forEntityName: "CoreDataBook", in: context) else { return } + let coreDataBook = NSManagedObject(entity: entity, insertInto: context) + coreDataBook.setValue(item.book?.title, forKey: "title") + coreDataBook.setValue(item.id, forKey: "id") + coreDataBook.setValue(item.book?.description, forKey: "bookDescription") + coreDataBook.setValue(item.salesInfo?.buyLink, forKey: "buyLink") + coreDataBook.setValue(true, forKey: "isFavorite") + coreDataBook.setValue(item.book?.authors?.first, forKey: "author") + + do { + try context.save() + } catch { + print("error on saving") + } + } + + func fetchAllSavedBooks() -> [Item] { + var books: [Item] = [] + guard let context = context else { return [] } + let fetchRequest = NSFetchRequest(entityName: "CoreDataBook") + do { + guard let result = try context.fetch(fetchRequest) as? [NSManagedObject] else { return [] } + for data in result { + let item = Item() + item.book = Book() + item.book?.imageLinks = ImageLinks() + item.salesInfo = SalesInfo() + item.book?.authors = [] + item.book?.authors?.append(data.value(forKey: "author") as? String ?? "") + item.id = data.value(forKey: "id") as? String + item.book?.description = data.value(forKey: "bookDescription") as? String + item.salesInfo?.buyLink = data.value(forKey: "buyLink") as? String + item.book?.title = data.value(forKey: "title") as? String + books.append(item) + } + return books + } catch { + print("Failed to load data from CoreData") + return [] + } + } + + func deleteBook(_ book: Item?) { + guard let book = book, let context = context else { return } + let fetchRequest = NSFetchRequest(entityName: "CoreDataBook") + fetchRequest.predicate = NSPredicate(format: "id = %@", book.id ?? "") + guard let bookToDelete = try? context.fetch(fetchRequest).first as? NSManagedObject else { return } + context.delete(bookToDelete) + do { + try context.save() + + } catch { + print("Failed to fetch book from CoreData and delete") + } + } + + private var context: NSManagedObjectContext? { + guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return nil } + return appDelegate.persistentContainer.viewContext + } +} diff --git a/iOSBooks/iOSBooks/Data/Models/Book.swift b/iOSBooks/iOSBooks/Data/Models/Book.swift index 50078a8..d909cfd 100644 --- a/iOSBooks/iOSBooks/Data/Models/Book.swift +++ b/iOSBooks/iOSBooks/Data/Models/Book.swift @@ -9,11 +9,19 @@ import Foundation class Book: Codable { - let authors: [String]? - let title: String? - let subtitle: String? - let description: String? - let imageLinks: ImageLinks? + var authors: [String]? + var title: String? + var subtitle: String? + var description: String? + var imageLinks: ImageLinks? + + init() { + authors = nil + title = nil + subtitle = nil + description = nil + imageLinks = nil + } enum CodingKeys: String, CodingKey { case authors, title, subtitle, description, imageLinks diff --git a/iOSBooks/iOSBooks/Data/Models/ImageLinks.swift b/iOSBooks/iOSBooks/Data/Models/ImageLinks.swift index 3f19a98..1a4b273 100644 --- a/iOSBooks/iOSBooks/Data/Models/ImageLinks.swift +++ b/iOSBooks/iOSBooks/Data/Models/ImageLinks.swift @@ -9,8 +9,13 @@ import Foundation class ImageLinks: Codable { - let smallThumbnail: String? - let thumbnail: String? + var smallThumbnail: String? + var thumbnail: String? + + init() { + smallThumbnail = nil + thumbnail = nil + } enum CodingKeys: String, CodingKey { case smallThumbnail, thumbnail diff --git a/iOSBooks/iOSBooks/Data/Models/Item.swift b/iOSBooks/iOSBooks/Data/Models/Item.swift index b06c4ed..cf2b625 100644 --- a/iOSBooks/iOSBooks/Data/Models/Item.swift +++ b/iOSBooks/iOSBooks/Data/Models/Item.swift @@ -9,9 +9,15 @@ import Foundation class Item: Codable { - let id: String? - let book: Book? - let salesInfo : SalesInfo? + var id: String? + var book: Book? + var salesInfo : SalesInfo? + + init() { + id = nil + book = nil + salesInfo = nil + } enum CodingKeys: String, CodingKey { case book = "volumeInfo" diff --git a/iOSBooks/iOSBooks/Data/Models/SalesInfo.swift b/iOSBooks/iOSBooks/Data/Models/SalesInfo.swift index 1a395db..1852163 100644 --- a/iOSBooks/iOSBooks/Data/Models/SalesInfo.swift +++ b/iOSBooks/iOSBooks/Data/Models/SalesInfo.swift @@ -9,6 +9,11 @@ import Foundation class SalesInfo: Codable { - let country: String? - let buyLink: String? + var country: String? + var buyLink: String? + + init() { + country = nil + buyLink = nil + } }