선행 내용
아래 포스팅을 먼저 보고 오시면 좋습니다
ViewController의 Lifecycle을 다룬 내용입니다.
Layout 관련된 생명 주기
viewWillAppear()와 viewDidAppear() 사이에
레이아웃이 결정되기 전, 후의 생명 주기 메서드가 있습니다.
바로 viewWillLayoutSubviews()와 viewDidLayoutSubview() 입니다.
뷰가 생성된 후 bounds 및 위치 등의 레이아웃에 변화가 발생했을 때 호출되는 메서드들인데요.
오늘은 이 두 개의 메서드에 대해 알아보겠습니다.
Layout이란?
UIView의 Layout은 화면에서 UIView의 크기와 위치를 의미합니다.
모든 View는 frame을 가지고 있고 부모 View를 기준으로 어디에 위치하고 얼마나 크기를 차지하는지 나타냅니다.
UIView는 레이아웃이 변했다는 것을 시스템에게 알려주거나,
view의 레이아웃이 다시 계산될 때 특정 작업을 실행할 수 있도록 콜백 메서드를 제공합니다.
view가 생성된 후 레이아웃이 결정되기 전, 후에 호출되는 메서드가
오늘 알아볼 viewWillLayoutSubviews()와 viewDidLayoutSubview() 입니다.
레이아웃은 아래 순서로 보여집니다.
- 업데이트
- 레이아웃
- 그리기(draw)
업데이트 과정에서는 Auto Layout의 제약(Constraints)을 갱신합니다.
제약은 subview -> superview의 순서로 갱신됩니다.
제약이 모두 갱신되면 이 제약을 바탕으로 레이아웃 단계로 들어갑니다.
이 단계에서 view의 center와 bounds가 결정됩니다.
레이아웃의 갱신은 업데이트와 반대로 superview -> subview 순서로 호출됩니다.
draw 단계에서는 UIView의 drawRect()가 호출됩니다.
CoreGraphics를 사용하여 그리게 됩니다.
draw 단계에 대해서는 위 링크에 자세히 나와있습니다.
layoutSubviews, setNeedsDisplay 등 레이아웃에 대한 내용은 다른 포스팅으로 다시 다루겠습니다!
viewWillLayoutSubviews
viewWillLayoutSubviews()는 ViewContoller의 view가 subview들의 레이아웃을 변경하기 직전에 호출됩니다.
view의 bounds가 변경되면 view는 서브뷰들의 위치를 조정합니다.
view가 서브뷰의 레이아웃을 변경하기 직전임을 ViewController에게 알리는 메서드입니다.
view가 서브뷰들의 레이아웃을 변경하기 전에 뭔가 하고 싶다면 이 메서드를 override하여 사용하면 됩니다.
기본값은 아무것도 수행하지 않습니다.
아래 동작을 할 때 사용하면 유용합니다.
- view를 추가하거나 제거
- view들의 크기나 위치를 업데이트
- 레이아웃 제약을 업데이트
- view와 관련된 property 업데이트
viewDidLayoutSubviews
viewDidLayoutSubviews()는 ViewController의 view가 subview들의 레이아웃을 변경한 직후 호출됩니다.
ViewController의 view의 bounds가 변경되면
view가 서브뷰들의 위치를 조정한 뒤에 해당 메서드를 호출합니다.
이 메서드가 호출됐다고 하더라도 view의 서브뷰의 레이아웃이 바뀌었음을 나타내지는 않습니다.
각 서브뷰들은 각자 레이아웃을 조정합니다.
뷰가 서브뷰들의 배치를 조정한 후 하고 싶은 작업이 있을 때
이 메서드를 override하여 사용하면 됩니다.
아래 동작을 할 때 사용하면 유용합니다.
- 다른 view들의 contents 업데이트
- view들의 크기나 위치를 최종적으로 조정
- collectionView, tableView의 데이터 reload
직접 호출해보기
viewWillLayoutSubview()와 viewDidLayoutSubview()를 호출해 봅시다.
First 화면의 Next 버튼을 누르면 Navigation Push가 되어 Second 화면으로 이동합니다.
First ViewController가 처음 생성될 때
viewWIllAppear()와 viewDidAppear() 사이에
viewWillLayoutSubview() -> viewDidLayoutSubview()가 호출됩니다.
Layout Subview 메서드는 내부 구현에 따라 호출 횟수가 달라질 수 있습니다.
Second ViewController를 Push 할 때
화면이 넘어가면서 레이아웃이 변경되기 때문에 First VC에서도 Layout Subview 메서드들이 호출됩니다.
First VC의 로그만 살펴보면
viewWillLayoutSubviews -> viewDidLayoutSubviews -> viewWillDisappear -> viewWillLayoutSubview -> viewDidLayoutSubview -> viewDidDisappear 순서로 호출되는 것을 볼 수 있습니다.
Second VC의 로그는 First VC 생성될 때와 동일합니다.
view가 load되면서 추가될 때 loadView에서 Layout Subviews 메서드들을 호출합니다.
view가 추가, 스크롤, 크기 조정, 재사용될 때도
setNeedsLayout와 setNeedsDisplayInRect가 내부적으로 호출되면서 Layout Subviews를 호출합니다.
따라서 ViewController 구현에 따라 두 번도, 세 번도, 네 번도 호출될 수 있습니다.
이 메서드들은 여러번 호출된다는 것이 다른 생명 주기 메서드들과의 차이점이네요.
레이아웃 관점에서 살펴봐야 한다는 것이 포인트입니다.
Second ViewController를 Pop 할 때
Second VC가 Pop이 될 때는 레이아웃을 재조정하지 않습니다.
그렇기 때문에 Disappear 메서드들만 호출이 된 것이죠.
대신 First VC는 다시 나타나면서 레이아웃이 조정되고 Lyaout Subviews 메서드들이 호출됩니다.
활용
view의 레이아웃의 크기나 위치와 관련된 로직을 수행할 때는 viewDidLayoutSubviews()에서 해야합니다.
가운데 Next 버튼의 frame과 bounds를 viewDidLoad()와 viewDidLayoutSubviews()에 찍어보았습니다.
두 개의 수치가 다른 것을 볼 수 있죠?
viewDidLayoutSubviews()에서 view의 frame과 bounds가 최종적으로 결정되기 때문에
viewDidLayoutSubviews()에서 처리를 해주어야 합니다.
마무리
오늘은 레이아웃과 관련된 ViewController 생명 주기에 대해 알아보았습니다.
레이아웃 관련된 포스팅을 먼저 작성하고 해당 내용을 작성해야 순서가 맞는 것 같은데...
어쩌다보니 먼저 작성하게 되었습니다 ㅎ;ㅎ (아직 공부가 안 되서... ㅠ)
추후 레이아웃에 대해 포스팅할 때 수정사항이 있다면 다시 언급하도록 하겠습니다.
감사합니다!
참고
https://developer.apple.com/documentation/uikit/uiviewcontroller/1621437-viewwilllayoutsubviews
https://developer.apple.com/documentation/uikit/uiviewcontroller/1621398-viewdidlayoutsubviews
https://ios-development.tistory.com/195
https://developer.apple.com/documentation/uikit/uiview/1622482-layoutsubviews
아직은 초보 개발자입니다.
더 효율적인 코드 훈수 환영합니다!
공감과 댓글 부탁드립니다.