연습/swift개발

4.1 화면의 기본, 뷰를 알아보자

TimeSave 2022. 1. 20. 21:51

 

뷰에 대해서 이야기 해보자 말했듯이, 뷰는 사각형 영역이야

뷰는 좌표 공간으로 정의해

드로잉이나 입력 처리 등을 할 때는 정의된 좌표공간이 필요해

그리고 그 공간은 그리기나 터치이벤트를 처리하는데 사용되지

 

이러한 종류의 뷰는 위계구조를 가지고 있어서

네가 상상을 해본다면

이게 하나의 MVC를 위한 전체 UI를 포함한 뷰라고 할 수 있지

그리고 버튼같은 하위뷰들이 이 뷰 안에 들어가 있지

예를 들어 StackView 같은 것 말야

그리고 그 뷰들이 또 다른 뷰에도 들어가 있을 수 있어

따라서 이것이 왜 계층구조인지 알 수 있어

 

뷰를 계층 혹은 뷰 계층이라고 부르는 것에 쌓고 있는 거야

당연하게도 이 뷰들은 서로 겹쳐질 수도 있어

그리고 또 만약 뷰안에 또 뷰가 있을지라도

여기 이 뷰를 예시로 들자면

만약 이 뷰가 속에 다른 뷰를 가지고 있다면

속에 있는 뷰가 부모 뷰 경계 밖으로 나올 수도 있어

이건 문제가 없지

또 뷰 안에 설정에서 경계선에서 내부 뷰를 잘라서 이 부분만 보여줄건지

아니면 내부 뷰를 모두 보여주고 경계 밖에서도 그릴건지 설정해줄 수 있어

 

이게 우리가 사용자 인터페이스를 만드는 방법이야

이런 것들을 그룹화 하는거지

전에 했던 계산기 프로젝트에서 이미 많이 해봐서 익숙할거야

그리고 이런걸 만들면 모든 뷰는 결국 하나의 슈퍼 뷰를 가져

그 슈퍼뷰 안에 모든 것이 들어가지

또 모든 뷰는 자식 뷰를 몇 개라도 가질 수 있지

스택 뷰 안에 몇 개의 뷰가 스택될 수 있듯이 말야

자식 뷰는 뷰에서 찾아 볼 수 있는데

UIVIew의 subViews라는 변수로 확인할 수 있어

UIView의 배열로 저장이 되어있어

조금 뒤에 이야기 할건데 자식뷰간의 순서도 중요해

 

그리고 뷰에 대한 건 이게 끝이야

 

이게 우리가 뷰 계층을 만드는 방식이지

 

iOS에는 UIWindow라는 것도 있지만 거의 신경 쓰지 않아도 되

 

아마 거의 쓰지 않을 것이기 때문이지

 

UIWindow는 앱 마다 하나씩 밖에 있을 수 없어

 

사실은 외부 스크린이 있으면 여러 개가 있을 수도 있어

 

예를 들어 화면을 다른 곳에 뿌린다던가

 

아니면 2번째 화면이 있을 경우지

 

정확히 생각이 나지는 않지만 애플TV 같은 경우에는

 

너희가 가진 기기를 위한 두번째 스크린으로 사용할 수 있어

 

그런 상황에서는 2개의 UIWindow를 가질 수 있겠지만

 

하지만 아까 말했듯이 중요하지 않아

 

왜냐하면 UIWindow 객체를 만들거나

 

메세지를 보내는 일이 거의 없을 거거든

 

중요한 것은 뷰에 대한 것이고 뷰 계층에 관한 내용이야

 

iOS에선 작성될 일이 없을거야

 

Mac에서는 조금 다른데 Mac에서는 윈도우가 여러개 있지

 

분리된 윈도우 화면들이 iOS에서는 없지

 

그럴 만한 공간도 없고

 

이런 뷰 계층은 XCode 내부에서 그래픽을 활용해서 만들어져

 

계산기 프로젝트에서 했던 것처럼

 

하지만 뷰 계층은 코드로도 물론 만들 수 있지

 

이 두 메소드는 addSubview와 removeFromSuperView야

 

뷰를 넣고 빼는 것을 담당하지

 

addSubiew는 미래의 슈퍼 뷰에게 보내져

 

"이 뷰를 너 자신에게 추가해!" 이렇게 말하면서 말이지

 

removeFromSuperView는 지우고 싶은 뷰에 보내는거야

 

따라서 removeFromSuperView는 사실

 

정확히 말하자면 슈퍼뷰에서 "너 자신을" 없애라고 볼 수 있지

 

뷰 계층이 어디서 시작될까?

 

계층은 가장 꼭대기에서부터 시작되

 

스토리보드에서 보이는 MVC에서부터

 

거기에 모든 공간을 채우는 뷰가 있고

 

뷰 컨트롤러에 최상위 계층에 있는 뷰의 포인터가 있지

 

그 포인터는 view라고 이름이 지어졌어

 

만약 viewController를 본다면

 

여기 있는 사람들 다 viewController가 뭔지는 알지?

 

계산기 프로젝트를 할 때 대부분의 코드를 넣어둔 곳 있잖아?

 

Outlet과 Action들을 모두 연결해 놓은 곳 말이야

 

viewController 클래스는 view 라는 변수를 가지고

 

이걸 아직 사용하지는 않았지

 

하지만 이건 최상위 계층에 있는 UIView로 향하는 포인터야

 

그 뷰는 포인터를 가지기 위한 중요한 뷰야

 

왜냐하면 너흰 거기에다가 자식 뷰를 넣을 거거든

 

특히나 코드를 사용해서 넣을 것이라면 이 변수가 꼭 필요하지

 

addSubView를 호출해서 그 안에 넣어주면 되

 

그리고 이 view는 스토리보드에서 자동으로 연결이 되지

 

그러니까 너흰 아무것도 할 필요가 없어

 

자동으로 모든게 연결되니까

 

그리고 이 최상위 뷰가 bounds가 바뀌는 뷰야

 

예를 들어 기기를 회전시킨다고 해보자

 

bounds가 세로로 길고 얇은 상태에서

 

세로로 짧고 옆으로 긴쪽으로 변화하겠지

 

이게 최상위 뷰의 bounds야

 

이 최상위 뷰의 경계를 바꾸게 된다면

 

계산기 프로젝트에서 했던 것처럼 제약조건을 넣어둔다면

 

스택 뷰를 경계선과 묶어 두면

 

만약 그 경계선들이 바뀌었을 땐

 

당연히 그 안에 있는 스택뷰도 움직이고 늘어날거야

 

그리고 그 스택뷰는 어떻게 사이즈를 바꾸고

 

내부에 있는 것들을 어떻게 배열할지 알아

 

그게 스택 뷰가 하는 일 중에 하나거든

 

사실 그게 스택뷰가 하는 일의 대부분이야

 

그래서 기기를 회전하는 것, 즉 최상위 뷰의 bounds을 바꾸면

 

물결효과처럼 안에 있는 요소들이 모두 다시 재배치되는 이유야

 

이제 UIView를 초기화 하는 것에 대해서 알아보자

 

항상 그렇듯이 될 수 있으면 생성자(initializer)는 피하는게 좋아

 

= 뒤에 할당해 주는 선언을 피할 수 있다면 말이지

 

하지만 어쩔 수 없이 생성자를 호출해야 된다면

 

변수 선언으로 초기화를 할 수 없을 것 같을 때 말야

 

UIView의 초기화는 조심해야해

 

왜냐하면 중요한 생성자가 2개 있기 때문이지

 

그 중 하나는 필수적인 생성자인데

 

2번째 줄에 있는 init(coder: NSCoder)야

 

이건 스토리보드에서 나오면서 UIView를 만들 때 사용되

 

따라서 만약 뷰를 스토리보드에서 드래그 해서 만들었는데

 

만약 앱이 실행될 때 스토리보드가 재구성 되면

 

이 init(coder: NSCoder)생성자가 호출 될꺼야

 

init(frame: CGRect)는 뷰를 코드에서 만들 때 호출하게 될거야

 

이 생성자에 입력되는 프레임은

 

슈퍼뷰 안에 있는 이 뷰의 프레임이야

 

다시 말해, 이 뷰가 어디 있을 건지 입력해 주는 것이지

 

그리고 아무런 인자없이 생성자를 호출하는 것도 맞는 문법이야

 

결론적으로 크기가 없는 직사각형이 만들어지는 것이지

 

왼쪽 위에 매우, 매우 작게 나타나겠지

 

나중에 프레임을 지정해서 움직이게 할 수도 있어

 

어쨌든 생성자가 2개 있기 때문에

 

그리고 그 중에 두번째 것은 필수이기 때문에

 

결국에는 2개 다 구현을 하게 될거야

 

너가 만든 뷰들이 스토리보드에서 만들어지거나

 

코드로도 생성될 수도 있도록 해야하기 때문이지

 

그래서 내가 추천하는 것은 모든 생성자를

 

이런 setup( ) 함수 같은곳에 모두 넣어두는 거야

 

그리고 이 2개의 생성자들을 override 해서

 

그 내부에서 setup이라는 함수를 호출하는 거지

 

이렇게 하면 둘다 같은 setup을 수행하게 되겠지

 

당연한 이야기야

 

하지만 이 두 생성자를 모두 처리해줘야 된다는 것을 명심해

 

UIView를 위한 다른 생성 방식은

 

이 방법은 물론 스토리보드에서만 작동하기는 하지만

 

너의 코드를 awakeFromNib( ) 메소드 안에 집어넣는거야

 

이 메소드는 스토리보드에서 나오는 모든 객체에 호출할 수 있어

 

하지만 코드에서 객체를 생성하는 경우엔 전혀 호출이 되지 않지

 

그래서 아까 setup( )함수에 넣어둔 것처럼 할 수 있어

 

view가 스토리보드에서 올 때만 동작하는 것이 괜찮다면 말이야

 

이제 드로잉에 대해 이야기 해보자

 

이제 UIView가 주어졌고 이 위에 뭔가를 그리고 싶어

 

하지만 너희가 만든 뷰에 어떻게 그릴 수 있는지 보여주기 전에

 

우린 자료 타입에 대해서 알아 볼 필요가 있어

 

첫번째는 CGFloat이라는 건데

 

우린 Double 이나 Float을 쓰지 않을꺼야

 

스위프트의 Float 구조에 대해서 설명한 적은 없는데

 

Double이랑 매우 흡사해

 

Float은 단일정밀도(Single precision)를 가지고 있고

 

Double은 이중정밀도(Double precision)을 가지고 있는거지

 

그래서 Double이라고 불리우는거야

 

하지만 우리가 그릴 때는 Float이나 Double을 사용하지 않고

 

우리는 특별한 구조인 CGFloat을 사용할 거야

 

이것이 의미하는 건 우리가 때때로는 Double로 계산을 하겠지만

 

드로잉 할 때는 그 값을 CGFloat으로 바꿔야한다는 것이야

 

이렇게 CGFloat( )을 생성자로 호출해서

 

운좋게도 CGFloat의 생성자는 Double과 Float을 받을 수 있어

 

따라서 항상 CGFloat을 사용하도록 해

 

여러가지 버그들이 생길 거야

 

CGFloat만 받는 드로잉 API에

 

Double을 넣으려고 시도하면 그렇게 되거든

 

CGFloat말고도 다른 구조체들도 있어

 

CGPoint라는 구조는 2개의 변수만 가지고 있지

 

하나는 x좌표이고, 다른 하나는 y 좌표야

 

CGPoint가 하나의 점을 대변하지

 

그리고 CGSize라는 것이 있어

 

마찬가지로 형태로 2개의 변수를 가지고 있어. 너비와 높이.

 

이것들은 쉽지

 

그리고 CGRect가 있어

 

CGRect는 이 슬라이드가 전부야

 

왜냐하면 당연한 구조이기는 하지만

 

CGPoint로 이 직사각형의 시발점이 있고, CGSize로 이 직사각형의 크기를 가지고 있어

 

직사각형이면 시작점과 크기가 필요하기 때문이지

 

또 많은 메소드들을 가지고 있어

 

유용한 변수와 메소드들이 많은데

 

예를 들어 이 직사각형의 최소 x값(minX)이나

 

아니면 가운데 y값(midY)이나

 

아니면 다른 직사각형이랑 겹치는 부분을 찾아서

 

여기 있는 / 영역처럼 겹치는 부분을 가져올 수 있지

 

또 이 intersects( ) 메소드는 이 직사각형이

 

다른 직사각형과 겹치는 지 여부를 알려주지

 

이런 유틸리티 메소드들이 수십개 있어

 

따라서 이런 메소드들을 확실히 알아두는 것이 좋아

 

몇개의 직사각형을 겹치려고 하거나 할 때

 

이런 메소드들을 직접 만들지 않으려면 그래야 겠지

 

이것들이 우리가 드로잉할 때 쓸 핵심 타입들이야

 

따라서 익숙해지는 것이 좋지

 

이제 뷰 안에서 드로잉을 할 좌표계에 대해서 알아보자

 

원점(Origin)은 좌측 상단에 있어

 

직교 좌표계나 맥 처럼 좌측 하단이 원점이 아니야

 

iOS에서는 좌측 상단에 있어

 

이것은 y 증가는 스크린 아래로 움직인다는 뜻이야

 

따라서 여기 내가 만든 (500, 35) 점을 보면

 

X축으로는 500을 움직였지만

 

Y축으로는 35 밖에 아래로 움직이지 않았지

 

이 점이 저기 오른쪽에 있는 이유야

 

드로잉에 사용되는 모든 단위들은 포인트라고 불러

 

포인트(point)는 픽셀(pixel)이 아니야

 

아이폰 플러스나 아이폰 6플러스 같은 iOS 디바이스들은

 

매우 높은 해상도를 가지고 있어

 

매우 매우 높은 해상도지

 

포인트당 3픽셀을 가지고 있어

 

다른 디바이스 중 어떤건 포인트당 2픽셀이야

 

그리고 나머지는 1픽셀이지

 

항상 포인트 단위로 드로잉 할꺼니까

 

보통 픽셀에 대해서 너무 많이 신경쓰지 않아도 돼

 

만약 많은 픽셀을 가지고 있다면

 

그리는 선이 매우 부드러워 보이거나

 

너희가 가진 이미지가 매우 자세하게 표현되어서

 

그리는데 이미지 데이터가 많이 필요하게 되겠지

 

하지만 만약 너가 신경을 쓴다면

 

과제 3번에서도 신경을 좀 써야할텐데

 

UIView에게 contentScaleFactor가 무엇인지 물어야 할거야

 

저건 기본적으로 포인트당 얼마나 많은 픽셀이 있는지를 말해

 

드로잉 하는 경계선(boundary)에 대해 이야기 해보자

 

이건 매우 중요하고 헷갈리는 부분이 많아

 

간단한데도 이걸 설명하는데 많은 시간을 쓰지만

 

그래도 사람들은 머릿속이 망가지는 내용이야

 

너희가 그린 직사각형에 대해서 이야기해보자

 

직사각형 안에 그릴꺼니까

 

그릴 때 UIView 안에 있는 이 변수를 이용할 거야

 

bounds라고 하고 CGRect 타입이야

 

이것이 너만의 좌표계로 너가 드로잉한 직사각형이야

 

좌표계 안에서 그리는 것이지

 

드로잉 하는 코드를 쓸 때마다 이것을 사용해야 해

 

frame이라는 것도 있는데 이것도 CGRect타입이야

 

사람들이 이 두 개를 헷갈려 하지

 

frame 은 완전히 다른거야

 

frame은 완전히 다른 목표 가지고 있고

 

완전히 다른 좌표 시스템이야

 

만약 bound 대신에 frame을 사용한다고 하면

 

어떤 상황에서는 작동하겠지만, 다른 상황에서는 망가질거야

 

그렇다면 frame이란 뭘까?

 

frame 이란 슈퍼뷰 안에서 너의 뷰가 "어디"에 있는가를 나타내

 

frame은 직사각형이고 CGRect인데

 

슈퍼뷰의 좌표계 상에서 너의 뷰를 완전히 덮는 직사각형을 말해

 

그래서 내가 만약 어떤 뷰를 어떤 곳에 놓으려고 하면

 

슈퍼뷰 안의 어느 곳에 그 뷰가 들어갈지 지정해줘야 해

 

그 뷰의 frame을 지정해줘서 그렇게 할 수 있어

 

그리고 당연하게도 슈퍼뷰에 넣는 것을 이야기하기 때문에

 

슈퍼뷰의 좌표계 내에서 지정해줘야 하는 거지

 

자체 뷰안의의 좌표계에서 지정해주는 것이 아니라

 

center도 마찬가지로 많은 사람들이

 

center가 내 뷰의 정중앙을 의미하는 줄 알지

 

하지만 그렇지 않아

 

center는 슈퍼뷰의 좌표계를 기준으로 정중앙을 표현한거야

 

따라서 이건 너의 뷰의 위치를 결정해주지

 

frame과 center가 너의 뷰의 위치를 지정해주는거야

 

이것들은 너희의 드로잉과는 전혀 상관이 없어

 

그릴 때는 bounds를 이용해야 해

 

따라서 난 너희들의 드로잉 코드 내부에

 

frame이나 center를 사용하는 것을 보고 싶지 않아

 

이해하겠니?

 

이건 실습할 때 다시 강조하도록 하지

 

frame과 center에 대해서 하나 더 이야기 할 게 있는데

 

frame과 center의 크기과 너비가

 

같을거라고 생각할 수도 있지만 그렇지 않아

 

왜냐하면 view가 회전할 수 있기 때문이야

 

만약 여기 회전된 뷰 B가 있다고 했을 때

 

bounds는 200의 너비와 250의 높이를 가지고 있을거야

 

하지만 이 frame을 보면 너비 320에 높이 320을 가지고 있지

 

왜 그럴까?

 

이건 슈퍼뷰 좌표계에서 회전된 뷰를 모두 포함할 수 있는

 

가장 작은 직사각형이기 때문이야

 

따라서 높이와 너비는 같지 않아

 

이게 320,320 사이즈의 frame 이고, 이건 bound야

 

만약 B에서 드로잉을 하고 있다면

 

이게 회전되었는 지도 알 수 없어

 

따라서 여기 있는 좌표계인 bound를 쓰는지 확인할 필요가 있어

 

 

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

4.5 얼굴앱-얼굴형  (0) 2022.01.20
4.4 텍스트를 넣으려면  (0) 2022.01.20
4.3 그림을 그려보자  (0) 2022.01.20
4.2 뷰는 어떻게 만들까  (0) 2022.01.20
시작  (0) 2022.01.20