UI 업데이트를 위해 사용하는 setNeedsLayout과 layoutIfNeeded를 알아보기 전에 먼저 이벤트 처리 과정이 어떻게 이루어지는지 알아보겠습니다.
UI를 업데이트하는 것은 리소스가 많이 들어가는 작업이기에 효율적인 업데이트를 위해 update cycle을 만들어 일정 시간 뒤 UI를 업데이트합니다. (iOS는 초당 60프레임을 보여주고, Update cycle은 1/60초)
즉, View의 변화가 생기더라도 즉시 업데이트하는 것이 아니라 한 cycle을 기다려야 하기 때문에 delay가 발생합니다.
You should not call this method directly. If you want to force a layout update, call the setNeedsLayout() method instead to do so prior to the next drawing update. If you want to update the layout of your views immediately, call the layoutIfNeeded() method.
이 메서드를 직접 호출하지 마십시오. 레이아웃 업데이트를 강제로 수행하려면 다음 drawing 업데이트 전에 대신 setNeedsLayout() 메서드를 호출하십시오. 만약 뷰의 레이아웃을 즉시 업데이트하려면 layoutIfNeed() 메서드를 호출하십시오.
layoutSubviews는 view 및 subview들의 크기를 다시 계산하거나 위치의 재지정을 다루는 메서드입니다.
현재 계산 중인 view와 subview들의 크기와 위치 정보를 모두 재귀적으로 계산하고 지정하기 때문에
시스템에 큰 부하를 주게 됩니다. 그리고 공식 문서에 나와있듯이 이 메서드는 직접 호출하는 것을 금지하고 있습니다.
Invalidates the current layout of the receiver and triggers a layout update during the next update cycle.
receiver(수신자)의 현재 레이아웃을 무효화하고 다음 업데이트 주기 동안 레이아웃 업데이트를 트리거합니다.
setNeedsLayout이 호출되면 시스템에게 view의 layout을 다시 계산해야 한다고 알려줍니다.
하지만 즉시 업데이트하는 것이 아니라, 다음 update cycle에서 layoutSubviews가 불릴 때 해당 view와 그 subview 들에 모두 적용됩니다.
layoutSubviews를 호출할 수 있는 가장 비용이 적은(부하가 적은) 방법입니다.
| 메서드 | 역할 | 호출 결과 |
|---|---|---|
| setNeedsDisplay() | 뷰의 콘텐츠(그래픽)를 다시 그리도록 요청 | draw(_ rect: CGRect)가 다음 루프에 호출됨 |
| setNeedsLayout() | 서브뷰들의 레이아웃을 다시 계산하도록 요청 | layoutSubviews()가 다음 루프에 호출됨 |
Lays out the subviews immediately, if layout updates are pending.
레이아웃 업데이트가 보류 중인 경우 즉시 하위 뷰를 배치합니다.
layoutIfNeeded가 호출되면, UI를 업데이트하라는 queue의 뒤에 넣는 것이 아니라, 맨 앞쪽에 넣어서 시스템이 바로 layoutSubviews를 호출하도록 만듭니다. (update cycle이 올 때까지 기다리지 않고 즉시 layoutSubviews를 발동시킴)
즉, 해당 메서드가 호출되면 update cycle을 바로 실행하여 layout이 즉시 적용됩니다.
예를 들어 아래의 코드와 같이 애니메이션을 구현할 때 layoutIfNeeded를 사용하여 Main Run Loop를 기다리지 않고, View의 업데이트를 즉시 동기적으로 실행하여 애니메이션 효과를 보여줄 수 있습니다.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
changeConstraints()
}
func changeConstraints() {
self.appIcon.snp.remakeConstraints {
$0.leading.equalToSuperview().offset(-self.appIcon.bounds.width)
$0.top.equalToSuperview().offset(-self.appIcon.bounds.height)
}
// 2초 동안 layoutIfNeeded() 메서드를 실행 -> Main Run Loop에서 2초 동안 layout을 업데이트
UIView.animate(withDuration: 2) { [weak self] in
guard let self = self else { return }
self.view.layoutIfNeeded()
}
}

https://sueaty.tistory.com/162
https://ios-development.tistory.com/986