안녕하세요 소라미입니다 :D
사실 이 포스팅이 오늘 주제는 아니고.. 다른 포스팅을 작성하고 있었는데
갑자기 막혔는데 도저히 모르겠더라구요??
그래서 노선을 틀어버림 ㅋㅁㅋ
이번엔 UI 개발을 할 때 어쩌면 자주 사용하는
setNeedsLayout
layoutIfNeeded
이 두 가지 메서드에 대해서 알아보려고 합니당!
가볍게 개념을 맛만 볼 정도예요
왜냐면 깊게 들어가면 너무 깊어지므로.. 그건 나중에 본 블로그에서나 다루겠습니다!!
우리가 개발을 하다보면요
어떤 애니메이션을 그리거나, 혹은 뷰의 크기를 변경하거나, constraints를 업데이트 하는 등 화면을 다시 그려야 할 때가 있죠??
이런 식으로 어떤 UI가 다시 그려져야 할 때,
iOS는 야!!! UI 바껴랏!! 한다고 즉시 다시 그리진 않아요!
무슨 말이냐면,
iOS는 앱이 실행되면 Main Run Loop라는 것을 돌리는데,
(이 루프에 대해 잘 모르겠으면 이 포스팅을 보고 오시면 되구요 :0)
이 Main Run Loop라는 것은, 아래와 같이 업데이트 사이클이란 것을 돈답니당
한 번의 사이클이 다 돌았을 때, 그제야 바뀌어야 되는 UI들을 한꺼번에 업데이트 한단 말이에요
좀 더 쉽게 얘기하자면,
내가 바로 어이!! 애니메이션 좀 그려봐! 라고 즉시 명령을 한들,
현재 돌고있는 Main Run Loop의 사이클이 다 돌 때까지, 업데이트가 되지 않는다는 말입니당
근데 보통.. 이 Main Run Loop의 사이클 주기가 긴 편ㅇㅣ 아니어라.. (매우 빠른 편 ...)
사람의 눈에는 그렇게 크게 차이를 느끼진 못하거덩여?
근데 만약 시시각각 바로바로 0.1초 단위로 프레임이 바뀌어야 하는 애니메이션 같은 경우엔 문제가 될 수도 있어요
topAnchor = animationView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 200)
자 이렇게 topAnchor가 safeArea부터 200만큼 떨어져있는 빨간색 공 뷰가 있다고 해요
저희는 애니메이션 시작! 버튼을 눌렀을 때
3초 동안
이렇게 safeArea부터 500만큼 떨어지는 애니메이션을 주고 싶어요
그러면 이렇게 코드를 짜겠죠??
@objc func animation() {
UIView.animate(withDuration: 3) { [weak self] in
self?.topAnchor.constant = 500
}
}
근데 이렇게 짜면 애니메이션이 어떻게 되냐면여
네 이렇게 레전드 멍미가 됩ㄴㅣ다
이는 위에서 얘기 했듯이,
시시각각 View의 Frame을 업데이트 해야하는 애니메이션의 의도와는 달리,
Main Run Loop의 라이프 사이클이 한바퀴 돌때까지 기다렸다가
돌아왔을 때 애니메이션 블락안의 코드를 실행해버려서 그럽니다..ㅠㅠㅠㅠ
(제가 업데이트 하고 싶은 시점과, 실제 View의 Frame이 다시 그려지는 시점이 달라서 생기는 문제옝요)
따라서 먼 조치를 취해야 겟져?? 옙옙
엥 setNeedsLayout vs layoutIfNeeded 차이라면서 갑자기 웬 layoutSubvies() 메서드인가 싶죠???
사실 위의 애니메이션처럼 뷰의 프레임이 바뀌는 것 처럼,
뷰의 위치나 사이즈를 재계산 해서 배치해야 할 경우,
UIView는 클래스 내부의 인스턴스 메서드인 layoutSubView라는 메서드를 반드시 호출해야 해여
그러면 이 메서드는 내 subView들의 layoutSubView라는를 재귀적으로 호출해서
내 subView들까지 줄줄이 재배치가 되는 거거든요
(따라서 뷰의 레이아웃과 관련된 로직을 viewDidAppear같은 곳이 아닌 viewDidLayoutSubVies()에서 하는 겁니당)
그럼 결국, 우리가 UI를 다시 그려야 하는 시점이 오면
이 layoutSubView라는 메서드를 호출시켜야 한다는 말이자나여???
근데 사실 layoutSubView 메서드는 직접 호출하는 것이 금지되어 있슴니당
시스템에 의해서 재배치 되어야 하는 경우, 자동으로 호출되기 때문이에요
아ㅏ- 여기까지 왔으면 헷갈릴 수 있는데!
시원하게 정리한번 하구 가져
UI의 레이아웃을 다시 그리는 메서드는 layoutIfNeeded 메서드예요
하지만 이 메서드는, 시스템적으로 호출이 되기 때문에 우리가 직접 호출할 수 없어요
시스템적으로 어떻게 호출이 되느냐면,
우리가 만약 A라는 뷰의 레이아웃을 바꿔주고 싶다면,
시스템은 현재 Main Run Loop의 사이클이 한 바퀴 다 돌때까지 기다리구여. 그리고 사이클이 다 돌았을 때 그때 layoutIfNeeded 메서드를 실행한답니당
저희가 위에서 UIView.animate 안에서 레이아웃이 바뀌어야 하는 코드를 호출했잖아ㅕ??
따라서 animate 메서드에서 (아마) Main Run Loop의ㅣ 한 사이클이 다 돌고 난 후에 이 layoutSubView가 호출되었을 거예요
이해가 갔죠??
그러면 만약 내가 View가 변해서 직접적으로 layoutIfNeeded를 호출하고 싶을 때가 있잖아요
그럴 때 사용하는ㄴ 메서드가 바로
setNeedsLayout
layoutIfNeeded
이 두가지 메서드입니다!
난(개발자가) 지금 직접 레이아웃을 변경시켜야겠습니다!
근데 참을성이 조으니 기존 시스템 방식처럼, 다음 UI Update Cycle까지는 기다려주겠슴니다!
하고 호출하는 것이 setNeedsLayout임니다
따라서,
@objc func animation() {
UIView.animate(withDuration: 3) { [weak self] in
self?.topAnchor.constant = 500
self?.view.setNeedsLayout()
}
}
이렇게 작성해주먄
여전히 공이 공뚝떨
그것은 시스템적으로 호출되던 방법과 같이 다음 Update Cycle까지 기다리기 때문인데요
따라서 다음 사이클이고 나발이고 바로 시시각각 업데이트가 필요한 경우에느
위 메서드를 사용해주시먄 됨니다
이 메서드를 사용할 경우,
다음 Update Cycle이 도는 것을 기다리지 않고 바로 layoutSubView 메서드를 호출해버려요
@objc func animation() {
UIView.animate(withDuration: 3 { [weak self] in
self?.topAnchor.constant = 500
self?.view.layoutIfNeeded()
}
}
그럼
이렇게 시시각각 layoutSubView 메서드를 호출하기 때문에
우리가 원하는 애니메이션이 나오는 겁니다!!
우리가 뷰의 레이아웃을 바꾸는 작업을 했을 때
layoutSubviews()를 호출해야 뷰가 재배치 된다
근데 이 메서드는 직접 호출할 수 없다
시스템적으로 사이클에 의해 호출이 된다
따라서 시스템 사이클이고 나발이고 어떤 레이아웃을 즉시 업데이트 하고 싶을 때,
layoutSubviews()를 간접 호출할 수 있는 메서드가 있는데
그것이
setNeedsLayout
layoutIfNeeded
이 두가지 메서드가
🟣setNeedsLayout 는
호출 즉시 다음 업데이트 사이클까지 기다렸다가 뷰를 갱신(layoutSubviews를
호출)하는 것이고,
🟣layoutIfNeeded 는
호출 즉시 참을성이 부족해 업데이트 사이클이고 나발이고 바로 뷰를 갱신(layoutSubviews를 호출)해버린다
끗
layoutIfNeeded가 layoutSubviews를 호출하기 위함이라면, layoutSubviews를 직접 호출해도 될 것 같은데요, 혹시 차이점이 있을까요?