iOS를 개발하다보면 View의 Layout과 Content와 관련된 이슈를 자주 접하게 된다.
실제로 UIView가 언제 Update되는지 모르기 때문에 발생한다.
View가 언제 Update 되는지 알기 위해서는 iOS App's Run Loop 를 이해하고 그것이 UIView가 제공하는 메서드들과 어떤 관계를 갖고 있는지 파악해야 한다.
iOS App's Run Loop
iOS App의 Main Run Loop는 유저로부터 모든 input Event를 받고, 응답을 담당한다.
유저가 발생한 모든 상호작용은 Event Queue에 추가된다.
아래의 사진처럼 App 객체는 Event Queue로부터 Event를 하나씩 꺼내서 App의 다른 객체들에게 전달한다.
App 객체는 유저로부터 input Event를 해석하고 그에 상응되는 App의 Core 객체들 안에 있는 Handler를 호출해준다.
또한 이러한 Handler는 개발자들이 만들어놓은 코드를 실행한다.
이러한 메서드들이 반환되면 다시 Main Run Loop로 돌아가 Update Cycle이 다시 시작된다.
Update Cycle은 View들을 배치하고 다시 그리는 역할을 한다.
Update Cycle
Update Cycle은 App이 유저로부터의 모든 Event Handling Code를 수행하고 다시 Main Run Loop로 컨트롤을 반환하는 지점
바로 이 지점에서 시스템은 우리의 View들을 배치하고(layout), 보여주고(display), 제약(contraints)한다.
만약 우리가 Event Handler들을 처리하는 과정에서 어떤 UIView에 변화를 준다면, 해당 UIView는 다시 그려져야 한다고 표시됨
다음 Update Cycle에서 시스템은 이 UIView의 모든 변화를 수행한다.
유저가 상호작용하는 것과 Layout이 변하는 시간의 Gap은 유저가 인지하지 못한다.
iOS App은 60 FPS이고, Update Cycle은 이 중 1 프레임에 해당된다.
이렇게 빠르게 Update되기 때문에, 유저는 UI와 상호작용 간의 차이를 느끼지 못한다.
그러나 이벤트가 처리되는 시점과 실제로 View가 다시 그려지는 시점의 차이가 존재하기 때문에, View는 우리가 View를 Update하기 원하는 Run Loop의 특정 시점에 Update되지 않을 수도 있다.
이는 다음과 같은 위험을 초래한다.
만약 우리가 View의 마지막 Layout이나 Content에 대해 계산을 해야하는 시점이라면, 예전 정보를 갖고 View를 조작할 가능성이 생긴다.
Layout
UIView의 Layout은 화면에서 UIView의 크기와 위치를 의미한다.
모든 View의 frame을 갖고 있고, 이는 SuperView의 좌표계에서의 위치와 크기를 나타낸다.
UIView는 시스템에게 Layout이 변했다고 알려줄 수 있는 메서드
View의 Layout이 다시 계산되는 시점에 특정 작업을 실행할 수 있게 오버라이드 가능한 콜백 메서드를 제공
layoutSubviews()
View와 SubView들의 위치와 크기를 재조정한다.
현재 View와 모든 SubView들의 위치와 크기를 제공한다.
이 메서드는 재귀적으로 모든 SubView들의 layoutSubviews까지 호출되기 때문에, 부하가 크다.
이 메소드를 직접 호출하면 안된다
대신, layoutSubviews를 시스템이 호출하도록 유도하는 방식 존재
이 방식은 모두 run loop가 돌아가는 동안 layoutSubviews가 실행되는 시점이 모두 다르다.
setNeedsLayout() 호출 or 즉시 업데이트를 원하면 layoutIfNeeded() 메소드 호출
layoutSubviews가 완료될 때, View를 소유한 VC의 viewDidLayoutSubviews가 호출된다.
layoutSUbviews는 View의 layout이 변화했다는 callback이기 때문에, layout 관련 로직은 오래된 layout을 사용하는 것을 방지하기 위해 viewDidLoad/viewWillAppear가 아닌 viewDidLayoutSubviews에 호출해야 한다.
자동 refresh triggers
다음과 같은 이벤트들은 자동으로 View가 그것들의 layout에 변화가 생겼다는 것을 인지하여, 시스템에서 layoutSubviews가 다음 update Cycle에서 호출된다.
View를 Resizing
SubView 추가
UIScrollView 스크롤 시, UIScrollView와 그것의 부모뷰에 layoutSubviews가 호출됨
Device를 회전
View의 Constraint를 변경
위와 같은 방법들은 자동으로 시스템이 알아채어 layoutSubviews를 호출해준다.
그러나 layoutSubviews를 직접 호출할 수 있는 방법들도 존재한다.
setNeedsLayout()
layoutSubviews를 가장 적은 부하로 호출할 수 있는 메서드이다.
setNeedsLayouts는 시스템에게 이 View의 Layout이 재계산되어야 한다고 알린다.
setNeedsLayouts는 즉시 반환되고, 실제로 View Update를 해주는 것은 아니다.
대신, 시스템이 다음 Update Cycle에서 layoutSubviews를 View와 SubView들에게 호출하게 한다.
실제로 setNeedsLayouts가 호출되는 시점과 View가 다시 그려지는 시점은 정확하지는 않지만, 유저가 인지할 수는 없다.