1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
|
//
// 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://.../dummy.json")!)
let authData = ("..:..").data(using: .utf8)!.base64EncodedString()
request.addValue("Basic \(authData)", forHTTPHeaderField: "Authorization")
request.cachePolicy = .reloadRevalidatingCacheData // Needed otherwise default caching policy seems not to check properly
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
}
}
|