필수 요구 사항의 테마의 의미를 좀 더 확장해서 테마가 카드에 사용할 이모지들, 카드 뒷면 색, UI 배경화면 색을 모두 포괄하는 의미로 정의해서 사용했다.
별도의 구조체를 선언할까 싶기도 했는데 귀찮아서 그냥 (emojis: [String], cardBackColor: UIColor, backgroundColor: UIColor) 형태의 튜플을 사용했다.
사실상 이 과제에 소요한 시간의 2/3 가 테마 구현에 소요됐는데 처음에는 init 이랑 static 을 사용했고, 여기에 쓸데없는 변수도 많이 선언하고, 또 init 자체가 좀 복잡해서 코드가 깔끔하지 않은 느낌이었다(이후에 3강을 들었는데 교수님이 ViewController 에서의 init은 복잡해서 지양한다고 하셨다). 다시 보니까 그냥 lazy 키워드로도 충분한 해결이 가능할 것 같아서 바꿔줬고, 덕분에 코드도 훨씬 깔끔해졌다. 다양한 해결 방법이 있겠지만 이번 주차에 lazy 를 사용하는 방법을 배운만큼 이를 활용해봤다.
themes 변수에 사용가능한 모든 테마들을 배열로 관리하고, randomElement() 메서드로 하나를 이 중 하나를 랜덤으로 선택해서 theme 변수에 저장했다. 그냥 그대로 컴파일하면 themes 와 theme 모두 ViewController 의 프로퍼티라서 빌드에 실패하기 때문에 theme 프로퍼티를 lazy 하게 만들어줬다.
class ViewController: UIViewController {
@IBAction func touchNewGame() {
game = Concentration(numberOfPairsOfCards: (cardButtons.count + 1) / 2)
emoji = [:]
theme = themes.randomElement() ?? defaultTheme
updateViewFromModel()
updateViewByTheme()
}
// MARK: - Theme
lazy private var theme = themes.randomElement() ?? defaultTheme
private func updateViewByTheme() {
for index in cardButtons.indices {
cardButtons[index].backgroundColor = theme.cardBackColor
}
background.backgroundColor = theme.backgroundColor
}
private var themes: [(emojis: [String], cardBackColor: UIColor, backgroundColor: UIColor)] = [
(["🦇", "😱", "🙀", "😈", "🎃", "👻", "🍭", "🍬", "🍎"], cardBackColor: .systemOrange, backgroundColor: .black),
(["🎅", "🧣", "☃️", "🥶", "❄️", "🛷", "⛸", "🏂", "🌨"], cardBackColor: .white, backgroundColor: .systemBlue),
("👷🧱🛠🦺🧰🚧🚜🪚".map { String($0) }, cardBackColor: .systemYellow, backgroundColor: .black),
("🌱🌷🌸🌹🌲🌵🍁🌴🌺🌼🌿🍃".map { String($0) }, cardBackColor: .systemGreen, backgroundColor: .systemBrown),
("💙💚💜❤️💛🤎💖🧡".map { String($0) }, cardBackColor: .systemBrown, backgroundColor: .systemPink),
("🌭🍑🥑🍝🥞🍦🍖".map { String($0) }, cardBackColor: .systemMint, backgroundColor: .systemPurple)
]
private let defaultTheme = (emojis: ["🦇", "😱", "🙀", "😈", "🎃", "👻", "🍭", "🍬", "🍎"], cardBackColor: UIColor.orange, backgroundColor: UIColor.black)
}
문제는 랜덤으로 선택한 테마를 적용하려면 수동으로 미니언들을 부려야 하는 데 말 그대로 명령을 내릴 시점이 없었다. cardButtons 는 UIButton 의 Collection 으로 ViewController 에서 초기화하는 친구들이 아니라서 init 에서는 업데이트할 수가 없었기 때문...이것 때문에 별의 별 생각을 다 하다가 우연히 과제 지시 사항의 Things to Learn 부분에서 viewDidLoad 를 봤는데 보는 순간 알 수 있었다...이 친구가 나를 구해줄 거라는 걸...
공식 문서를 보면 viewDidLoad() 메서드는 view controller 가 자신의 view 체계를 메모리에 올린 직후에 호출되며, 이 메서드를 오버라이딩함으로써 view 가 초기화 될 때 추가적인 작업을 수행할 수 있다. 딱 내가 필요한 함수 그 자체...랜덤으로 선택된 테마에 따라서 배경화면과 카드 뒷면 색깔(즉, 버튼 색깔)이 로딩 시점에 업데이트 되도록 선언해줬더니 해결됐다.
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
updateViewByTheme()
}
private func updateViewByTheme() {
for index in cardButtons.indices {
cardButtons[index].backgroundColor = theme.cardBackColor
}
background.backgroundColor = theme.backgroundColor
}
}
이미 매칭된 카드를 누르는 경우 선택되지 않아야 하므로 이제 flipCount 을 Model 로 옮겨줘야 한다. Concentration 의 chooseCard(at:) 메서드에서 현재 누른 카드가 매칭되지 않은 경우에만 flipCount 가 증가하도록 했다.
문제는 이제 이걸 어디서 view controller 의 flipCountLabel 과 연동해주느냐였다. 연산프로퍼티나 lazy 를 생각하긴 했는데 이러면 didset 을 쓸 수 없었다. 최종적으로는 결국 flipCount 또한 model 의 데이터에 따라 view 를 업데이트하는 것이므로, view controller 의 updateViewFromModel() 메서드에 flipCountLabel.text = "Flips : \(game.flipCount) 를 추가해서 해결했다
class ViewController: UIViewController {
@IBAction func touchNewGame() {
game = Concentration(numberOfPairsOfCards: (cardButtons.count + 1) / 2)
emoji = [:]
theme = themes.randomElement() ?? defaultTheme
updateViewFromModel()
updateViewByTheme()
}
}