[TIL #17] 2023 / 04 / 19
오토 레이아웃을 잡고 객체의 frame을 사용하려 하는데, 한번 애를 먹었던 기억이 있어,
오늘은 이렇게 Update Clycle의 viewDidLayoutSubviews를 한번 간단히 정리하고 넘어가려고 합니다.
일단 Life Cycle + Update Cycle를 그림으로 한번 보고 가시죠.
각 Cycle 마다, 역할이 조금씩 다릅니다.
각 시점마다 무슨일이 일어나는지 알고나면 프로젝트를 만들 때, 도움이 많이 되더라고요.
viewDidLayoutSubviews
말 그대로 "UIVIew" 객체가 모든 서브뷰들의 레이아웃을 변경한 후에 호출되는 메서드입니다.
다른 메서드와 달리, 뷰와 서브뷰들의 레이아웃이 모두 정해진 후 호출되는 메서드입니다.
다른식으로 말해보면,
viewDidLayoutSubviews는 뷰 + 서브뷰의 크기와 위치가 최종적으로 확인된 후에 호출이 되므로,
즉, 이 시점에서는 모든 레이아웃이 변경 된 후에 호출이 되므로, 뷰와 서브뷰들의 마지막 실제 크기와 위치를 얻을 수 있습니다.
분명히 머리로는 이해가 되지만, 언제 써먹는지 궁금할 것 같아서,
간단한 예제를 들고 왔습니다.
일단 버튼의 레이아웃을 잡아줬고, cornerRadius를 통해 버튼의 모양을 원으로 만들어 주는 작업을 했습니다.
그리고 viewDidLoad에서 실행을 시켜 줬습니다.
class ViewController: UIViewController {
let button = UIButton()
override func viewDidLoad() {
super.viewDidLoad()
configUI()
}
func configUI() {
view.addSubview(button)
button.backgroundColor = .darkGray
button.translatesAutoresizingMaskIntoConstraints = false
button.layer.cornerRadius = button.frame.width / 2
button.clipsToBounds = true
NSLayoutConstraint.activate([
button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
button.centerYAnchor.constraint(equalTo: view.centerYAnchor),
button.widthAnchor.constraint(equalToConstant: 50),
button.heightAnchor.constraint(equalToConstant: 50)
])
}
}
그런데 분명히 버튼 너비 크기만큼 cornerRadius를 줬는데도,
버튼 모양이 변하지 않네요? 이상하죠?
원으로 변하지 않는 이유를 확인을 위해,
각 Life Cycle 시점마다 버튼의 Frame을 알아 보기위해 프린트를 찍어봤습니다.
class ViewController: UIViewController {
let button = UIButton()
override func viewDidLoad() {
super.viewDidLoad()
configUI()
print(#function + " --- \(button.frame)")
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
print(#function + " --- \(button.frame)")
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
print(#function + " --- \(button.frame)")
}
func configUI() {
view.addSubview(button)
button.backgroundColor = .darkGray
button.translatesAutoresizingMaskIntoConstraints = false
button.layer.cornerRadius = button.frame.width / 2
button.clipsToBounds = true
NSLayoutConstraint.activate([
button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
button.centerYAnchor.constraint(equalTo: view.centerYAnchor),
button.widthAnchor.constraint(equalToConstant: 50),
button.heightAnchor.constraint(equalToConstant: 50)
])
}
}
빌드 시, 결과 값으로 아래와 같이 나옵니다.
viewDidLoad() --- (0.0, 0.0, 0.0, 0.0)
viewWillAppear(_:) --- (0.0, 0.0, 0.0, 0.0)
viewDidLayoutSubviews() --- (170.0, 397.0, 50.0, 50.0)
viewDidLoad 및 viewWillAppear에서는 (0, 0, 0, 0)이 나오고,
viewDidLayoutSubviews의 시점에서만, Frame의 위치 및 크기들이 정상적으로 나왔네요.
이유는 위에서도 간단히 말했지만,
viewDidLayoutSubviews는 뷰가 생성되고 화면에 보이기 직전입니다.
즉, 뷰가 생성된 후, 최초로 뷰들의 크기나 위치를 알 수 있는 시점이므로
이와 같이 서브뷰의 크기를 사용이 가능하게 됩니다.
class ViewController: UIViewController {
let button = UIButton()
override func viewDidLoad() {
super.viewDidLoad()
configUI()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
button.layer.cornerRadius = button.frame.width / 2
}
func configUI() {
view.addSubview(button)
button.backgroundColor = .darkGray
button.translatesAutoresizingMaskIntoConstraints = false
button.clipsToBounds = true
NSLayoutConstraint.activate([
button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
button.centerYAnchor.constraint(equalTo: view.centerYAnchor),
button.widthAnchor.constraint(equalToConstant: 50),
button.heightAnchor.constraint(equalToConstant: 50)
])
}
}
viewDidLayoutSubviews를 간단히 정리하면
일반적으로 서브뷰를 다시 배치해야 하거나, 크기 or 위치를 조정하는 작업에서 사용할 수 있습니다.
왜냐하면, viewDidLayoutSubviews는 모든 레이아웃이 모두 정해진 후, 호출이 되므로
서브뷰들의 크기 or 위치 값을 사용하여 UI를 업데이트할 수 있는 거죠!
참고. layoutSubviews
Life Cycle의 순서를 나타낸 그림을 다시 확인해보면,
실행 순서는 layoutSubviews() -> viewDidLayoutSubviews() 순으로 호출되게 됩니다.
layoutSubviews()는
뷰의 크기나 위치, 뷰 계층 구조가 변경될 때마다, 호출하게 되는데,
이 작업은 UIView의 하위 모든 서브뷰들의 크기 및 위치를 정해주는 작업이므로 비용이 큰 작업이라고 합네요.
(ex. 뷰가 처음 생성 될 때, 서브뷰가 추가 및 제거될 때, setNeedsLayout, layoutIfNeeded 메서드 등)
그 후, viewDidLayoutSubviews가 자동으로 호출이 되게 됩니다.
다시 말하지만, layoutSubviews()는 비용이 큰 작업이기 때문에 자주 호출되면, 성능이 떨어질 수 있습니다.
그래도 꼭 사용해야 된다면, 효율을 높히기 위해 최적화하는 방법들을 소개하겠습니다.
1. setNeedsLayout를 호출한 후, layoutIfNeeded를 통해 즉시 레이아웃을 업데이트시켜,
layoutSubviews를 여러 번 호출되는 것을 방지할 수 있습니다.
2. 뷰 계층 구조를 단순화하여 비용을 줄일 수 있습니다 (하위 뷰의 수를 줄인다)
3. 되도록이면 layoutSubviews에서 복잡한 계산을 피하고 정말 필요할 때만 실행()해줘야 합니다.
참고 사이트
한번 이분 참고해 보세요. Update Cycle을 잘 정리해 놓으셨네요.
https://jeonyeohun.tistory.com/336
'프로젝트' 카테고리의 다른 글
[Swift/TIL #19] 현재 입력된 텍스트의 줄 수 구하기 (0) | 2023.04.30 |
---|---|
[Swift/TIL #18] UICollectionViewCompositionalLayout으로 Cell의 크기를 동적으로 만들어 보자 (더보기 버튼) (0) | 2023.04.28 |
[Swift/TIL #16] Layer 그림자 설정 중, 보라색 경고 (dynamic shadows) (0) | 2023.04.18 |
[Swift/TIL #15] UIMenu를 사용해보자 (0) | 2023.04.17 |
[Swift/TIL #14] UITabBarAppearance 적용하기 (0) | 2023.04.14 |