본문 바로가기
아이폰 개발/Swift

[스탠포드]Swift 강좌 - MVC 패턴

by 인생여희 2021. 3. 9.

[스탠포드]Swift 강좌 - MVC 패턴

 

 

 

@정의

 

1.Model : 앱이 무엇이냐? 무엇에 대한 집합. UI 와 독립되어 있다.

 

2.Controller : 어떻게 화면에 표시할 것인지에 대해 관심을 가진다.

 

3.View : 버튼, 라벨 등 UI에 관련된 객체들. Controller 의 통제를 받는다.

 

 

@서로의 관계

 

1.모델과 컨트롤러 : 컨트롤러는 모델에 접근할 수 있다. 하지만 모델은 Notification & KVO 방식을 통해 모델의 변화를 컨트롤러에게 알릴 수 있다.

 

2.모델과 뷰 : 모델은 UI에 독립적이며 View와 소통할 수 없다.

 

3.뷰와 컨트롤러 : 컨트롤러는 View에 대해서 outlet을 이용해서 View에게 직접 접근할 수 있다.

뷰는 target - Action 구조로 사용자의 행위에 따라 필요한 함수를 호출 할 수 있다.

또한 구조적으로 미리정해진 방식으로 행위에 대한 요청 (delegate) , 데이터에 대한 요청 (data-source)을 할 수 있다.

 

 

@MVC 모델 예제 

 

 

 

Card 모델 만들기

 

Card.swift

 

1. 클래스와 구조체는 비슷하다.

2. 구조체는 상속이 없다.

3. 구조체는 값 타입이고, 클래스는 참조 타입이다.

4. 값타입은 복사되고, 참조 타입은 포인터를 보낸다.

5. 클래스는 공짜 초기화 메소드가 없다. 구조체 공짜 초기화 메소드가 있다.

6. 구조체 예) 배열, 딕셔너리

 

 

import Foundation



/*
 이모티콘이나 이미지 같은건 여기에 있으면 안된다.
 그것들은 카드를 어떻게 보여주는지와 관련된 내용이다.
                            
 */

//Card 구조체
struct Card {
    var isFaceUp = false        //앞, 뒤 여부
    var isMatched = false       // 매치 여부
    var identifier : Int        //식별자
    
    //타입 변수 : 고유 식별자 변수
    static var identifierFactory = 0
    
    
    //타입 메소드 : 고유 식별자 생성 함수
    static func getUniqueIdentifier() -> Int{
        identifierFactory += 1
        return identifierFactory
    }
    
    //초기화 + 식별자 지정해주기
    init() {
        self.identifier = Card.getUniqueIdentifier()
    }
    
    
}

 

 

Concentration.swift

 

공개 api 작성: 어떻게 모든 메소드와 변수들을 작성할지 정하는것.

 

import Foundation


class Concentration {

    //필수적인게 뭐가 있을까?
    //배열 카드가 있어야 한다.
    //var cards =  Array<Card>()
    var cards = [Card]()
 
    //앞면이 하나인 카드가 존재할 때..를위한 체크
    //뒤집혀진 카드의 숫자를 Tracking 하는 변수를 하나 설정.
    //어떤 카드도 뒤집혀 있지 않은 상태(== nil)가 있을 수 있기 때문에 옵셔널로 타입을 정합니다.
    var indexOfOneAndOnlyFaceUpCard:Int?
    
    //이 게임에서 무엇을 할 수 있을까?
    //카드를 고른다.
    //카드 뒤집기
    func chooseCard(at index : Int){
        
        //매치가 안된 카드일때
        if !cards[index].isMatched {
            
            //옵셔널의 값이 존재하고, matchIndex 값이 index와 같지 않다면
            if let matchIndex = indexOfOneAndOnlyFaceUpCard , matchIndex != index {
                
                if cards[matchIndex].identifier == cards[index].identifier {
                    
                    //매치 표시
                    cards[matchIndex].isMatched = true
                    cards[index].isMatched = true
                }
                
                //해당 카드 앞면 표시
                cards[index].isFaceUp = true
                
                //어떤 카드도 뒤집혀있지 않은 상태. 즉, 카드가 하나라도 앞면인 상태가 존재하는 상태
                indexOfOneAndOnlyFaceUpCard = nil
                
            }else{
                
                for fileDownIndex in cards.indices {
                    cards[fileDownIndex].isFaceUp = false
                }
                
                cards[index].isFaceUp = true
                indexOfOneAndOnlyFaceUpCard  = index
                
            }
        }
    }

    //초기화 - 컨트롤러에 넣어준 개수 만큼 카드 생성
    init(numberOfPairsOfCards : Int) {
        
        for _ in 1...numberOfPairsOfCards {
        
            let card = Card()
            
            //구조체는 값을 복사한다.
            //let matchingCard = card
            
            //카드를 복사해서 카드s 배열에 복사
            cards += [card, card]

        }
        
        //TODO:카드 섞기
        
    }
    
}

 

 

ViewController.swift

 

참고 

 

UIKit 라이브러리 : UI를 담당하는 부분. 버튼, 텍스트필드 등. Alerts, Core Motion, WebView, View Hierarchy,

Map Kit, Image Picker, Controls, Camera, Multi-Touch 등

UIViewController 는 UIKit에 포함되어 있다.

 

import UIKit

class ViewController: UIViewController {

    //카운트 Int
    var flipCount = 0 {
        didSet{
            flipCountLabel.text = "count:\(flipCount)"
        }
    }
    
    //카운트 라벨
    @IBOutlet weak var flipCountLabel: UILabel!
   
    //카드 버튼 배열
    @IBOutlet var cardButtons: [UIButton]!
    
    //Concentration 클래스의 모든 변수가 초기화 되었으면
    //기본 이니셜라이즈를 사용할 수 있다.
    
    //속성 이니셜라이저 변수는 self가 존재하기 전에 실행되어야 한다.
    //game 도 초기화 되어야 되고, cardButtons 도 초기화 되어야 한다.
    //한 변수가 다른 변수에 의존하고 있다.
    //lazy를 사용하면 누가 사용하기 전까지는 초기화 하지 않는다.
    //누군가 game을 사용하려할때 초기화 한다.
    //lazy가 되면 didSet은 가질 수 없다.
    //lazy 변수는 속성 관찰자를 사용할 수 없다.
    //이제 컨트롤러가 Model에게 이야기 할 수 있다!
    //버튼 개수만큼 카드 생성
    lazy var game = Concentration(numberOfPairsOfCards:(cardButtons.count + 1) / 2)


    //스위프트에는 모든 인자에는 이름이 있다.
    //외부 인자 이름, 내부 인자 이름
    @IBAction func touchCard(_ sender: UIButton) {
        print("hahah")
        flipCount += 1
        
        //버튼이 눌리면 배열얼 보고 눌러진 버튼을 찾아내면
        //어떤 카드인지 알 수 있다.
        //firstIndex 리턴 값은 옵셔널 값이다.
        let cardNumber = cardButtons.firstIndex(of: sender)
        
        //옵셔널의 값 구하기
        if let cardNum = cardNumber {
            //print("cardNum = \(cardNum)")
            //flipCard(withEmoji: emojiChoices[cardNum], on: sender)
        
            //모델이 처리 하도록 처리
            game.chooseCard(at: cardNum)
            
            self.updateViewFromModel()
            
        }else{
            print("값이 없습니다.")
        }
    }
    
    
    
    func updateViewFromModel(){
        
        for index in cardButtons.indices {
            let button = cardButtons[index]
            let card = game.cards[index]
            
            //앞면이면
            if card.isFaceUp{
                
                button.setTitle(emoji(for: card), for: .normal)
                button.backgroundColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1)
                
            //뒷면이면
            }else{
                
                button.setTitle("", for: .normal)
                button.backgroundColor = card.isMatched ?  #colorLiteral(red: 0, green: 0, blue: 0, alpha: 0) : #colorLiteral(red: 0.9411764741, green: 0.4980392158, blue: 0.3529411852, alpha: 1)
                
            }
            
        }
    }
    
    
    //이모티콘 배열
    var emojiChoices : Array<String> = ["😃","😎" , "🥸" , "😖" ,"😇" ]

    //빈 딕셔너리
    var emoji = [Int:String]()
    
    //이모티콘 전달 함수
    func emoji(for card: Card) -> String{
        
        //딕셔너리의 특정 key 값의 value가 없으면
        if emoji[card.identifier] == nil , emojiChoices.count > 0{
            let randomIndex = Int(arc4random_uniform(UInt32(emojiChoices.count)))
            
            //이모지 딕셔너리에 삭제한 배열의 값(이모지) 할당
            emoji[card.identifier] = emojiChoices.remove(at: randomIndex)
        }
        
        //딕셔너리의 key 값이 nil 이면 ? 값 리턴, 아니면 옵셔널의 값 리턴
        return emoji[card.identifier] ?? "?"
    }
}


/*
 
 모델 : 무엇에 해당하는 부분 언제카드를 뒤집어야 하는지? 등
 컨트롤러 : 어떻게.
 뷰 : 컨트롤러의 하인들..
 */

 

 

Concentration 2.zip
0.04MB

 

 

 

참고

www.edwith.org/swiftapp/lecture/26620/?isDesc=false