UIViewController는 UIKit에서 앱의 뷰 계층을 관리하는 개체이며, 한 개의 페이지(화면)는 한 개의 ViewController를 가진다. 컨트롤러 안에 컴포넌트들을 배치하여 화면을 구성하게 된다.
class ViewController: UIViewController {
override func loadView() {
// 수동으로 정의한 view를 초기화할 수 있다.
// self.view = customView()
}
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {}
override func viewIsAppearing(_ animated: Bool) {}
override func viewDidAppear(_ animated: Bool) {}
override func viewWillDisappear(_ animated: Bool) {}
override func viewDidDisappear(_ animaed: Bool) {}
}
ViewController의 속성인 view가 메모리에 올라가기 전에 view를 수동으로 생성하고 초기화 할 수 있는 생명주기. 이외에도 사전에 세팅해야하는 게 있다면 여기에 호출한다. 수동으로 view를 초기화하지 않는다면 자동으로 view의 초기화가 이루어진다.
ViewController에 컴포넌트를 선언할 때(특히,addTarget을 호출하는UIControl컴포넌트인 경우)let으로 선언하면 에러가 발생하는 경우가 있는데, 그 이유는ViewController의 속성들이 초기화되지 않은 상태에서 그 속성에 접근하려는 코드를 포함하고 있기 때문이다.button.addTarget(self, ...) // self따라서
lazy var로 선언하여 해당 컴포넌트가 호출되는 순간으로 초기화를 미뤄준다.
이건UIPageControl을 연습하다가 맞닥뜨렸던 에러인데,addTarget뿐만 아니라ViewController에 선언된 프로퍼티items에 접근할 때도 발생하는 걸 겪었었다.Cannot use instance member 'items' within property initializer property initializers run before 'self' is available
self가 사용 가능할 때(ViewController가 초기화 되고 나서) 프로퍼티를 사용할 수 있다는 내용이다. 저 에러 메시지를 처음 맞닥뜨렸을 때는 100% 이해하진 않은 채로 그냥lazy var로 바꾸어 해결했었는데, 강의를 통해 제대로 배우고 나니 완벽히 이해되었다.
view가 메모리에 올라갔음(load)을 의미하는 생명주기로, 한 번만 호출된다.
view가 메모리에 load되고 UI가 나타나기 직전의 생명주기. 다른 화면으로 넘어갔다가 다시 돌아올 때도 호출되기 때문에 viewDidLoad와 달리 여러 번 호출될 수 있다.
view가 나타나는 과정의 생명주기. view가 UI에 나타나기 직전과 직후 사이에 호출된다. 개인적으로 이 메소드의 용례가 궁금하다.
view가 화면에 나타난 직후의 생명주기. 사용자가 UI를 눈으로 확인할 수 있게 된 시점이다.
view가 화면에서 사라지기 직전의 생명주기. 다른 화면으로 넘어가게 될 경우 호출되는 것이다.
view가 화면에서 사라진 직후의 생명주기.
view가 disappear하는 거까지 배웠으면 다 배웠다는 생각이 들 수 있지만, auto layout을 제대로 적용하고 싶은 사람에게는 저게 끝이 아니다.

https://developer.apple.com/documentation/uikit/uiviewcontroller
애플 공식문서 UIViewController 페이지에서 스크롤을 아주아주 열심히 내리다보면 저 친구들이 등장한다. Configuring the view's layout behavior. 직역하면 화면의 레이아웃 행동(작용)을 설정. 대충 레이아웃 어쩌고 관련 속성들인 느낌이 바로 온다. 이중에서 viewWillLayoutSubviews()과 viewDidLayoutSubviews()를 실습해보려 하는데, 바로 위에 배운 메소드들과 생긴 게 비슷하니 무슨 역할을 하는지 바로 알 것 같다.
view가 subview들의 layout을 적용하기 직전의 생명주기
view가 subview들의 layout을 적용한 직후의 생명주기
실습으로 확인하기

연습용 프로젝트에 그냥 까만 정사각형의 subview 하나를 선언했다. 오토레이아웃 코드를 보면 subview를 화면의 중앙에 배치하고 superview와의 간격을 통해 width를 지정하고, height는 width와 같도록 지정함으로써 직접적인 수치 주입 없이 뷰의 위치와 크기를 정의했다. layout 생명주기를 통해 저 코드들이 적용되는 시점을 확인해보겠다.

우선 viewDidLoad → viewWillLayoutSubviews → viewDidLayoutSubview 순으로 호출되는 것이 보인다. subview의 크기를 출력해봤을 때, viewWillLayoutSubviews 시점까지 0, 0으로 크기가 안잡혀있고 viewDidLayoutSubviews 시점에 370, 370으로 잡혀있는 것을 확인할 수 있다. (370은 iPhone 16 Pro의 너비에서 32를 뺀 값이다.)
어떤 레이아웃은 뷰 컴포넌트의 크기 값을 이용하여 적용해야 할 때가 있다. 대표적인 예로 cornerRadius가 있는데, 정사각형을 완벽한 원으로 만들려면 이 값을 width 또는 height 값의 절반을 지정해줘야 한다.
subview.layer.cornerRadius = subview.bounds.width / 2
이 코드에서 370 / 2 값을 cornerRadius가 갖게 하려면, bounds.width 값이 370이 되고 나서 할당해줘야 된다는 의미다.
private lazy var subview: UIView = {
let view = UIView()
view.layer.cornerRadius = view.bounds.width / 2
return view
}()
이 코드? 소용 없다.
override viewDidLoad() {
super.viewDidLoad()
layout()
}
private func layout() {
view.addSubview(subview)
NSLayoutConstraint.activate([
// ... //
])
subview.layer.cornerRadius = subview.bounds.width / 2
}
이 코드도? 소용 없다.
override func viewDidLayoutSubview() {
subview.layer.cornerRadius = subview.bounds.width / 2
}
이 코드로 해야 한다.

참고로, 오토레이아웃이 아닌 직접적인 값을 주입하여 서브 뷰의 크기를 지정할 경우에는 메모리에 올리는 순간부터 초기화된 값을 갖기 때문에 viewWillLayoutSubviews에서도 디버깅이 된다.

x, y 값을 넣을 줄 몰라 원 위치가 별로지만 출력 내용으로 subview가 레이아웃 적용 전부터 크기 값을 갖고 있는 것을 확인할 수 있다.
지급 받은 강의 내용에는 없지만 layout life cycle까지 공부하게 된 계기가 있다. 계산기 앱 과제를 할 때 기기 화면이 커짐에 따라 버튼도 커지고, 그에 따라 모서리 둥글기도 적용되어 원 모양을 유지하는 오토레이아웃을 구현하고 싶었는데 이걸 몰라서 실패했기 때문이다.
사실 개인 프로젝트로 할 때 Combine을 이용해서 AppDelegate 단계에서 button layout이 window 크기를 구독하는 방식으로 구현했었다. 이것저것 공부를 안했던 당시 내가 떠올릴 수 있는 최선의 방식이었다.
디버깅 하면서 아무리 bounds.size를 출력해도 0만 나와서 답답했던 기억이 생생하다. 과제를 제출하면서 튜터님께 질문으로 남겼었는데 이걸 직접적으로 알려주시기 보다는 뷰의 생명주기를 찾아보라는 조언을 주셨다. 뷰 컨트롤러 생명주기가 강의에 나온 김에 이때다 싶어 함께 공부하게 되었다.
사실은 계산기 앱의 다른 컴포넌트를 구현하면서 이미 써보기도 했다. 바로 label을 scrollview 안에 넣고 contentOffset을 지정할 때 스크롤 뷰의 크기를 이용했기 때문인데, 이 때는 이걸 정확하게 이해하고 쓴 것은 아니었다. (scrollview 적용한 이야기)
하여튼 정리하고 나니까 UIKit에서의 뷰와 오토레이아웃에 대한 이해도가 올라간 기분이다. 이 내용을 활용해서 계산기 버튼 오토레이아웃을 적용해보고 싶다.
오,, 오토레이아웃과 연관지어서 하시다니
보법이 남다르시네요