연습/swift개발

4.7 얼굴앱 - 눈 넣기

TimeSave 2022. 1. 20. 22:08

 

FaceView로가서 좀 더 추가해보자

 

눈을 추가해볼까

 

얼굴에 눈을 추가해 볼꺼야

 

여기에 작은 헬퍼 함수를 만드는 걸로 시작해보겠어

 

눈과 입 등 모두를 두개골(skull)의 사이즈와 연관되게 만들거야

 

두개골의 사이즈가 어떻든간에

 

안에 들어갈 모든 것들을 두개골에 연관되도록 할거란 말이지

 

그럼 skullCenter와 skullRadius를 drawRect밖으로 꺼내서

 

이것들을 변수(var)로 만들거야

 

이렇게 하게 되면, 숙제에도 있을건데

 

이런 에러가 뜰거야

 

인스턴스 멤버인 ‘bounds’는

 

'FaceView’ 타입에서 사용할 수 없다 라고 말하고 있지

 

너흰 매우 혼란스러울 거야

 

왜냐하면 bounds는 명확히 FaceView의 인스턴스 멤버인데

 

어떻게 여기서 bounds를 사용할 수 없다는걸까?

 

그에 대한 대답은 여기있어

 

왜냐하면 bounds가 초기화 상태 안에 있기때문이야

 

지금은 이것을 초기화하는 중이지

 

초기화하는 동안 클래스를 사용할 수 없어

 

아직 초기화가 되지 않았잖아

 

그래서 bounds 같은 변수나 메소드를 부를 수없어

 

모두들 이해되니?

 

이 메시지를 보게 될거라고 확신해

 

학교 게시판에 이걸 올려놓고 질문할지도 모르지

 

그래서 내가 이게 왜 그런건지 말해주는 거야

 

초기화하는 중에는 할 수 없어

 

초기화를 완전히 완료할 때까지는 프로퍼티에 접근할 수 없어

 

그럼 여기선 무엇을 할 수 있을까?

 

연산이 되도록 계산 프로퍼티(computed property)로 바꿀꺼야

 

이 값을 반환하도록 바꿀거고

 

계산 프로퍼티에는 두가지가 있었지

 

get { }으로 감싸지 않는 것을 주목해서 봐

 

값을 가져오기만 하는 계산 프로퍼티가 있다면

 

여기에 get { } 을 쓸 필요가 없어

 

우린 그렇게 하지 않을거야

 

get { }을 넣지 않으니까 더 깔끔해보이지

 

여기도 마찬가지야

 

이건 CGPoint이지

 

이것을 반환할거고

 

get { }을 넣지 않을거야

 

이해가 돼니?

 

내가 하고 싶은 또 하나는

 

현재 우리 두개골이 뷰의 가장자리를 벗어나고 있어

 

두개골의 사이즈를 조정할 수 있게 만들어서

 

가장자리보다는 작게 하고 싶어

 

scale이라는 이름의 public var(변수)를 추가할 거야

 

CGFloat이 되겠지

 

이건 두개골의 scale(축소확대비율)이 될거야

 

두개골의 반경(반지름)을 계산하는 부분에 저 scale을 곱할거야

 

scale값은 90%(0.90)으로 줄거야

 

이렇게 하면 항상 두개골이 90%의 사이즈를 유지하겠지

 

이제 이 변수 두개를 사용할건데

 

계산 변수들이지

 

눈이나 입 등 다른 것들을 계산할 때도 똑같은 것들이 사용될거야

 

오늘은 눈을 한 다음에 입 부분을 할건데

 

다음 수업의 시작으로 입을 하겠지만

 

어쨌든 수업 이후에 게시판에 모든 코드를 올릴거야

 

아마 입에 대한 코드를 올릴거니까 한번 봐 보렴

 

하지만 이 눈을 먼저 해보자

 

어떻게 눈을 만들어야 할까?

 

눈을 만들기위해선 두개골 반경과 눈 크기 사이의 비율이 필요해

 

이것들을 빨리 만들기 위해 아주 빠르게 코드를 옮겨 왔어

 

여기 모든 종류의 비율이 있어

 

skullRadiusToEyeOffset (두개골반경에서 눈 사이 비율)이나

 

눈 반경, 입 너비, 입 높이 등등

 

스위프트에서 상수를 어떻게 쓰는지를 잘봐봐

 

구조체를 만들었고

 

그리고 타입이 되는 변수들을 만들었지 static으로

 

let을 썼어

 

이것처럼 타입을 적고 값을 할당해주었지

 

그래서 이게 구조체를 선언하는 방식이야

 

이것들은 대문자로 시작을 하는 점에 주목해

 

당연히 타입의 이름도 항상 대문자로 시작하니까 잊지마

 

어떤 사람들은 이렇게 하기 싫겠지만 이렇게 해주도록 해

 

스위프트에서는 모든 타입의 이름 첫글자를 대문자로 해야 해

 

그리고 이것들도 항상 대문자로 시작해야 해

 

여기 있는 static들은 구조체에서 기본적으로 상수여야 해

 

Ratio. 다음에 이것으로 접근하게 될거야

 

이건 타입의 이름이고

 

이건 그 안에 들어 있는 값이니까

 

이것들을 사용해 볼 때 다시 보여줄거야

 

그럼 이제 어떻게 해야할까?

 

어떻게 눈을 만들어야 할까?

 

여기에 새로운 메소드를 생성할거야

 

우선 enum 타입을 만들어보자

 

타입 이름은 Eye라고 하고 이 안에 왼쪽 눈과 오른쪽 눈이 있어

 

API안에 눈에 대해 설명할 수 있는 enum이 되겠지

 

그 다음 pathForCircleCenteredAtPoint라는 함수를 만들거야

 

CGPoint인 midPoint와

 

CGFloat인 withRadius을 인자로 받을거야

 

그리고 UIBezierPath를 반환할거야

 

이건 유틸리티 함수가 될거야

 

private으로 만들어졌는지 확인하고

 

이것 또한 private이고

 

사실 이 두개도 private이야

 

여기에 private를 넣는 습관이 있어

 

범위를 적절하게 만들기위해서지

 

이건 적절한 public야

 

이 함수는 centePoint와 radius를 받아서

 

우리에게 BezierPath를 줄거야

 

여기에서 했던 것과 완전히 똑같지

 

이걸 그냥 복사해서

 

복사가 아니고 잘라내서 여기에 넣을거야

 

자, 여기 있지

 

이렇게 되면 center는 더 이상 skullCenter가 아니라

 

midPoint가 되겠지

 

radius도 skullRadius가 아니라 withRadius가 되겠지

 

한가지 이상한 점을 확인해볼건데

 

함수를 호출할 때는 깔끔하게 읽히지

 

왜냐하면 여기 아래에서 함수를 호출한다고 해보면

 

pathForCircleCenteredAtPoint 호출하고

 

center에는 skull이, withRadius에는 skullRadius를 넣으면

 

영어같아서 훨씬 읽기 좋지

 

여기에 의미를 이해하기 쉬운 전치사도 있고 말야

 

하지만 코드 안으로 들어왔을 때

 

withRadius라고 불리는 건 조금 이상해

 

잘 이해되지 않아

 

이안에서는 그냥 radius라고 불리길 원해

 

이럴 때 전에 이야기했었던 내부이름과 외부이름을 쓰면 돼

 

보이지?

 

우리가 할 건 이게 전부야

 

이것을 직접 반환하지는 않을거고

 

path라는 상수를 하나 만들어서 여기에 할당해주고

 

path를 lineWidth(선굵기)를 설정하는데에도 사용할거야

 

아래에 해놓은 걸 잘라내서 붙여 넣자

 

모든 lineWidth가 같아지게 될거야

 

사실 이 lineWidth도 scale해서 했던 것처럼

 

바깥으로 빼 둘수도 있겠지만

 

일단 빠르게 진행하기위해 이렇게 계속 할게

 

그 다음에 그 path를 반환하겠지

 

이해되지?

 

그건 그렇고 이렇게 코드가 너무 길어지면

 

보기 좋게 하는 한가지 방법이 있어

 

각 인자를 한 줄에 두는 거야

 

코드가 정말 길 때 이렇게 하면 더 읽기 쉽지

 

그래서 이렇게 skull을 다시 만들었고

 

그건 그렇고 이 skull을 사용하지 않고 있어

 

stroke( )를 하는 것을 제외하고는 말야

 

그럼 이 부분을 선택해서

 

잘라내고 여기에 놓자

 

모두들 무엇을 하는지 보이지?

 

체이닝(chaining)하는 방식으로 호출한거야

 

stroke( )는 여기서 불리지

 

skull 변수를 따로 만들지 않고 이렇게 해도 꽤 명확하지

 

skullCenter와 skullRadius를 가져올테니까

 

이제 pathForCircleCenteredAtPoint 함수가 있으니까

 

이걸 내 눈을 그리는 데에도 사용할 수 있겠지

 

그래서 여기에 또 다른 private func을 추가할거야

 

getEy..어떻게 불렀더라

 

pathForEye라고 부를거야

 

Eye타입인 eye를 인자로 받을거고 UIBezierPath를 반환할거야

 

이건 왼쪽 눈 또는 오른쪽 눈을 가져올거야

 

여기에 무슨 인자가 오는 지에 따라 그렇게 되겠지

 

그러기 위해서는 eye radius(눈의 반경)가 필요해

 

eyeRadius를 만들어야겠지

 

skullRadius를 위에 있는 Ratio 중 하나로 나눈 값을 넣을거야

 

Ratio는 여기에 있지

 

Ratios.SkullRadiusToEyeRadius를 쓸거야

 

let eyeCenter 도 만들건데

 

사실 여기선 eye 받는 getEyeCenter 함수를 부를거야

 

그럼 이 함수를 만들어보자

 

private func getEyeCenter에 인자는 eye를 받을거고

 

center가 될 CGPoint를 반환할거야

 

eyeCenter는 어떻게 여기에 가져올까?

 

꽤 직관적으로 할 수 있어

 

여기에 작성을 할 건데..뭘 여기서 할려고 했었더라..

 

let eyeOffset를 만들고 skullRadius를

 

Ratios.skullRadiusToEyeOffset로 나눈 값을 넣을거야

 

얼굴에 들어갈 모든 것들의 위치를

 

skullRadius와 skullCenter를 기준으로 하고 있는게 보이지?

 

let eyeCenter 에는 skullCenter를 넣어줄거야

 

우선 눈이 얼굴의 정가운데에 오도록 시작할거야

 

그리고 나서 위로 올리든지 해볼게

 

우선 위로 올리는 건

 

eyeCenter.y -= 라고 할건데

 

y축에서 y - 는 올라가고 + 내려가기 때문이지

 

그 다음엔 eyeOffset이 올거야

 

그럼 eyeOffset 값만큼 위로 올라가겠지

 

어떤 눈인지에 따라서

 

만약 왼쪽 눈이면 eyeOffset.x를 왼쪽으로 움직일거야

 

(*잠시후 코드 정정합니다)

 

(*코드 철자 오류 수정중)

 

이것도 eyeCenter가 되야지

 

eyeCenter를 eyeOffset만큼 왼쪽으로 움직일거야

 

만약 오른쪽 눈이라면

 

눈을 오른쪽으로 움직여야겠지 eyeOffset 만큼

 

이렇게 하면 우리의 눈이 어디에 있어야할지 정해줄거야

 

여기에선 eyeCenter를 반환할거야

 

모두 이해되지?

 

어디에 눈이 있는지?

 

에러가 뜨네 뭐지?

 

let이 아니라 var가 되어야 하네

 

아마 var로 되어야 할거야

 

이건 let이 될거고

 

let과 var를 다시 구분해줬어

 

getEyeCenter 함수를 이용해서 눈의 center를 가져올거야

 

눈을 위한 Radius와 Center도 가지고 있지

 

이제 반환할 수 있겠지

 

pathForCircleCenteredAtPoint에

 

eyeCenter를 넣고 withRadius에는 eyeRadius를 넣는 걸로

 

이제 pathForEye가 만들어졌고

 

이 path를 stroke(그리는 절차)가 필요해

 

pathForEye에 왼쪽 눈(.Left)을 넣고

 

stroke( ) 할거야

 

pathForEye(.Right).stroke( ) 오른쪽 눈도 똑같이 해줘야겠지

 

.Left라고 할 수 있는 것에 주목해

 

굳이 Eye.Left라고 할 필요가 없어

 

스위프트가 여기 들어갈 타입을 Eye라고 추론할거니까

 

모두들 이해되지?

 

바라건데 그렸을 때 얼굴에 눈이 나타나면 좋겠군

 

엇! 제대로 작동하지 않네

 

무슨 일이 생긴건지 자세히 들여다보자

 

왼쪽 눈은 괜찮아 보이는데

 

오른쪽 눈은 가운데 지점에 갇혀 있는 것처럼 보이네

 

왜 그럴까?

 

오른쪽 눈으로 이동해서 살펴 보자

 

오른쪽 눈을 eyeCenter의 'y'를 기준으로 옮겼나보군

 

여기 위에 선언한 그대로 말이야

 

이건 x가 되어야 해

 

다시 동작시켜 보자

 

빙고! 얼굴을 생겼고 돌릴 수도 있어

 

좋아 보여!

 

이제 입이 필요해

 

입을 만들 시간이... 음 시간이 될 것 같네

 

어쨌든 입부분을 만들어볼게

 

여기에 똑같은 것을 만들거야

 

private func에 함수 이름이 이번엔 pathForMouth가 되겠지

 

입은 오직 하나뿐이지

 

그리고 UIBezierPath를 반환할거야

 

여기 아래에 똑같은 것을 할거야

 

pathFroMouth( ).stroke( ) 라고 말이지

 

그럼 어떻게 입을 만들까?

 

입을 만들려면 Bezier curve를 사용해야 해

 

몇명이나 Bezier curve에 대해 알고있니?

 

손 들어볼래? 거의 아무도 모르는 군

 

Bezier curve는 두 포인트 사이의 그린 선이야

 

하지만 어딘가에 두개의 컨트롤 포인트가 있지

 

컨트롤 포인트에 접선을 그리려는 곳에 말이야

 

그리고 거기서부터 curve가 시작되지

 

그리고나서 다른 컨트롤 포인트에 접선을 그리고

 

거기에 있는 다른 한 곳에 그리기를 시도하지

 

그래서 curve를 그릴 수 있어

 

이런 작은 컨트롤 포인트를 이용해서 말이지

 

그래서 시작과 끝 그리고 두개의 컨트롤 포인트를 정해야 해

 

첫번째로 입을 넣을 직사각형을 만들거야

 

입을 그릴 비율이 필요해

 

mouthWidth, mouthHeight, mouthOffset

 

skull의 반경 비율로 만들거야

 

이렇게 해서 시간을 약간 줄일 수 있어

 

mouthRect를 만들거야

 

입을 담을 직사각형이 되는 것이지

 

let mouthRect = CGRect 라고 할거야

 

이 CGRect는

 

여기에 있는 x, y, width, height 인자로 된

 

이니셜라이저를 사용할거야

 

x는 skullCenter.x - mouthWidth/2이고

 

그래서 내 입은 skullCenter에서 mouthWidth를 뺀 게 되겠지

 

다음 인자로 가서

 

y도 비슷한데

 

skullCenter.y + mouthOffset이 될거야

 

왜냐하면 입은 얼굴 가운데의 아래쪽에 놓을거니깐

 

그래서 아래 쪽에 두는거야

 

width는 mouthWidth, height은 mouthHeight이지

 

이렇게 하면 직사각형이 어떻게 보이는지 보여줄려고 해

 

UIBezierPath에는 직사각형을 인자로 받는 컨스트럭터가 있어

 

mouthRect 값을 쓰면 되겠지

 

실행해보자

 

이런, 반환을 해줘야지

 

이게 우리가 사용할 path야

 

우선 직사각형을 만들었고 컨트롤 포인트는 아직 만들지 않았어

 

직사각형이 만들어졌지

 

여기가 입을 만들 곳이야

 

웃는 입이면 아래로 내려갈거고

 

찡그리면 위로 올라오겠지

 

그래서 여기에 놓을거야

 

여기는 Bezier path의 start point가 될거야

 

여기는 end point가 되겠지

 

스마일한 입모양을 만들기위해 여기 컨트롤 포인트 하나 만들고

 

여기에도 컨트롤 포인트 하나를 만들거야

 

그럼 이 컨트롤 포인트를 향해 그려지기 시작할거야

 

바닥을 찍고 여기 사이의 접선을 향해 올라가기 시작할거야

 

스마일을 만들기 위한 계획이 이거야

 

Bezier path에 대해 모른다고 걱정하지마

 

시간을 아끼기위해 여기에 미리 작성된 코드를 입력할거야

 

가장 중요한 것은 mouthCurvature(굽은 비율)이야

 

Double타입으로 되어 있는데

 

완전 찡그릴 때는 -1이고 함박 웃음 때는 1이 되는 사이의 값이야

 

smileOffset에는 우선 -1과 1사이 값인지 확실히 하고

 

mouthCurvature에서 그렇게 하기로 했었지

 

smileOffset은 -1에서 1사이의 값에

 

mouthRect의 height를 곱해줄거야

 

이래서 스마일 정도가 1일때

 

항상 아래쪽에 컨트롤 포인트를 놓았던거야

 

이해되지?

 

여기 입의 왼쪽 위에서 시작하고

 

오른쪽 위에서 끝낼 거야

 

첫번째 컨트롤 포인트는 mouthRect 밑변의 3/1 지점이고

 

두번째 컨트롤 포인트는 다른 쪽으로부터 3/1 지점에 놓일거야

 

y 값은 mouthRect밑변에서 smileOffset을 더한만큼이 될거고

 

완전히 웃을 때 이게 직사각형의 아래쪽을 따라가거나

 

반대로 직사각형 위쪽으로 올라갈 수도 있겠지

 

이제 모든 것들을 포함한 UIBezierPath를 반환할거야

 

이렇게 하면 되는데 let UIBezierPath

 

미안, let path = UIBezierPath( ) 하나를 만들고

 

path가 움직이게 만들거야

 

시작점으로 가는 부분엔 start를 이렇게 넣고

 

start는 여기서 만들어 주었지

 

그리고 Bezier curve를 만들거야

 

addCurveToPoint 라고 하면 되겠지

 

path에 Bezier curve를 추가하는 방법이지

 

end point에는 end가 들어가지

 

end는 여기서 만든거고

 

그리고 여기엔 cp1과 cp2 두개의 컨트롤 포인트를 넣어줄거야

 

그건 여기 이 녀석들이야

 

좋아, 이제 끝났어

 

lineWidth는 5.0로 설정할거고

 

path를 반환할거야

 

제대로 작동하는지 볼까?

 

입을 0으로 설정해서 웃는지 찡그리는지 보자

 

지금은 직선이 나와야 해

 

왜냐하면 컨트롤 포인트들은 라인을 따라서 될 거니까

 

제대로 됐네, 행복한 표정은 아니야

 

이번엔 매우 슬프게 만들어보자

 

오 정말 슬퍼보이네 :(

 

하지만 슬픈 얼굴을 보여주고 싶진 않아

 

활짝 웃게 해보자

 

웃는 얼굴이 여기 있네, 아주 행복해보여

 

이제 우리가 끝내기 전에 한가지 할게 남았어

 

private 메소드안에서 mouthCurvature은 현재 지역변수야

 

public으로 만들거야

 

이렇게 밖으로 가져와서

 

앞에는 public var를 놓을거야

 

scale 아래 여기에 놓자

 

var로 만들고 public이지

 

이제 다른 사람들이 FaceView를 사용해서

 

얼마나 행복하게(웃게) 만들지 설정할 수 있게 되었어

 

public으로 만들었으니까

 

그래서 웃는지 찡그릴지 설정할 수 있지

 

다음주에 이런 설정들이 매우 의미있게 될거야

 

여러 개의 MVC를 만들기 시작하면

 

무언가를 보여주기위해 다른 MVC들을 사용할 수 있길 바랄거야

 

계속 잘 작동하는지 볼까

 

mouth curvature(곡률)은 여전히 웃고 있는지 말야

 

여기 활짝 웃는 얼굴이 있네

 

좋아, 다음주에 이어서 해볼게

 

 

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

4.9 얼굴앱 - 모델과 컨트롤러 분리  (0) 2022.01.20
4.8 다양한 얼굴 표정  (0) 2022.01.20
4.6 얼굴앱 - 스토리보드 연결  (0) 2022.01.20
4.5 얼굴앱-얼굴형  (0) 2022.01.20
4.4 텍스트를 넣으려면  (0) 2022.01.20