-
Notifications
You must be signed in to change notification settings - Fork 15
/
NotificationViewController.swift
184 lines (150 loc) · 5.64 KB
/
NotificationViewController.swift
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
182
183
184
import UIKit
import UserNotifications
import UserNotificationsUI
class NotificationViewController: UIViewController, UNNotificationContentExtension {
// MARK: - Outlets
@IBOutlet private weak var stackView: UIStackView!
@IBOutlet private weak var playAgainButton: UIButton!
@IBOutlet private weak var bestScoreLabel: UILabel!
@IBOutlet private weak var attemptsLabel: UILabel!
// MARK: - Actions
@IBAction func playAgainPressed(_ sender: UIButton) {
resetGame()
}
// MARK: - Variables
private var matchGame = MatchGame()
private var cardViews = [MatchCardView]()
private var boardLayout: (rows: Int, columns: Int) {
if traitCollection.verticalSizeClass == .compact {
return (2, 4)
} else {
return (3, 4)
}
}
// lock/unlock the board to avoid fast fingers, no more than 2 cards visible at a time
private var lockBoard = false {
didSet {
view.isUserInteractionEnabled = !lockBoard
}
}
override func viewDidLoad() {
super.viewDidLoad()
configureBoard(rows: boardLayout.rows, columns: boardLayout.columns)
matchGame.configureGame(numberOfCards: cardViews.count, delegate: self)
}
func didReceive(_ notification: UNNotification) {
// do nothing
}
}
// MARK: - MatchGame Delegate
extension NotificationViewController: MatchGameDelegate {
func cardsDidLoad(_ cards: [MatchCard]) {
configureCardViews(cards)
}
func cardsDidMatch(_ indicies: [Int], currentScore: Int) {
updateBoard(currentScore: currentScore) {
self.fadeOutMatchedCards(at: indicies)
}
}
func cardsDidNotMatch(_ indicies: [Int], currentScore: Int) {
updateBoard(currentScore: currentScore) {
self.flipUnmatchedCards(at: indicies)
}
}
}
// MARK: - Private Methods
private extension NotificationViewController {
func configureBoard(rows: Int, columns: Int) {
for _ in 0..<rows {
let matchCardsStackView = UIStackView(axis: .horizontal, distribution: .equalSpacing, spacing: 10)
for _ in 0..<columns {
let cardView: MatchCardView = .fromNib()
matchCardsStackView.addArrangedSubview(cardView)
cardView.translatesAutoresizingMaskIntoConstraints = false
cardView.widthAnchor.constraint(equalTo: cardView.heightAnchor, multiplier: 2.5/3.5).isActive = true
cardViews.append(cardView)
}
stackView.addArrangedSubview(matchCardsStackView)
}
}
/// Configures each `MatchCardView`'s flipped image with an associated `selectedImage` (torchie) and `tag`. If the number of `cards` do not equal the number of `cardViews`, mistakes were made.
/// - parameter cards: The array of shuffled cards that will be assigned to an array of `cardViews` one by one.
func configureCardViews(_ cards: [MatchCard]) {
guard cards.count == cardViews.count else { fatalError("Mistakes were made") }
var tag = 0
for (card, cardView) in zip(cards, cardViews) {
cardView.configureView(selectedImage: card.selectedImage, tag: tag, delegate: self)
tag += 1
}
}
func updateBoard(currentScore: Int, animateBoard: @escaping () -> ()) {
disableBoard()
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
self.updateScore(currentScore: currentScore)
animateBoard()
self.enableBoard()
}
}
func fadeOutMatchedCards(at indicies: [Int]) {
UIView.animate(withDuration: 0.25) {
indicies.forEach { self.fadeOutMatchedCard(at: $0) }
} completion: { _ in
if self.matchGame.noMatchesLeft {
self.displayCompletedBoard()
}
}
}
func fadeOutMatchedCard(at index: Int) {
self.cardViews[index].alpha = 0.0
}
func flipUnmatchedCards(at indicies: [Int]) {
indicies.forEach { cardViews[$0].flipCard() }
}
func disableBoard() {
lockBoard = true
}
func enableBoard() {
lockBoard = false
}
func updateScore(currentScore: Int) {
attemptsLabel.text = String(currentScore)
}
func displayCompletedBoard() {
let highScore = matchGame.getHighScore()
bestScoreLabel.text = String(highScore)
playAgainButton.isHidden = false
saveCompletedMatchGameEvent(with: highScore)
}
func resetGame() {
matchGame.playAgain()
playAgainButton.isHidden = true
cardViews.forEach {
$0.flipCard()
$0.alpha = 1.0
}
attemptsLabel.text = "0"
}
}
// MARK: - MatchView Delegate
extension NotificationViewController: MatchCardViewDelegate {
func cardTapped(at index: Int) {
matchGame.cardFlipped(at: index)
}
}
// MARK: - Analytics
private extension NotificationViewController {
/// Saves a custom event to `userDefaults` with the given suite name that is your `App Group` name. The value `"Event Name`" is explicity saved and `highScore` is added to the `properties` dictionary.
/// - parameter highScore: The value that will be saved to the `properties` dictionary with the key `"Score"`.
///
/// There is a conditional unwrap to check if there are saved pending events (in the case of a user completing multiple games) and appends the event or saves a new array with one event.
func saveCompletedMatchGameEvent(with highScore: Int) {
let customEventDictionary = Dictionary(eventName: "Completed Match Game", properties: ["Score": highScore])
let remoteStorage = RemoteStorage(storageType: .suite)
if var pendingEvents = remoteStorage.retrieve(forKey: .pendingCustomEvents) as? [[String: Any]] {
pendingEvents.append(contentsOf: [customEventDictionary])
remoteStorage.store(pendingEvents, forKey: .pendingCustomEvents)
} else {
remoteStorage.store([customEventDictionary], forKey: .pendingCustomEvents)
}
}
}