[Quiz 앱 2] MVC Design Pattern

Lena·2022년 1월 3일
1

iOS앱개발 입문기

목록 보기
13/17

채점 결과 알려주기

색상으로 알려주기

IBAction 함수에서 실행문 코드를 변경해준다.

        // 사용자가 입력한 답 채점
        if userAnswer == actualAnswer{
            sender.backgroundColor=UIColor.green
        } else {
            sender.backgroundColor=UIColor.red
        }

채점결과를 색상으로 알려주지만 문제는 다음 문제에서도 이전 버튼 채점결과 색상이 그대로 남아있다.
이는 updateUI() 함수에서 수정할 수 있다.

    func updateUI() { // 문제번호를 업데이트 하는 함수
        questionLabel.text = quiz[questionNumber].text
        
        trueButton.backgroundColor = UIColor.clear
        falseButton.backgroundColor = UIColor.clear   
    }

위와 같이 수정하고 실행시키면, 아예 채점 결과 색상이 변하지 않는 문제를 확인할 수 있다.

    @IBAction func answerButtonPressed(_ sender: UIButton) {
        
        let userAnswer = sender.currentTitle  // 사용자의 답에 currentTitle의 sender 값을 넣는다 (True, False)
        let actualAnswer =  quiz[questionNumber].answer
        
        if userAnswer == actualAnswer{
            sender.backgroundColor=UIColor.green
        } else {
            sender.backgroundColor=UIColor.red
        }

        if questionNumber + 1 < quiz.count {
            questionNumber += 1
            
        } else {
            questionNumber = 0
        }
        updateUI()
    }

색상이 변한 뒤 바로 updateUI() 함수가 나오면서 바로 clear 시키기 때문에 사용자에게는 보이지 않는 것. 채점 결과를 보여주는 변경된 버튼에 대해 일정 시간 지연시켜주는 timer를 통해서 이를 해결해보자.

    timer = Timer.scheduledTimer(timeInterval: 2.0, target:self, selector: #selector(updateUI), userInfo: nil, repeats: false)
    

그러나 여전히 해결되지 않았다.

  • 원인 1 : EggTimer에서 썼던 함수를 가져오면서 Timer 함수를 timer 라는 변수에 넣어주는 코드를 썼기 때문. 수정 이후에도 문제 해결 되지않았다.
  • 원인 2 : Timer()함수 내부에 repeatsfalse로 설정했기 때문에 반복되지 않고 꺼지는데, 이후에 updateUI() 를 다시 호출함으로써 clear 해버렸기 때문.

다음과 같이 코드를 수정하고 정상적으로 작동하게 되었다.

   @IBAction func answerButtonPressed(_ sender: UIButton) {

        let userAnswer = sender.currentTitle  // 사용자의 답에 currentTitle의 sender 값을 넣는다 (True, False)
        let actualAnswer =  quiz[questionNumber].answer
        
        
        // 사용자가 입력한 답 채점
        if userAnswer == actualAnswer{
            sender.backgroundColor = UIColor.green
        } else {
            sender.backgroundColor = UIColor.red
        }
        
        // 문제번호를 하나씩 늘려간다
        if questionNumber + 1 < quiz.count {
            questionNumber += 1
            
        } else {
            questionNumber = 0
        }
        
        Timer.scheduledTimer(timeInterval: 0.2, target:self, selector: #selector(updateUI), userInfo: nil, repeats: false)

    }

Progress Bar

progressBar.progress = Float(questionNumber + 1) / Float(quiz.count)
  • progressBar의 property인 progress를 설정해준다.
  • quiz의 갯수를 세어서 이를 questionNumber로 나누어주는데 각각 Float로 형변환 해준 이유는 EggTimer 에서와 동일하다. (Float로 형변환을 먼저 하여야 정확한 값을 얻을 수 있기 때문)
  • questionNumber에 더하기 1을 해준 이유는 progressBar의 진행을 끝까지 채워주기 위해 해 준 것 (이 역시 EggTimer와 동일하다)

MVC Pattern

퀴즈앱에서 문제가 되는 부분들이 코드의 앞부분을 차지하면서 가독성이 많이 떨어지게 되었다. 만약 문제가 방대해 진다면 점점 컨트롤하기가 어려워질 것이다. 이를 구조화하기 위한 MVC 패턴에 대해 알아보자.

  • MVC Design Pattern
    MVC 패턴은 여러 디자인 패턴 중 하나로, Model-View-Controller 세 개의 핵심구조를 이용해 애플리케이션을 설계하는 것이다.
    • Model : 데이터를 담당
    • View : 데이터에 대한 화면 표현을 담당
    • Controller : 모델과 뷰 사이에 위치해 데이터를 가공해 뷰로 전달하며, 뷰에서 발생하는 이벤트를 입력받아 처리하는 역할을 한다.

MVC패턴은 프로그램을 특성에 따라 서로 영향을 미치지 않을 수 있는 범위로 분리해놓았기 때문에 데이터 관리 부분을 수정해도 비즈니스 로직이나 화면 표현 코드에 영향 미치지 않으며 화면을 표현하는 코드 수정하더라도 비즈니스 로직이나 데이터 관리 부분에 영향 미치지 않을 수 있다. 코드 재사용에 매우 유용하다.

이제 이를 퀴즈앱에 적용해보자.

  • 질문들의 모음은 (데이터들) Model로, Main.storyboard는 View로, ViewController는 Controller로 그룹화해준다.
  • Model 에서 질문 (데이터)를 담은 구조체 만들기.
import Foundation

struct QuizBrain {
    let quiz = [
       Question(q: "A slug's blood is green.", a: "True"),
       Question(q: "Approximately one quarter of human bones are in the feet.", a: "True"),
// ~~ blah blah ~~ many questions in here
    ]
    
     var questionNumber = 0
}
  • 다시 ViewController로 가보면 코드가 오류 투성이다. 이를 해결하기 위해 다음과 같이 코드를 수정하였다.

QuizBrain (Model)


import Foundation

struct QuizBrain {
    let quiz = [
       Question(q: "A slug's blood is green.", a: "True"),
       Question(q: "Approximately one quarter of human bones are in the feet.", a: "True"),
       // blah blah ~~ Questions in here
    ]
    
    var questionNumber = 0 // 문제번호 초기화
    
    // 모든 기능을 함수로 구현하였다.
    
    func checkAnswer(_ userAnswer: String) -> Bool {
        // 사용자가 정답을 입력하면 채점하는 함수
        if userAnswer == quiz[questionNumber].answer {
            return true
        } else {
            return false
        }
    }
    func getQuestionText() ->String {
        // 문제를 출력하는 함수
        return quiz[questionNumber].text
    }
    
    func getProgress() -> Float {
        // Progress Bar 설정 함수
        let progress = Float(questionNumber) / Float(quiz.count)
        return progress
        
    }
    
    mutating func nextQuestion(){
        // 다음 문제를 출력하는 함수

        if questionNumber + 1 < quiz.count {
            questionNumber += 1
            
        } else {
            questionNumber = 0
        }
    }
}

ViewController (View)

// QuizBrain에서 정의했던 함수들을  호출해서 사용하고 있다 
import UIKit

class ViewController: UIViewController {
    
    @IBOutlet weak var questionLabel: UILabel!
    @IBOutlet weak var progressBar: UIProgressView!
    @IBOutlet weak var trueButton: UIButton!
    @IBOutlet weak var falseButton: UIButton!
    
    var quizBrain = QuizBrain()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        

        updateUI() // updateUI()함수 호출
    }

    
    @IBAction func answerButtonPressed(_ sender: UIButton) {

        let userAnswer = sender.currentTitle!  // 사용자의 답에 currentTitle의 sender 값을 넣는다 (True, False)
        let userGotItRight = quizBrain.checkAnswer(userAnswer) // Model - QuizBrain에서 underscore로 처리해주었으므로
        
        
        // 사용자가 입력한 답 채점
        if userGotItRight {
            sender.backgroundColor = UIColor.green
        } else {
            sender.backgroundColor = UIColor.red
        }
        
        quizBrain.nextQuestion() 
        
        Timer.scheduledTimer(timeInterval: 0.2, target:self, selector: #selector(updateUI), userInfo: nil, repeats: false)

    }
    
    @objc func updateUI() { // 문제번호를 업데이트 하는 함수
        questionLabel.text = quizBrain.getQuestionText()
        // 문제를 출력할거니까! questionNumber array의 첫번재 요소를 문제로 쓸 것
        
        progressBar.progress = quizBrain.getProgress()
        
        trueButton.backgroundColor = UIColor.clear
        falseButton.backgroundColor = UIColor.clear
    }
}

score 출력하기

  • score Label을 만들고 IBOutlet 연결해준다.
  • Model에서 채점 결과 맞는 경우 score값을 1씩 증가시킨다.
var score = 0 // 점수 초기화

mutating func checkAnswer(_ userAnswer: String) -> Bool {
    // 사용자가 정답을 입력하면 채점하는 함수
    if userAnswer == quiz[questionNumber].answer {
        score += 1
        return true
    } else {
        return false
    }
}

mutating func getScore() -> Int {
    return score
}
  • 이 함수를 View에서 다음과 같이 받아온다.
 @objc func updateUI() { // 문제번호를 업데이트 하는 함수
     questionLabel.text = quizBrain.getQuestionText()

     progressBar.progress = quizBrain.getProgress()
     scoreLabel.text = "Score: \(quizBrain.getScore())"
     // scoreLabel의 property 인 text 값에다가 getScore() 함수를 불러온다 
     
     trueButton.backgroundColor = UIColor.clear
     falseButton.backgroundColor = UIColor.clear
    }

결과물


0개의 댓글