단어장 프로젝트 (4)
4일차 시작.
사실 오래걸릴것같았던 기능들이 하루만에 해결이 되어서 뭘 할지 고민이 많다.
추가로 게임기능 하나정도만 더하면 아마 괜찮지 않을까? 싶어서 하나 더 구현해본다.
아마 제일 빡세지 않을까? 라는 생각이 좀 든다.
우선 알파벳 버튼을 만들어야하는데 A to Z 너무 많다.
이것도 LabelFactory처럼, ButtonFactory를 만들어 준다.
1
2
3
4
5
6
7
8
9
10
11
12
13
class ButtonFactory {
func makeButton(title: String, color: UIColor = .black, backgroundColor: UIColor = .lightGray, completion: @escaping (UIAction) -> Void) -> UIButton {
let button = UIButton()
button.setTitle(title, for: .normal)
button.setTitleColor(color, for: .normal)
button.backgroundColor = backgroundColor
button.layer.borderWidth = 0.3
button.addAction(UIAction(handler: completion), for: .touchUpInside)
return button
}
}
이렇게 되면 addaction도 만들면서 직접 설정이 가능.
그리고 UIDesign을 시작.
결과는 다음과 같다.
이제 Hangman 이미지를 만들어 준다.
이런식으로 디자인을 했다.
실패횟수는 7회로 하면 될듯하다.
Word 부분을 UIView로 만들고 안에 단어의 글자 만큼 _ 로 나오게 표현을 해보았다.
1
2
3
4
5
6
7
8
9
10
11
private func makeWordLabel () {
for i in 0 ... dummyList[gameCount].words.count - 1 {
print(dummyList[gameCount].words)
let label = LabelFactory().hangManLabel(title: "_", size: 20, isBold: true)
hangManBodyView.wordFrameView.addSubview(label)
label.snp.makeConstraints {
$0.leading.equalTo(hangManBodyView.wordFrameView.snp.leading).offset(i * 20)
}
}
}
그리고 HangMan게임용 Label을 또 만들어주었다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func hangManLabel (title: String, color: UIColor = .black, size: CGFloat, tag:Int, textAlignment: NSTextAlignment = .center, isBold: Bool) -> UILabel {
let label = UILabel()
label.text = title
label.textColor = color
if isBold == true {
label.font = UIFont.boldSystemFont(ofSize: size)
} else {
label.font = UIFont.systemFont(ofSize: size)
}
label.tag = tag
label.textAlignment = textAlignment
label.numberOfLines = 0
return label
}
이것을 만든 목적은 바로 저 tag를 사용하는데 있다.
이제 게임과 관련된 로직을 구현해야하는데 순환참조가 발생한다.
1
2
3
4
5
6
7
lazy var buttonA = ButtonFactory().makeButton(title: "A") { [weak currentVC, weak self] _ in
if currentVC?.dummyList[currentVC!.gameCount].words.contains("A") == true {
} else {
}
}
바로 여기서 buttonA 자기 자신에 대해서 처리를 해야하다보니. 순환참조 에러가 발생.
고민을 하다가 이 부분을 튜터님께 여쭤보니 Notification Center를 사용을 해보는게 어떠냐고 하신다.
Notification Center 란?
Notification Center에 등록된 Event가 발생하면 해당 Event에 대한 행동을 취한다.
우선 ture / false에 따른 Notification Name을 extension을 통해 만들어 준다.
1
2
3
4
extension Notification.Name {
static let trueNotification = Notification.Name("trueNotification")
static let falseNotification = Notification.Name("falseNotification")
}
static을 사용함으로써, 쉽게 사용할수있게 해둠.
그리고 VC로 가서 addObserver를 해준다. (Viewdidload)
1
2
NotificationCenter.default.addObserver(self, selector: #selector(trueAnswer), name: .trueNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(falseAnswer), name: .falseNotification, object: nil)
그리고 버튼에다가는
1
2
3
4
5
6
7
8
9
lazy var buttonA = ButtonFactory().makeButton(title: "A") { [weak currentVC] buttonA in
if currentVC?.dummyList[currentVC!.gameCount].words.contains("A") == true {
print("true")
NotificationCenter.default.post(name: .trueNotification, object: buttonA)
} else {
print("false")
NotificationCenter.default.post(name: .falseNotification, object: buttonA)
}
}
이렇게 하다가 도저히 @objc에서 안되어서 결국 또 Combine에 이어 GPT에게 도움을 요청 근본적으로 button을 만들때 기존처럼 button.addAction
을 통해서 바로 접근하던게 떠올랐는데 그걸 전부 다 길게 할 수 없어서 factory처럼 가능한지 물어봤다.
1
2
3
4
5
6
7
8
9
10
11
12
func makeButton(title: String, color: UIColor = .black, backgroundColor: UIColor = .lightGray, completion: @escaping (UIButton) -> Void) -> UIButton {
let button = UIButton()
button.setTitle(title, for: .normal)
button.setTitleColor(color, for: .normal)
button.backgroundColor = backgroundColor
button.layer.borderWidth = 0.3
button.addAction(UIAction { action in
guard let button = action.sender as? UIButton else { return }
completion(button)
}, for: .touchUpInside)
return button
}
이녀석은 이렇게 제시를 했다.
코드를 보니 일리가 있다.
애초에 내가 처음에 만들었던 buttonFactory에서 completion을 저 버튼으로 넘기면 되었는데, 생각이 너무 짧았다.
그냥 UIAction을 리턴시켜야한다는 그 고정관념에 빠져있었다.
Combine에 이어 이번에 이렇게 button 그자체를 넘기는것도 GPT에게 배워간다.
위의 코드는 꼭 잊지않도록 해야겠다.
1
2
3
4
5
6
7
8
lazy var buttonA = ButtonFactory().makeButton(title: "A") { [weak currentVC, weak self] button in
if currentVC?.dummyList[currentVC!.gameCount].words.contains("A") == true {
self?.checkWord(button: button, backgroundColor: .blue, systemName: "checkmark")
} else {
self?.checkWord(button: button, backgroundColor: .red, systemName: "xmark")
currentVC?.failCount += 1
}
}
이렇게 해서 체크를 했는데
다른 알파벳들도 모두 빨간색으로 리턴한다
확인해보니 currentVC?.dummyList[currentVC!.gameCount].words
가 애초에 없다.
전역변수로 첨에 vc를 만든게 문제로 판단.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func checkWord(button: UIButton) {
guard let currentVC = currentViewController as? HangManGameViewController else { return }
if currentVC.dummyList[currentVC.gameCount].words.contains(button.currentTitle!.lowercased()) {
button.isEnabled = false
button.setTitle("", for: .normal)
button.backgroundColor = .blue
button.setImage(UIImage(systemName: "checkmark"), for: .normal)
} else {
button.isEnabled = false
button.setTitle("", for: .normal)
button.backgroundColor = .red
button.setImage(UIImage(systemName: "xmark"), for: .normal)
currentVC.failCount += 1
currentVC.updateMan()
}
}
단어문제처럼 이렇게 함수에서 모든걸 처리하게 바꿨다.
버튼도 위와같이 바꿔주었다.
1
2
3
lazy var buttonA = ButtonFactory().makeButton(title: "A") { [weak self] button in
self?.checkWord(button: button)
}
그리고 VC로 돌아가서
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private func gameStart () {
dummyList = dummyGenerator.makeDummy()
makeWordLabel()
updateMan()
hangManBottomView.isHidden = false
}
func updateMan () {
if failCount >= 7 {
hangManBodyView.hangManImageView.image = UIImage(named: imageList[failCount])
let alert = alertController.makeAlertWithCompletion(title: "게임종료", message: "게임이 끝났습니다.\n다시 시작하시겠습니까?") { [weak self] _ in
self?.failCount = 0
self?.gameStart()
}
hangManBottomView.isHidden = true
self.present(alert, animated: true)
} else {
hangManBodyView.hangManImageView.image = UIImage(named: imageList[failCount])
}
}
이렇게 코드를 작성.
어차피 영단어는 소문자로 입력이 될거같아서, 소문자만 받게처리했는데, 이건 나중에 상황봐서 고치면 될듯하다.
우선은 구색만 갖춘다.
Notification을 했어도 되었을듯…. Notification도 buttonFactory의 내용을 바꾼 시점에서는 가능했을듯 하다.
다음 추가 기능때 NotificationCenter를 사용해보는걸로…
현재 다시시작을 하게되면 저 버튼이 그대로 남아있어서 저걸 돌리는 방법은 내일 찾아봐야할듯…
그리고 정답일때 표시가 되게도 해야한다.
할게많다.