연습/swift개발

4.9 얼굴앱 - 모델과 컨트롤러 분리

TimeSave 2022. 1. 20. 22:09

 

여기 있는 controller에 대해 이야기해보자

 

Model이 필요한데 여기에 만들어둔 작은 Class를 생성할거야

 

FacialExpression(얼굴 표정)라는 이름의 클래스인데

 

여기로 드래그해서 여기 copy에 체크할거야

 

어떤 것을 드래그해 올 때 거의 항상 체크해야 할거야

 

copy를 원하고, link되는 것을 원하지 않는다면 집중해서 들어야 해

 

대부분 이렇게 해줄거야

 

미리만들어 둔 FacialExpression을 복사해 가져왔고

 

이제 FacialExpression 안을 살펴보자

 

여기 FacialExpression을 보면

 

이건 UI 같은 것이 아니라 Model이야

 

Foundation만 import 해왔고 UI와는 완전히 독립적이야

 

이 모델에서 얼굴에 대한 개념은 FaceView와는 달라

 

FaceView는 입의 곡률, 눈썹의 기울기 같은 것이지

 

여기에 그런건 없어

 

eyeBrows(눈썹)를 가지고 있긴하지만

 

Relaxed(편안한), Normal(일반), Furrowed(주름진) 만 있어

 

3가지 상태에 대해서만 알고 있지

 

Mouth(입)도 똑같아

 

Frown(찌푸린), Smirk(실실웃는), Neutral(평상시), Grin(활짝웃는), Smile(미소짓는)이 다야

 

입의 곡률을 가지고 있진 않지

 

그게 뭔지 몰라

 

하나 멋진 것은 여기 보이는 enum이야

 

enum안에 함수가 있는 걸 잘 봐

 

이 함수들을 실행하는 것에 대해선 이야기 하지 않을거야

 

읽기 과제를 본다면 알게 될거야

 

어쨌든 enum안에 함수가 몇 개 있어

 

FacialExpression은 enum으로 명시돼 있는 입, 눈, 눈썹인거야

 

여기 Eyes같은 enum은 상태를 가지고 있더라도

 

FaceView는 이것을 표현할 수가 없어

 

FaceView에는 Squinting(가늘게 눈을 뜬)은 없고

 

눈을 뜨고 감게 하는 법만 알뿐이야

 

그래서 중요한 것은 이 Model은 Controller에 의해서

 

View를 위해 해석되어야만 한다는 거야

 

Controller는 알아내야만 할거야

 

Model에 있는 EyeBrows에서 Relaxed가 무얼 뜻하는지 말야

 

FaceView 입장에선 눈썹의 곡률 같은 것을 의미하겠지

 

Controller는 이게 뭔지 알아내서 View에게 말해줄거야

 

이런게 MVC에서 Controller의 주요 역할이야

 

View를 위해 Model을 해석해주는 일이지

 

반대로 Model을 위해 View에 들어간 것들을 해석하기도 하지

 

그건 제스처(Gesture) 부분을 이야기할 때 보게 될거야

 

이게 Controller가 존재하는 제일 중요한 목적이야

 

그럼 Controller가 그런 일을 할 수 있도록 만들어보자

 

FacialExpression을 FaceView의 무언가로 바꿔 보는거지

 

Controller로 가서 제일 먼저 여기에 var를 만들거야

 

Model을 위한 포인터(pointer)이지

 

타입은 FacialExpression이 될거고

 

시작하려면 FacialExpression으로 설정을 할건데

 

FacialExpression은 구조체였던 것 기억하지

 

그럼 자동으로 만들어지는 초기화구조를 가져올 수 있는데

 

그건 그렇고 타입은 알아서 추론되도록 놔둘게

 

자동 생성되는 초기화구조를 가져와서

 

눈은 뜬 상태로 두고 (eyes: .Open)

 

눈썹은 그냥 일반으로 둘거고 (eyeBrows: .Normal)

 

입은 웃도록 해볼게 (mouth: .Smile)

 

이게 FacialExpression의 기본값이 될거야

 

이것이 우리 MVC의 Model이지

 

이 Model에서 재미있는건 만약 값이 바뀐다면

 

나의 View를 새롭게 업데이트 해야 하지

 

웃고 있던 입을 찡그리도록 바꾸면

 

FaceView에서 곡률 값을 바꿔야만 할거야

 

그렇게 하기 위해 전에도 썼었던 마법인 didSet을 사용할거야

 

FacialExpression이 바뀔 때마다 UI를 업데이트 하기 위해서지

 

그리고 이 FacialExpression은 값 타입이니까

 

이 변수 중 하나라도 값이 바뀌면 didSet이 불릴거야

 

이게 Class였다면 그렇지 않았겠지

 

다행히도 이건 값 타입이니까

 

이 부분이 호출될거야

 

이렇게 updateUI( ) 함수가 필요한데

 

여긴 다른 함수가 들어가도 좋아

 

난 updateUI( )를 해주는 걸 선호하긴 하는데

 

너희가 원하는 걸 해도 좋아

 

private func updateUI로 함수를 선언할건데

 

여기 안에는 FaceView를 업데이트하는 내용이 들어가야겠지

 

FaceView를 업데이트한다면 그것에 대한 포인터도 필요해

 

어떻게 view안에 포인터를 만들수 있을까?

 

control키를 누른 채 드래그 했었지

 

outlet을 만들었는데 그렇게 해보자

 

스토리보드로 가보면 여기 FaceView가 있지

 

이쪽엔 FaceViewController가 있고

 

FaceView로부터 여기로 ctrl + 드래그하고

 

faceView라는 이름의 outlet을 만들거야

 

계산기 앱에서는 display라고 했고 여기선 faceView지

 

이렇게 해주면

 

이제 이 outlet이 만들어졌어

 

이 얼굴에 대한 포인터가 생긴거야

 

이제 이 얼굴에 곡률이나 모든 것들을 설정하라고 말할 수 있어

 

이제 이것을 가르키는 포인터를 생겼고

 

updateUI( )안에서 포인터에게 말해서

 

얼굴 표정에 따라 설정해 줄 수 있어

 

그럼 어떻게 할 수 있을까?

 

첫번째로 눈부터 해볼거야

 

만약 FacialExpression의 눈이 떠 있는 상태라면

 

FaceView에 메시지를 보낼 거야

 

faceView.eyeOpen = true 라고 말이야

 

이건 간단한 편이야

 

faceView인데 모델에서 하는 방식과 매우 비슷하지

 

눈이 감겨있을 때도 마찬가지야

 

faceView.eyeOpen = false

 

하지만 가늘게 눈을 뜨는(Squinting) 경우는 어떻게 할까?

 

faceView에는 squint가 없어서 이전처럼 할 수 없지

 

그럼 일단 faceView.eyeOpen = false으로 해둘게

 

눈을 가늘게 뜬 건 눈을 감은 것에 더 가까울테니까

 

내가 할 수 있는 최선의 방법이야

 

가끔은 컨트롤러는 이렇게 할 수 밖에 없을 때도 있어

 

뷰는 모델을 표현할 때 완벽히 들어맞지 않을 수도 있지

 

대신 할 수 있는 한 최선을 다 해보는 거야

 

FaceView에게 우리의 요청을 보낼지도 모르지

 

이렇게 말하면서 말이지

 

"이봐, 우리는 FaceView에 눈을 가늘게 뜨는 항목을 넣고 싶어"

 

"왜냐하면 모델에는 그 항목이 있으니까"

 

그럼 그들은 "너희 제안을 받아들여서 고려해 볼게"라고 할거야

 

하지만 우린 지금 꼼짝 못하고 있는 상태야

 

지금은 가늘게 눈을 뜨는 상태를 눈을 감은 것으로 둘거야

 

나머지 두가지는 어떨까?

 

그걸 처리하기 위해 곡률(curvatrure)이 있는거야

 

이렇게 하면 될거야

 

faceView.mouthCurvature = expression.mouth 라고

 

하지만 expression.mouth는 웃음 또는 찡그림 같은 거였어

 

도대체 우리가 뭘 해야할까?

 

저걸 어떻게 곡률을 바꿀 수 있는걸까?

 

private 딕셔너리를 하나 만들거야

 

이름은 mouthCurvatures(입 곡률들)이라고 복수형으로 할게

 

등호('=')를 사용해서 바로 딕셔너리를 만들거야

 

대괄호 [ ]로 딕셔너리를 만들 수 있었던 것 기억하지

 

딕셔너리의 키(key)는 모델의 mouthCurvatures로 할거야

 

딕셔너리의 값(value)는 뷰에 대한 mouthCurvatures 이지

 

Double 타입이 될거고

 

예를 들어, FacialExpression.Mouth.Frown이라고 해보자

 

이게 키이고 값은 -1.0가 될거야

 

이 곡률 값에 따라 입이 그려질거야

 

무슨 말하는지 이해가 되니?

 

딕셔너리 이렇게 하나 만들었어

 

이렇게 해서 모델과 뷰 사이의 맵핑을 만든거야

 

*맵핑(mapping): 데이터간의 대응관계를 설정

 

그럼 'Grin'은 어떤 값과 대응시킬까

 

그건 그렇고 'Grin'을 적을 때 한가지 주목할게 있어

 

이미 FacialExpression.Mouth까지 접근하는게 추론됐으니까

 

매번 이 부분을 반복할 필요는 없어

 

그냥 .Grin이라고만 하면 돼

 

Grin에 해당하는 값은 0.5 로 할거야

 

활짝 웃는게 아니고 살짝 웃는 표정이니까

 

활짝 웃는건(.Smile) 1.0 가 되겠지

 

.Smirk(히죽거리며 웃음)는 찡그림(Frown)이 약간 들어가지만

 

많이 찡그리게 하진 않고 -0.5 정도로 둘게

 

마지막으로 Neutral(무표정) 입은 0.0 이 될거야

 

이렇게해서 mouthCurvatures 테이블을 만들었어

 

아래로 내려와서

 

mouthCurvature = mouthCuravtures[expression.mouth]

 

라고 해줄게

 

여기 에러가 하나 생기는데 무슨 문제인지 아는 사람?

 

이 부분은 딕셔너리를 검색(lookup)하는 부분이야

 

딕셔너리 look up이 뭘까?

 

어떤 타입을 반환할까?

 

옵셔널이야

 

근데 이건 Double이지 옵셔널이 아니지

 

그럼 이걸 어떻게 처리해야 할까?

 

만약에 FacialExpression이 추후에 업그레이드가 되었는데

 

찾고자 하는 expression이 없다면?

 

그럴 땐 기본값을 0을로 둘거야

 

이렇게 기본값을 설정하는 것 기억하니?

 

Double 타입의 어떤 값을 가져오려고 시도했는데

 

nil을 반환하면 nil 대신 이 기본값을 반환하는거야

 

이해되니?

 

여기 var가 빠졌네

 

지금까지 한 것 모두 알겠니?

 

계산기 앱이랑 비슷하니까 도움이 되길 바라

 

딕셔너리를 검색해서 값을 가져오는 부분 말이지

 

눈썹 기울임(eyeBrowTilt)에도 같은 방식을 쓸거야

 

eyeBrowTilt = eyeBrowTilts

 

eyeBrowTilts는 딕셔너리가 되겠지

 

[ ]안에는 expression.eyeBrows를 넣을거야

 

여기도 기본값으로 0을 넣어줄거야

 

찾지 못했을 때를 위해서지

 

여기에 private 변수를 만들고

 

eyeBrowTilts라고 부르자

 

이건 FacialExpression.Eyebrows.Relaxed가 될거야

 

Relaxed의 값은 CGFloat(0.5)이 될거야

 

이건 릴랙스한 눈썹모양이지

 

이런, CGFloat이 아니고 0.5이지

 

이건 Double 타입이야

 

eyeBrowTilts의 타입은 Double이니까

 

저기엔 CGFloat이 아니지

 

.Furrowed(찡그린)는 -0.5 만큼 기울일거야

 

.Normal(일반)은 0.0 로 만들거야

 

이해되지?

 

이렇게 해서 눈썹도 입처럼 딕셔너리 검색이 가능해졌어

 

이제 모델을 뷰와 대응시켰어

 

뷰를 위해 모델을 해석해주었지

 

일단 실행해보고 어떻게 보이나 확인해보자

 

흠...별로 좋아보이지 않는군

 

왜냐하면 모델을 확인해보면

 

눈은 뜬 상태가 되어야하는데 그렇게 보이고 있지

 

눈썹은 Normal인데 여기선 Normal이 아니지

 

찡그린 상태야

 

입은 웃고 있어야 하는데 그게 아니라 찌푸리고 있군

 

여기 보이는 모든 것들은 모델에서 설정한 UI가 아니라

 

여전히 스토리보드에 설정한 상태로 보이고 있어

 

그럼 왜 여기서 updateUI( ) 메소드가 불리지 않는걸까?

 

우리가 FacialExpression을 초기화할 때 말이지

 

그건 초기화 중에 값을 설정하면 didSet이 불리지 않기 때문이야

 

설정한 이후에만 불리지

 

왜 그럴까?

 

Swift에서 어떤 것이 초기화가 되고 있을 때

 

그걸로 무엇이든 하기 전에 완전히 초기화 되어 있어야 해

 

물론 여기서도 이걸로 뭔가를 하기 전에 초기화되어야 하는거지

 

이 초기화는 이뤄졌지만 이 updateUI( )는 불리지 않았어

 

FaceViewController를 초기화하는 단계서 발생했기 때문이야

 

이 expression을 나중에 설정했으면 updateUI( )가 불리고

 

UI가 바뀌었겠지

 

그럼 어떻게 처리해야할까?

 

어쨌든 해야만 했던 일이긴 하지

 

시스템에 의해 faceView outlet이 설정되었을 때

 

여기에서 didSet를 하고 updateUI를 호출할거야

 

이 didSet이 불리는 건

 

MVC가 생성된 직후에 iOS가 나타나고

 

이 outlet이 연결될 때 불릴거야

 

outlet에 연결되어서 실질적으로 faceView를 가리킬 때야

 

그래서 그렇게 되자마자 faceView를 지배할 수 있게 되고

 

그 땐 업데이트 할 수 있는거야

 

모델이 바뀔 때와 처음 뷰와 연결이 될 때 모두 업데이트 될거야

 

보통 이렇게 짧을 때는 한 줄로 정리해

 

여기도 그렇게 할 수 있지만

 

우리가 하고 있는 걸 강조하기 위해 이건 그냥 이렇게 둘거야

 

저렇게 넣는 방식이 흔하긴 해

 

이제 실행해보자

 

iOS가 나타나고 FaceView가 연결될거야

 

짠! 모델 설정대로 얼굴이 업데이트 됐네

 

여기에 있는 모델을 바꿔보면, 눈썹은 편안하게 하고

 

입은 히죽거리게 하고 눈은 감도록 만들어보자

 

모델에 있는 얼굴 표정을 바꾸었으니

 

뷰는 모델을 반영해야 할거야

 

이렇게 말이지

 

모두들 모델로 여기까지 만들어 본 것 이해하니?

 

 

'연습 > swift개발' 카테고리의 다른 글

4.11 얼굴에 제스처를 적용해보자  (0) 2022.01.20
4.10 제스처는 어떻게 만들까  (0) 2022.01.20
4.8 다양한 얼굴 표정  (0) 2022.01.20
4.7 얼굴앱 - 눈 넣기  (0) 2022.01.20
4.6 얼굴앱 - 스토리보드 연결  (0) 2022.01.20