Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion iOSBooks/Podfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ target 'iOSBooks' do

# Pods for iOSBooks
pod 'PromiseKit'
pod 'IQKeyboardManagerSwift'
pod 'Kingfisher', "~>5.1"

target 'iOSBooksTests' do
Expand Down
21 changes: 21 additions & 0 deletions iOSBooks/iOSBooks.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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 */

Expand Down Expand Up @@ -89,6 +91,8 @@
74B9185123199E8F00ADDEA4 /* BookDetailViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookDetailViewModel.swift; sourceTree = "<group>"; };
74B9185323199F4500ADDEA4 /* BookDetailViewController+View.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BookDetailViewController+View.swift"; sourceTree = "<group>"; };
74B918552319AE0300ADDEA4 /* BookDetailViewController+Safari.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BookDetailViewController+Safari.swift"; sourceTree = "<group>"; };
74B918572319B2CF00ADDEA4 /* CoreDataClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataClient.swift; sourceTree = "<group>"; };
74B9185A2319B2DD00ADDEA4 /* Book.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Book.xcdatamodel; sourceTree = "<group>"; };
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 = "<group>"; };
/* End PBXFileReference section */

Expand Down Expand Up @@ -212,6 +216,8 @@
isa = PBXGroup;
children = (
7447D3652316E3C100E01BD3 /* BooksClient.swift */,
74B918572319B2CF00ADDEA4 /* CoreDataClient.swift */,
74B918592319B2DD00ADDEA4 /* Book.xcdatamodeld */,
);
path = Clients;
sourceTree = "<group>";
Expand Down Expand Up @@ -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 */,
Expand Down Expand Up @@ -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 = "<group>";
versionGroupType = wrapper.xcdatamodel;
};
/* End XCVersionGroup section */
};
rootObject = 0E3964CC2314BB470093738B /* Project object */;
}
48 changes: 45 additions & 3 deletions iOSBooks/iOSBooks/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
// Copyright © 2019 Guilherme Antunes. All rights reserved.
//

import IQKeyboardManagerSwift
import CoreData
import UIKit

@UIApplicationMain
Expand All @@ -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()

Expand Down Expand Up @@ -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)")
}
}
}

}

13 changes: 13 additions & 0 deletions iOSBooks/iOSBooks/BookDetail/View/BookDetail.storyboard
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,26 @@
</textView>
</subviews>
</stackView>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="4yx-Iu-LCN">
<rect key="frame" x="330" y="56" width="72" height="37"/>
<fontDescription key="fontDescription" name="AvenirNext-Regular" family="Avenir Next" pointSize="18"/>
<state key="normal" title="Favoritar">
<color key="titleColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<connections>
<action selector="saveBook:" destination="6rU-Uw-H3q" eventType="touchUpInside" id="tMi-uV-QFL"/>
</connections>
</button>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="DsL-0l-xi7" firstAttribute="leading" secondItem="SrW-hW-4OI" secondAttribute="leading" constant="12" id="A9U-Mz-1My"/>
<constraint firstItem="DsL-0l-xi7" firstAttribute="top" secondItem="TpO-b0-I35" secondAttribute="bottom" constant="12" id="NTd-sh-A5j"/>
<constraint firstItem="SrW-hW-4OI" firstAttribute="trailing" secondItem="4yx-Iu-LCN" secondAttribute="trailing" constant="12" id="P14-Py-Px5"/>
<constraint firstItem="TpO-b0-I35" firstAttribute="leading" secondItem="SrW-hW-4OI" secondAttribute="leading" constant="12" id="Psc-l9-6GA"/>
<constraint firstAttribute="bottom" secondItem="DsL-0l-xi7" secondAttribute="bottom" id="chY-Lh-qBo"/>
<constraint firstItem="SrW-hW-4OI" firstAttribute="trailing" secondItem="DsL-0l-xi7" secondAttribute="trailing" constant="12" id="doA-ib-ugT"/>
<constraint firstItem="4yx-Iu-LCN" firstAttribute="top" secondItem="SrW-hW-4OI" secondAttribute="top" constant="12" id="gS0-BO-Ivy"/>
<constraint firstItem="TpO-b0-I35" firstAttribute="top" secondItem="SrW-hW-4OI" secondAttribute="top" constant="12" id="y6M-gB-GD3"/>
</constraints>
<viewLayoutGuide key="safeArea" id="SrW-hW-4OI"/>
Expand All @@ -72,6 +84,7 @@
<outlet property="bookTitleLabel" destination="768-XD-nlZ" id="z92-Hg-c8F"/>
<outlet property="buyLinkLabel" destination="baa-VJ-mqK" id="2ro-Qf-yqx"/>
<outlet property="descriptionTextView" destination="AgA-qA-aED" id="zGM-Go-3v2"/>
<outlet property="favoriteButton" destination="4yx-Iu-LCN" id="pHx-y7-Mep"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="7Zi-CG-FdX" userLabel="First Responder" sceneMemberID="firstResponder"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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?
Expand All @@ -36,4 +37,11 @@ class BookDetailViewController: UIViewController {
@objc func dismissScreen() {
viewModel?.presentPreviousStep()
}

// MARK: - Actions
@IBAction func saveBook(_ sender: UIButton) {
viewModel?.saveBookIfNeeded()
toggleFavoriteButton(sender)
}

}
19 changes: 19 additions & 0 deletions iOSBooks/iOSBooks/BookDetail/ViewModel/BookDetailViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ protocol BookDetailViewModelProtocol {
func presentPreviousStep()
func getScreenTitle() -> String?
func getBookBuyLinkURL() -> URL?
func saveBookIfNeeded()
var savedBook: Bool { get }
}

class BookDetailViewModel: BookDetailViewModelProtocol {
Expand All @@ -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 {
Expand Down Expand Up @@ -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)

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}

Expand All @@ -23,12 +26,26 @@ extension BooksListViewController: BooksListViewControllerPresentable {
}

func setupCollectionView() {
title = "iOS Books"
booksCollectionView?.dataSource = self
booksCollectionView?.delegate = self
}

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
}
}
10 changes: 8 additions & 2 deletions iOSBooks/iOSBooks/BooksList/View/BooksListViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
}
26 changes: 24 additions & 2 deletions iOSBooks/iOSBooks/BooksList/View/Subviews/BooksCell.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}

Expand Down
Loading