From 26b5b849c2428acb6892d1f2d4d71719e89b6c45 Mon Sep 17 00:00:00 2001 From: Thomas Minshull Date: Tue, 1 Oct 2019 17:26:27 -0600 Subject: [PATCH 01/13] Update Access control in ComicDetailsViewController. --- sources/ComicDetailsViewController.swift | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/sources/ComicDetailsViewController.swift b/sources/ComicDetailsViewController.swift index 5ff6d2b..e18b3f2 100644 --- a/sources/ComicDetailsViewController.swift +++ b/sources/ComicDetailsViewController.swift @@ -12,14 +12,14 @@ class ComicDetailsViewController: UIViewController { var tags:[String]? //Mark: UI Elements - let comicImageView: UIImageView = { + fileprivate let comicImageView: UIImageView = { let imageView = UIImageView(frame: CGRect(x: 0,y: 0,width: 150,height: 150)) imageView.contentMode = UIView.ContentMode.scaleAspectFill imageView.clipsToBounds = true return imageView }() - let tagCollectionView: UICollectionView = { + fileprivate let tagCollectionView: UICollectionView = { let layout = UICollectionViewFlowLayout() layout.scrollDirection = .vertical layout.estimatedItemSize = CGSize(width: 30, height: 30) @@ -30,7 +30,7 @@ class ComicDetailsViewController: UIViewController { return collectionView }() - let tagTextField: UITextField = { + fileprivate let tagTextField: UITextField = { let tagTextField = UITextField(frame: CGRect.zero) tagTextField.translatesAutoresizingMaskIntoConstraints = false tagTextField.borderStyle = .roundedRect @@ -38,7 +38,7 @@ class ComicDetailsViewController: UIViewController { return tagTextField }() - let addTagButton: UIButton = { + fileprivate let addTagButton: UIButton = { let addTagButton = UIButton(frame: CGRect.zero) addTagButton.setTitle("Add", for: .normal) addTagButton.contentEdgeInsets = UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8) @@ -123,11 +123,13 @@ class ComicDetailsViewController: UIViewController { } //Mark: Navigation Actions - @objc func onSave() { + @objc + fileprivate func onSave() { //To do callback } - @objc func onCancel() { + @objc + fileprivate func onCancel() { dismiss(animated: true) } } @@ -144,7 +146,6 @@ extension ComicDetailsViewController: UICollectionViewDataSource { tagCell.textLabel.text = tags?[indexPath.row] return tagCell } - } extension ComicDetailsViewController: UICollectionViewDelegate { @@ -154,7 +155,7 @@ extension ComicDetailsViewController: UICollectionViewDelegate { class TagCell: UICollectionViewCell { - var textLabel:UILabel = { + fileprivate var textLabel:UILabel = { let label = UILabel() label.translatesAutoresizingMaskIntoConstraints = false label.font = UIFont.preferredFont(forTextStyle: .subheadline) From 3205c6238518ae054bf51778ecf824078f78b0a4 Mon Sep 17 00:00:00 2001 From: Thomas Minshull Date: Tue, 1 Oct 2019 18:23:54 -0600 Subject: [PATCH 02/13] Add empty hidden .gitkeep file in xkcdTests directory, inorder to have the directory tracked by git. --- xkcdTests/.gitkeep | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 xkcdTests/.gitkeep diff --git a/xkcdTests/.gitkeep b/xkcdTests/.gitkeep new file mode 100644 index 0000000..e69de29 From de138384a902676462a09642e31397bda749c466 Mon Sep 17 00:00:00 2001 From: Thomas Minshull Date: Tue, 1 Oct 2019 18:29:08 -0600 Subject: [PATCH 03/13] Update TagCell initializer to implemnt both init(frame:) & init(coder). --- sources/ComicDetailsViewController.swift | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/sources/ComicDetailsViewController.swift b/sources/ComicDetailsViewController.swift index e18b3f2..a4b6267 100644 --- a/sources/ComicDetailsViewController.swift +++ b/sources/ComicDetailsViewController.swift @@ -164,7 +164,17 @@ class TagCell: UICollectionViewCell { override init(frame: CGRect) { super.init(frame: frame) + commonInit() + } + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + commonInit() + } + + private func commonInit() { addSubview(textLabel) + textLabel.topAnchor.constraint(equalTo: self.topAnchor).isActive = true textLabel.heightAnchor.constraint(equalTo: self.heightAnchor).isActive = true self.widthAnchor.constraint(equalTo: textLabel.widthAnchor, constant: 8).isActive = true @@ -173,11 +183,6 @@ class TagCell: UICollectionViewCell { layer.cornerRadius = 8.0 } - required init?(coder aDecoder: NSCoder) { - super.init(coder: aDecoder) - print("Not implemented") - } - override var isSelected: Bool { didSet { switch isSelected { From e4e66562c424e6d1b91e40f9dab8b1e33cad1733 Mon Sep 17 00:00:00 2001 From: Thomas Minshull Date: Tue, 1 Oct 2019 18:54:20 -0600 Subject: [PATCH 04/13] Fixed project.yml bug where buildScripts where only part of the test target. --- project.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/project.yml b/project.yml index cb6bce1..2bd3081 100644 --- a/project.yml +++ b/project.yml @@ -9,18 +9,18 @@ targets: sources: - sources - assets - xkcdTests: - type: bundle.unit-test + postbuildScripts: + - path: scripts/swiftlint.sh + name: SwiftLint + - path: scripts/xcodeFormating.sh + name: XcodeFormating + Tests: + type: bundle.unit-test platform: iOS sources: - - xkcdTests + - xkcdTests dependencies: - target: xkcd scheme: testTargets: - - xkcdTests - postbuildScripts: - - path: scripts/swiftlint.sh - name: SwiftLint - - path: scripts/xcodeFormating.sh - name: XcodeFormating + - Tests From 66fc7986a994bb441b782065e4969dd69122cbb7 Mon Sep 17 00:00:00 2001 From: Thomas Minshull Date: Tue, 1 Oct 2019 18:58:47 -0600 Subject: [PATCH 05/13] Fix SwiftLint warnings. --- sources/ComicDetailsViewController.swift | 43 ++++++++++++++---------- sources/ComicViewController.swift | 6 ---- 2 files changed, 26 insertions(+), 23 deletions(-) diff --git a/sources/ComicDetailsViewController.swift b/sources/ComicDetailsViewController.swift index a4b6267..c317b86 100644 --- a/sources/ComicDetailsViewController.swift +++ b/sources/ComicDetailsViewController.swift @@ -2,18 +2,17 @@ // Created: 2019-09-21 // - import UIKit class ComicDetailsViewController: UIViewController { - var comicId:Int? - var comicImage:UIImage? - var tags:[String]? + var comicId: Int? + var comicImage: UIImage? + var tags: [String]? - //Mark: UI Elements + // MARK: UI Elements fileprivate let comicImageView: UIImageView = { - let imageView = UIImageView(frame: CGRect(x: 0,y: 0,width: 150,height: 150)) + let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 150, height: 150)) imageView.contentMode = UIView.ContentMode.scaleAspectFill imageView.clipsToBounds = true return imageView @@ -47,7 +46,7 @@ class ComicDetailsViewController: UIViewController { return addTagButton }() - //Mark: Methods + // MARK: Methods override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .white @@ -61,7 +60,7 @@ class ComicDetailsViewController: UIViewController { setupTagCollectionView() } - fileprivate static func createHorizontalRowStack() -> UIStackView{ + fileprivate static func createHorizontalRowStack() -> UIStackView { let horizontalStackView = UIStackView(frame: CGRect.zero) horizontalStackView.axis = .horizontal horizontalStackView.translatesAutoresizingMaskIntoConstraints = false @@ -74,7 +73,7 @@ class ComicDetailsViewController: UIViewController { fileprivate static func createColumnStack() -> UIStackView { let verticalStackView = UIStackView(frame: CGRect.zero) verticalStackView.axis = .vertical - verticalStackView.translatesAutoresizingMaskIntoConstraints = false; + verticalStackView.translatesAutoresizingMaskIntoConstraints = false return verticalStackView } @@ -107,8 +106,16 @@ class ComicDetailsViewController: UIViewController { } fileprivate func setupNavigationBar() { - navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(onCancel)) - navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .save, target: self, action: #selector(onSave)) + navigationItem.leftBarButtonItem = UIBarButtonItem( + barButtonSystemItem: .cancel, + target: self, + action: #selector(onCancel) + ) + navigationItem.rightBarButtonItem = UIBarButtonItem( + barButtonSystemItem: .save, + target: self, + action: #selector(onSave) + ) navigationController?.navigationBar.isTranslucent = false } @@ -119,13 +126,13 @@ class ComicDetailsViewController: UIViewController { } fileprivate func loadTags() { - tags = ["Astronomy", "Discovery", "Futility", "Survival", "Scientist"].sorted() + tags = ["Astronomy", "Discovery", "Futility", "Survival", "Scientist"].sorted() } - //Mark: Navigation Actions + // MARK: Navigation Actions @objc fileprivate func onSave() { - //To do callback + //Todo callback } @objc @@ -140,7 +147,10 @@ extension ComicDetailsViewController: UICollectionViewDataSource { } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { - guard let tagCell = collectionView.dequeueReusableCell(withReuseIdentifier: "TagCell", for: indexPath) as? TagCell else { + guard let tagCell = collectionView.dequeueReusableCell( + withReuseIdentifier: "TagCell", + for: indexPath + ) as? TagCell else { return UICollectionViewCell() } tagCell.textLabel.text = tags?[indexPath.row] @@ -152,10 +162,9 @@ extension ComicDetailsViewController: UICollectionViewDelegate { } - class TagCell: UICollectionViewCell { - fileprivate var textLabel:UILabel = { + fileprivate var textLabel: UILabel = { let label = UILabel() label.translatesAutoresizingMaskIntoConstraints = false label.font = UIFont.preferredFont(forTextStyle: .subheadline) diff --git a/sources/ComicViewController.swift b/sources/ComicViewController.swift index 21856c1..1dd87dc 100644 --- a/sources/ComicViewController.swift +++ b/sources/ComicViewController.swift @@ -51,9 +51,6 @@ class ComicViewController: UIViewController { } private func addConstraints() { - // TODO - Added by Arun - // commenting the next line because we are still loading this view fro the storyboard - // view.translatesAutoresizingMaskIntoConstraints = false comicImageView.translatesAutoresizingMaskIntoConstraints = false view.centerXAnchor.constraint(equalTo: comicImageView.centerXAnchor).isActive = true @@ -72,7 +69,6 @@ class ComicViewController: UIViewController { comicImageView.addGestureRecognizer(tapGestureRecognizer) } - // MARK: UTILITIES private func nextComic() { @@ -93,7 +89,6 @@ class ComicViewController: UIViewController { currentComic = previousComic } - private func showDetails() { let comicDetailsViewController = ComicDetailsViewController() comicDetailsViewController.comicId = currentComic?.id @@ -106,7 +101,6 @@ class ComicViewController: UIViewController { return Int.random(in: 0 ... 2198) } - // MARK: ACTIONS @objc func handleBackGesture(_ sender: UISwipeGestureRecognizer) { From 9390e8d68d8e2b73864220f0daee1cb692ea8397 Mon Sep 17 00:00:00 2001 From: Thomas Minshull Date: Tue, 1 Oct 2019 20:12:42 -0600 Subject: [PATCH 06/13] Rename xkcdTests -> tests & updated test target name. --- project.yml | 20 ++++++++++---------- {xkcdTests => tests}/.gitkeep | 0 2 files changed, 10 insertions(+), 10 deletions(-) rename {xkcdTests => tests}/.gitkeep (100%) diff --git a/project.yml b/project.yml index 2bd3081..9e1ed23 100644 --- a/project.yml +++ b/project.yml @@ -3,6 +3,16 @@ options: minimumXcodeGenVersion: 2.5.0 bundleIdPrefix: com.codecritique targets: + Tests: + type: bundle.unit-test + platform: iOS + sources: + - tests + dependencies: + - target: xkcd + scheme: + testTargets: + - Tests xkcd: type: application platform: iOS @@ -14,13 +24,3 @@ targets: name: SwiftLint - path: scripts/xcodeFormating.sh name: XcodeFormating - Tests: - type: bundle.unit-test - platform: iOS - sources: - - xkcdTests - dependencies: - - target: xkcd - scheme: - testTargets: - - Tests diff --git a/xkcdTests/.gitkeep b/tests/.gitkeep similarity index 100% rename from xkcdTests/.gitkeep rename to tests/.gitkeep From c2089b4203c9c30565467aa57a5314515d40ce6e Mon Sep 17 00:00:00 2001 From: Thomas Minshull Date: Tue, 1 Oct 2019 20:18:07 -0600 Subject: [PATCH 07/13] Remove functions with default implementation. --- sources/AppDelegate.swift | 6 ------ sources/RandomComicViewController.swift | 4 ---- 2 files changed, 10 deletions(-) diff --git a/sources/AppDelegate.swift b/sources/AppDelegate.swift index 3956b7d..e31bdbd 100644 --- a/sources/AppDelegate.swift +++ b/sources/AppDelegate.swift @@ -11,10 +11,4 @@ import UIKit @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? - - func application(_ application: UIApplication, - didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { - // Override point for customization after application launch. - return true - } } diff --git a/sources/RandomComicViewController.swift b/sources/RandomComicViewController.swift index d7349dd..3c58122 100644 --- a/sources/RandomComicViewController.swift +++ b/sources/RandomComicViewController.swift @@ -11,10 +11,6 @@ class RandomComicViewController: UIViewController { @IBOutlet weak var comicImage: UIImageView! - override func viewDidLoad() { - super.viewDidLoad() - } - @IBAction func generateComic(_ sender: UIButton) { fetchComicData(completion: displayImage(comic:)) From 18ebd03bbf62a4f34f214053d86c2e96aff4bf5c Mon Sep 17 00:00:00 2001 From: Thomas Minshull Date: Wed, 2 Oct 2019 09:56:13 -0600 Subject: [PATCH 08/13] Make Networking code DRY-er by moving code to NetworkManager. --- sources/ComicViewController.swift | 75 +++++++++---------------- sources/NetworkManager.swift | 51 +++++++++++++++++ sources/RandomComicViewController.swift | 57 ++++--------------- 3 files changed, 87 insertions(+), 96 deletions(-) create mode 100644 sources/NetworkManager.swift diff --git a/sources/ComicViewController.swift b/sources/ComicViewController.swift index 1dd87dc..f11d021 100644 --- a/sources/ComicViewController.swift +++ b/sources/ComicViewController.swift @@ -9,6 +9,7 @@ import UIKit class ComicViewController: UIViewController { let comicImageView = UIImageView() + let networkManager = NetworkManager() lazy var backGestureRecognizer: UISwipeGestureRecognizer = { var swipeGesture = UISwipeGestureRecognizer(target: self, action: #selector(handleBackGesture(_:))) @@ -35,7 +36,13 @@ class ComicViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() setupSubViews() - fetchComicData(completion: displayImage(comic:)) + networkManager.fetchRandomComic { [weak self] (comic) in + guard let comic = comic else { + return + } + + self?.displayImage(comic: comic) + } } private func setupSubViews() { @@ -75,7 +82,13 @@ class ComicViewController: UIViewController { if let currentComic = currentComic { historyStack.push(currentComic) } - fetchComicData(completion: displayImage(comic:)) + + networkManager.fetchRandomComic { [weak self] (comicModel) in + guard let comicModel = comicModel else { + return + } + self?.displayImage(comic: comicModel) + } } private func previousComic() { @@ -97,8 +110,17 @@ class ComicViewController: UIViewController { present(navigationController, animated: false) } - private func generateRandomNumber() -> Int { - return Int.random(in: 0 ... 2198) + private func displayImage(comic: ComicModel) { + networkManager.fetchImage(for: comic) { (image) in + guard let image = image else { + return + } + + DispatchQueue.main.async { + self.currentComic = comic + self.comicImageView.image = image + } + } } // MARK: ACTIONS @@ -114,49 +136,4 @@ class ComicViewController: UIViewController { @objc func handleTapGesture(_ sender: UITapGestureRecognizer) { showDetails() } - - // MARK: Navigation - - private func fetchComicData(completion: @escaping (ComicModel) -> Void) { - - let number = generateRandomNumber() - let urlString = "https://xkcd.com/\(number)/info.0.json" - let url = URL(string: urlString) - let session = URLSession.shared.dataTask(with: url!) { (data, _, _) in - - let jsonDecoder = JSONDecoder() - - guard let data = data else { - print("No data has been returned") - return - - } - do { - let comicInfo = try jsonDecoder.decode(ComicModel.self, from: data) - completion(comicInfo) - - } catch { - print("Failed at decoding") - } - } - - session.resume() - } - - private func displayImage(comic: ComicModel) { - let session = URLSession.shared.dataTask(with: comic.imageURL) { (data, _, _) in - - guard let data = data else { - print("No data has been returned") - return - } - let image = UIImage(data: data) - DispatchQueue.main.async { - self.currentComic = comic - self.comicImageView.image = image - } - - } - session.resume() - } } diff --git a/sources/NetworkManager.swift b/sources/NetworkManager.swift new file mode 100644 index 0000000..40c6c7c --- /dev/null +++ b/sources/NetworkManager.swift @@ -0,0 +1,51 @@ +// +// NetworkManager.swift +// xkcd +// +// Created by thomas minshull on 2019-10-01. +// + +import UIKit + +struct NetworkManager { + func generateRandomId() -> Int { + return Int.random(in: 0 ... 2198) + } + + func fetchRandomComic(completion: @escaping (ComicModel?) -> Void) { + let randomId = generateRandomId() + fetchComicData(id: randomId, completion: completion) + } + + func fetchComicData(id: Int, completion: @escaping (ComicModel?) -> Void) { // swiftlint:disable:this identifier_name + let urlString = "https://xkcd.com/\(id)/info.0.json" + let url = URL(string: urlString) + let session = URLSession.shared.dataTask(with: url!) { (data, _, _) in + + let jsonDecoder = JSONDecoder() + + guard let data = data else { + print("No data has been returned") + return + + } + let comicInfo = try? jsonDecoder.decode(ComicModel.self, from: data) + completion(comicInfo) + } + session.resume() + } + + func fetchImage(for comic: ComicModel, with completion: @escaping ((UIImage?) -> Void)) { + let session = URLSession.shared.dataTask(with: comic.imageURL) { (data, _, _) in + + guard let data = data else { + print("No data has been returned") + return + } + + let image = UIImage(data: data) + completion(image) + } + session.resume() + } +} diff --git a/sources/RandomComicViewController.swift b/sources/RandomComicViewController.swift index 3c58122..ec855c2 100644 --- a/sources/RandomComicViewController.swift +++ b/sources/RandomComicViewController.swift @@ -9,57 +9,20 @@ import UIKit class RandomComicViewController: UIViewController { + let networkManager = NetworkManager() + @IBOutlet weak var comicImage: UIImageView! @IBAction func generateComic(_ sender: UIButton) { - - fetchComicData(completion: displayImage(comic:)) - - } - - func generateRandomNumber() -> Int { - return Int.random(in: 0 ... 2198) - } - - func fetchComicData(completion: @escaping (ComicModel) -> Void) { - let number = generateRandomNumber() - let urlString = "https://xkcd.com/\(number)/info.0.json" - let url = URL(string: urlString) - let session = URLSession.shared.dataTask(with: url!) { (data, _, _) in - - let jsonDecoder = JSONDecoder() - - guard let data = data else { - print("No data has been returned") - return - + let randomId = networkManager.generateRandomId() + networkManager.fetchComicData(id: randomId) { [weak self] (comicModel) in + guard let comicModel = comicModel else { return } + + self?.networkManager.fetchImage(for: comicModel) { (image) in + DispatchQueue.main.async { + self?.comicImage.image = image + } } - do { - let comicInfo = try jsonDecoder.decode(ComicModel.self, from: data) - completion(comicInfo) - - } catch { - print("Failed at decoding") - } - } - - session.resume() - } - - func displayImage(comic: ComicModel) { - let session = URLSession.shared.dataTask(with: comic.imageURL) { (data, _, _) in - - guard let data = data else { - print("No data has been returned") - return - } - let image = UIImage(data: data) - DispatchQueue.main.async { - self.comicImage.image = image - } - } - session.resume() } - } From f9c62e610eb20f243580dcdd538551c6672f1cdb Mon Sep 17 00:00:00 2001 From: Thomas Minshull Date: Thu, 3 Oct 2019 18:53:18 -0700 Subject: [PATCH 09/13] Extended NetworkManager to fetch array of all tags. --- sources/NetworkError.swift | 14 ++++++++++++ sources/NetworkManager.swift | 41 +++++++++++++++++++++++++++++++++--- sources/Tag.swift | 18 ++++++++++++++++ sources/XKCDError.swift | 1 + 4 files changed, 71 insertions(+), 3 deletions(-) create mode 100644 sources/NetworkError.swift create mode 100644 sources/Tag.swift diff --git a/sources/NetworkError.swift b/sources/NetworkError.swift new file mode 100644 index 0000000..1790c9c --- /dev/null +++ b/sources/NetworkError.swift @@ -0,0 +1,14 @@ +// +// NetworkError.swift +// xkcd +// +// Created by thomas minshull on 2019-10-03. +// + +import Foundation + +enum NetworkError: Error { + case clientError + case serverError + case defaultNetworkError +} diff --git a/sources/NetworkManager.swift b/sources/NetworkManager.swift index 40c6c7c..5137e2c 100644 --- a/sources/NetworkManager.swift +++ b/sources/NetworkManager.swift @@ -19,8 +19,8 @@ struct NetworkManager { func fetchComicData(id: Int, completion: @escaping (ComicModel?) -> Void) { // swiftlint:disable:this identifier_name let urlString = "https://xkcd.com/\(id)/info.0.json" - let url = URL(string: urlString) - let session = URLSession.shared.dataTask(with: url!) { (data, _, _) in + let url = URL(string: urlString)! + let dataTask = URLSession.shared.dataTask(with: url) { (data, _, _) in let jsonDecoder = JSONDecoder() @@ -32,7 +32,7 @@ struct NetworkManager { let comicInfo = try? jsonDecoder.decode(ComicModel.self, from: data) completion(comicInfo) } - session.resume() + dataTask.resume() } func fetchImage(for comic: ComicModel, with completion: @escaping ((UIImage?) -> Void)) { @@ -48,4 +48,39 @@ struct NetworkManager { } session.resume() } + + func fetchTags(completion: @escaping ((Result<[Tag], Error>) -> Void)) { + let urlString = "https://ivggashpl0.execute-api.us-west-2.amazonaws.com/staging/tags" + let url = URL(string: urlString)! + let dataTask = URLSession.shared.dataTask(with: url) { (data, response, _) in + + let jsonDecoder = JSONDecoder() + + guard let data = data else { + guard let response = response as? HTTPURLResponse else { + completion(.failure(NetworkError.defaultNetworkError)) + return + } + + switch response.statusCode { + case (400 ..< 500): + completion(.failure(NetworkError.clientError)) + case (500 ..< 600): + completion(.failure(NetworkError.serverError)) + default: + completion(.failure(NetworkError.defaultNetworkError)) + } + + return + } + + guard let tags = try? jsonDecoder.decode([Tag].self, from: data) else { + completion(.failure(XKCDError.failedToParseTagArray)) + return + } + + completion(.success(tags)) + } + dataTask.resume() + } } diff --git a/sources/Tag.swift b/sources/Tag.swift new file mode 100644 index 0000000..a1cce28 --- /dev/null +++ b/sources/Tag.swift @@ -0,0 +1,18 @@ +// +// Tag.swift +// xkcd +// +// Created by thomas minshull on 2019-10-03. +// + +import Foundation + +struct Tag: Codable { + let title: String + let comicId: Int + + enum CodingKeys: String, CodingKey { + case title + case comicId = "id" + } +} diff --git a/sources/XKCDError.swift b/sources/XKCDError.swift index 78466c3..7e0a1a6 100644 --- a/sources/XKCDError.swift +++ b/sources/XKCDError.swift @@ -9,4 +9,5 @@ import Foundation enum XKCDError: Error { case failedToParseURL + case failedToParseTagArray } From c8de78fb43b817f5f0b2a953a03aab0d1cbd5ad5 Mon Sep 17 00:00:00 2001 From: Thomas Minshull Date: Sun, 6 Oct 2019 12:13:12 -0700 Subject: [PATCH 10/13] Updated CommicDetailsViewController to have a refference to a commicModel and not just it's id. --- sources/ComicDetailsViewController.swift | 12 +++++++++--- sources/ComicViewController.swift | 2 +- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/sources/ComicDetailsViewController.swift b/sources/ComicDetailsViewController.swift index c317b86..9fa5fb6 100644 --- a/sources/ComicDetailsViewController.swift +++ b/sources/ComicDetailsViewController.swift @@ -6,7 +6,7 @@ import UIKit class ComicDetailsViewController: UIViewController { - var comicId: Int? + var comic: ComicModel? var comicImage: UIImage? var tags: [String]? @@ -51,8 +51,11 @@ class ComicDetailsViewController: UIViewController { super.viewDidLoad() view.backgroundColor = .white - title = comicId.map { String($0) } ?? "Unknown" - comicImageView.image = comicImage + if let title = comic?.id { + self.title = String(title) + } else { + title = "unknown" + } loadTags() setupNavigationBar() @@ -126,6 +129,9 @@ class ComicDetailsViewController: UIViewController { } fileprivate func loadTags() { + + // fet + tags = ["Astronomy", "Discovery", "Futility", "Survival", "Scientist"].sorted() } diff --git a/sources/ComicViewController.swift b/sources/ComicViewController.swift index f11d021..bc064f9 100644 --- a/sources/ComicViewController.swift +++ b/sources/ComicViewController.swift @@ -104,7 +104,7 @@ class ComicViewController: UIViewController { private func showDetails() { let comicDetailsViewController = ComicDetailsViewController() - comicDetailsViewController.comicId = currentComic?.id + comicDetailsViewController.comic = currentComic comicDetailsViewController.comicImage = currentComic?.image let navigationController = UINavigationController(rootViewController: comicDetailsViewController) present(navigationController, animated: false) From 29039e90ebbbb8968fe22be0e04d11eb61705636 Mon Sep 17 00:00:00 2001 From: Thomas Minshull Date: Sun, 6 Oct 2019 15:20:46 -0700 Subject: [PATCH 11/13] Added UITableView with DiffableDataSource for allTags list. --- sources/ComicDetailsViewController.swift | 60 +++++++++++++++++++++--- sources/Tag.swift | 2 + 2 files changed, 56 insertions(+), 6 deletions(-) diff --git a/sources/ComicDetailsViewController.swift b/sources/ComicDetailsViewController.swift index 9fa5fb6..25200a4 100644 --- a/sources/ComicDetailsViewController.swift +++ b/sources/ComicDetailsViewController.swift @@ -5,6 +5,11 @@ import UIKit class ComicDetailsViewController: UIViewController { + enum SearchTagSection: CaseIterable { + case onlySection + } + + private var tagTableViewDataSource: UITableViewDiffableDataSource? var comic: ComicModel? var comicImage: UIImage? @@ -46,6 +51,12 @@ class ComicDetailsViewController: UIViewController { return addTagButton }() + fileprivate var searchTagTableView: UITableView = { + let tableView = UITableView(frame: CGRect.zero) + tableView.translatesAutoresizingMaskIntoConstraints = false + return tableView + }() + // MARK: Methods override func viewDidLoad() { super.viewDidLoad() @@ -61,6 +72,14 @@ class ComicDetailsViewController: UIViewController { setupNavigationBar() layoutElements() setupTagCollectionView() + setUpSearchTagTableView() + + let allTags = [Tag(title: "1", comicId: 1), Tag(title: "tag", comicId: 2)] + let snapShot = NSDiffableDataSourceSnapshot() + snapShot.appendSections([SearchTagSection.onlySection]) + snapShot.appendItems(allTags) + tagTableViewDataSource?.apply(snapShot) + } fileprivate static func createHorizontalRowStack() -> UIStackView { @@ -95,6 +114,7 @@ class ComicDetailsViewController: UIViewController { //Establish layout hierarchy stackContainer.addArrangedSubview(topRow) stackContainer.addArrangedSubview(middleRow) + stackContainer.addArrangedSubview(searchTagTableView) view.addSubview(stackContainer) //Add the constraints @@ -103,6 +123,9 @@ class ComicDetailsViewController: UIViewController { tagTextField.heightAnchor.constraint(equalToConstant: 30).isActive = true addTagButton.heightAnchor.constraint(equalToConstant: 30).isActive = true + searchTagTableView.heightAnchor.constraint(equalToConstant: 150).isActive = true + searchTagTableView.widthAnchor.constraint(equalToConstant: 150).isActive = true + stackContainer.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true stackContainer.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true stackContainer.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true @@ -122,16 +145,33 @@ class ComicDetailsViewController: UIViewController { navigationController?.navigationBar.isTranslucent = false } + fileprivate func setUpSearchTagTableView() { + searchTagTableView.register(TagTableViewCell.self, forCellReuseIdentifier: "TagTableViewCell") + self.tagTableViewDataSource = makeTagTableViewDataSource() + searchTagTableView.dataSource = tagTableViewDataSource + searchTagTableView.delegate = self + } + + private func makeTagTableViewDataSource() -> UITableViewDiffableDataSource { + return UITableViewDiffableDataSource( + tableView: searchTagTableView) { (tableView, indexPath, tag) -> UITableViewCell? in + let cell = tableView.dequeueReusableCell( + withIdentifier: "TagTableViewCell", + for: indexPath + ) + + cell.textLabel?.text = tag.title + return cell + } + } + fileprivate func setupTagCollectionView() { - tagCollectionView.register(TagCell.self, forCellWithReuseIdentifier: "TagCell") + tagCollectionView.register(TagCollectionViewCell.self, forCellWithReuseIdentifier: "TagCell") tagCollectionView.dataSource = self tagCollectionView.delegate = self } fileprivate func loadTags() { - - // fet - tags = ["Astronomy", "Discovery", "Futility", "Survival", "Scientist"].sorted() } @@ -156,7 +196,7 @@ extension ComicDetailsViewController: UICollectionViewDataSource { guard let tagCell = collectionView.dequeueReusableCell( withReuseIdentifier: "TagCell", for: indexPath - ) as? TagCell else { + ) as? TagCollectionViewCell else { return UICollectionViewCell() } tagCell.textLabel.text = tags?[indexPath.row] @@ -168,7 +208,15 @@ extension ComicDetailsViewController: UICollectionViewDelegate { } -class TagCell: UICollectionViewCell { +extension ComicDetailsViewController: UITableViewDelegate { + +} + +class TagTableViewCell: UITableViewCell { + +} + +class TagCollectionViewCell: UICollectionViewCell { fileprivate var textLabel: UILabel = { let label = UILabel() diff --git a/sources/Tag.swift b/sources/Tag.swift index a1cce28..2ef5fa5 100644 --- a/sources/Tag.swift +++ b/sources/Tag.swift @@ -16,3 +16,5 @@ struct Tag: Codable { case comicId = "id" } } + +extension Tag: Hashable { } From 33c4d44e6c4a40cfef6a5b9de533b60985c93fe2 Mon Sep 17 00:00:00 2001 From: Thomas Minshull Date: Mon, 7 Oct 2019 20:57:23 -0700 Subject: [PATCH 12/13] Position TableView and fetch tags. --- sources/ComicDetailsViewController.swift | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/sources/ComicDetailsViewController.swift b/sources/ComicDetailsViewController.swift index 25200a4..e4837c6 100644 --- a/sources/ComicDetailsViewController.swift +++ b/sources/ComicDetailsViewController.swift @@ -10,6 +10,7 @@ class ComicDetailsViewController: UIViewController { } private var tagTableViewDataSource: UITableViewDiffableDataSource? + private var networkManager = NetworkManager() var comic: ComicModel? var comicImage: UIImage? @@ -74,12 +75,17 @@ class ComicDetailsViewController: UIViewController { setupTagCollectionView() setUpSearchTagTableView() - let allTags = [Tag(title: "1", comicId: 1), Tag(title: "tag", comicId: 2)] - let snapShot = NSDiffableDataSourceSnapshot() - snapShot.appendSections([SearchTagSection.onlySection]) - snapShot.appendItems(allTags) - tagTableViewDataSource?.apply(snapShot) - + networkManager.fetchTags { [weak self] (result) in + switch result { + case .success(let tags): + let snapShot = NSDiffableDataSourceSnapshot() + snapShot.appendSections([SearchTagSection.onlySection]) + snapShot.appendItems(tags) + self?.tagTableViewDataSource?.apply(snapShot) + case .failure(let error): + print("Error: ", error) + } + } } fileprivate static func createHorizontalRowStack() -> UIStackView { @@ -100,8 +106,8 @@ class ComicDetailsViewController: UIViewController { } fileprivate func layoutElements() { - let stackContainer = ComicDetailsViewController.createColumnStack() + stackContainer.distribution = .fill let topRow = ComicDetailsViewController.createHorizontalRowStack() let middleRow = ComicDetailsViewController.createHorizontalRowStack() @@ -123,12 +129,12 @@ class ComicDetailsViewController: UIViewController { tagTextField.heightAnchor.constraint(equalToConstant: 30).isActive = true addTagButton.heightAnchor.constraint(equalToConstant: 30).isActive = true - searchTagTableView.heightAnchor.constraint(equalToConstant: 150).isActive = true - searchTagTableView.widthAnchor.constraint(equalToConstant: 150).isActive = true + searchTagTableView.widthAnchor.constraint(equalTo: stackContainer.widthAnchor).isActive = true stackContainer.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true stackContainer.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true stackContainer.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true + stackContainer.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true } fileprivate func setupNavigationBar() { From 2ccd0216059191dac6ee2b4fcd97783eda879b4c Mon Sep 17 00:00:00 2001 From: Thomas Minshull Date: Sat, 19 Oct 2019 10:48:41 -0700 Subject: [PATCH 13/13] Update Tag Model comicId Type. --- sources/Tag.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sources/Tag.swift b/sources/Tag.swift index 2ef5fa5..0bba8e0 100644 --- a/sources/Tag.swift +++ b/sources/Tag.swift @@ -9,7 +9,7 @@ import Foundation struct Tag: Codable { let title: String - let comicId: Int + let comicId: [Int] enum CodingKeys: String, CodingKey { case title