//
// ForayTableViewController.swift
// foray
//
// Created by Nicholas Tay on 14/3/2022.
//
import UIKit
enum ItemType: String, Decodable {
case item
case quest
}
struct PenguinItem: Decodable {
var type: ItemType
var releaseDate: Date
var id: String
var name: String
}
struct YearSection {
var year: Date
var items: [PenguinItem]
}
class ForayTableViewCell: UITableViewCell {
@IBOutlet weak var cellItemName: UILabel!
@IBOutlet weak var cellItemSubtitle: UILabel!
@IBOutlet weak var cellItemImage: UIImageView!
}
// copied from sample project
private func parseDate(_ str : String) -> Date {
let dateFormat = DateFormatter()
dateFormat.dateFormat = "yyyy-MM-dd"
return dateFormat.date(from: str)!
}
private func firstDayOfYear(date: Date) -> Date {
let calendar = Calendar.current
let components = calendar.dateComponents([.year], from: date)
return calendar.date(from: components)!
}
class ForayTableViewController: UITableViewController {
// MARK: - Static data TEMP
var items = [PenguinItem]()
var sections = [YearSection]()
// MARK: - On load
override func viewDidLoad() {
super.viewDidLoad()
// Not sure if this is the right way to go about this...
let alert = UIAlertController(title: nil, message: "Grabbing data...", preferredStyle: .alert)
let loadingIndicator = UIActivityIndicatorView(frame: CGRect(x: 10, y: 5, width: 50, height: 50))
loadingIndicator.hidesWhenStopped = true
loadingIndicator.style = UIActivityIndicatorView.Style.medium
loadingIndicator.startAnimating();
alert.view.addSubview(loadingIndicator)
present(alert, animated: true, completion: nil)
reloadApiData()
dismiss(animated: false, completion: nil)
// Not 100% sure what this does (the for: bit)
self.refreshControl?.addTarget(self, action: #selector(doRefresh), for: UIControl.Event.valueChanged)
}
// Not sure why need @objc. Is it due to class private/public?
@objc func doRefresh(sender: AnyObject) {
reloadApiData()
}
func reloadApiData() {
loadApiData(onComplete: { (apiItems) in
self.items = apiItems
self.items.sort { (lhs, rhs) in lhs.releaseDate < rhs.releaseDate }
let groups = Dictionary(grouping: self.items) { (item) in
return firstDayOfYear(date: item.releaseDate)
}
self.sections = groups.map { (key, values) in
return YearSection(year: key, items: values)
}
// Sort the sections from oldest year to newest
self.sections.sort { (lhs, rhs) in lhs.year < rhs.year }
self.tableView.reloadData()
self.refreshControl?.endRefreshing()
})
}
func loadApiData(onComplete: @escaping ([PenguinItem]) -> ()) {
// return [
// PenguinItem(type: .item, releaseDate: parseDate("2006-05-26"), id: "mh", name: "Miners Helmet"),
// PenguinItem(type: .item, releaseDate: parseDate("2010-05-01"), id: "it", name: "Inner Tube"),
// PenguinItem(type: .item, releaseDate: parseDate("2009-04-24"), id: "tbg", name: "Toboggan"),
// PenguinItem(type: .item, releaseDate: parseDate("2006-03-29"), id: "spy", name: "Spy Phone"),
// PenguinItem(type: .item, releaseDate: parseDate("2008-11-18"), id: "bnb", name: "Black Ninja Belt"),
// PenguinItem(type: .quest, releaseDate: parseDate("2006-05-23"), id: "cmp", name: "Case of the Missing Puffles"),
// PenguinItem(type: .quest, releaseDate: parseDate("2009-11-16"), id: "gsm", name: "G's Secret Mission"),
// PenguinItem(type: .quest, releaseDate: parseDate("2009-04-18"), id: "cmc", name: "Case of the Missing Coins"),
// ]
var request = URLRequest(url: URL(string: "https://users.windblume.net/~nick/upload/dummy.json")!)
request.cachePolicy = .reloadRevalidatingCacheData // Needed otherwise default caching policy seems not to check properly
// Basic auth if required
//let authData = ("ext:PASSWORD").data(using: .utf8)!.base64EncodedString()
//request.addValue("Basic \(authData)", forHTTPHeaderField: "Authorization")
URLSession.shared.dataTask(with: request, completionHandler: { data, response, error -> Void in
print("finished getting data")
print(response!)
let jsonDecoder = JSONDecoder()
jsonDecoder.dateDecodingStrategy = .custom({ (decoder) -> Date in
let container = try decoder.singleValueContainer()
let dateStr = try container.decode(String.self)
return parseDate(dateStr)
})
let items = try! jsonDecoder.decode([PenguinItem].self, from: data!)
print("json decoded")
// Passing back to UI, need to do it on the main thread (I think due to async?)
DispatchQueue.main.async {
onComplete(items)
}
}).resume()
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
// Returns number of sections for table
return self.sections.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// Returns number of rows for table's section
return self.sections[section].items.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let item = self.sections[indexPath.section].items[indexPath.row]
let cell: ForayTableViewCell
switch item.type {
case .item:
cell = tableView.dequeueReusableCell(withIdentifier: "ForayCell", for: indexPath) as! ForayTableViewCell
cell.cellItemImage?.image = UIImage(named: item.id)
case .quest:
cell = tableView.dequeueReusableCell(withIdentifier: "ForayQuestCell", for: indexPath) as! ForayTableViewCell
}
cell.cellItemName?.text = item.name
cell.cellItemSubtitle?.text = "ID: " + item.id
return cell
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
let section = self.sections[section]
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy"
return "Released in " + dateFormatter.string(from: section.year)
}
// MARK: - Navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let indexPath = tableView.indexPathForSelectedRow!
let item = self.sections[indexPath.section].items[indexPath.row]
let dvc = segue.destination as! ForayDetailViewController
dvc.selectedItem = item
}
}