- 네이버 지도의 Bottom Sheet 구현
- UIPanGestureRecognizer를 사용하여 사용자의 터치에 따른 화면(View or ViewController)의 이동
구현아이디어
UIPanGestureRecognizer를 이용하여 띄워놓은 view를 사용자의 터치 이벤트에따라 view를 움직이게 하는 bottomSheet를 구현하고자 했습니다.
구현과정에서의 오류
- UIPanGestureRecognizer의 동작방식에 대한 이해부족 UIPanGestureRecognizer는 superView와의 관계를 통해 bottomSheetView의 위치를 잡도록 되어있습니다. 하지만 이 부분을 놓쳐 bottomSheetView의 독자적인 height constraint를 변경하여 구현 하려고 했고 실행 결과 몇번의 제스쳐에는 반응을 했지만 이 후 bottomSheetView의 위치를 잡을 수 없어 bottomSheetView가 움직이지 않았습니다. → superView와 bottomSheetView에 top constraint를 주어 해결하였습니다.
- UIPanGestureRecognizer의 구현 과정에서 다음과 같은 recognizer.setTranslation(CGPoint.zero, in: bottomSheetView) 메소드를 사용하였는데 translation메소드는 드래그가 처음 시작된 지점으로부터 이동좌표를 반환 합니다. 하지만 우리는 뷰를 이동시켰기 때문에 드래그가 처음 시작된 지점의 좌표는 더 이상 뷰의 좌표가 아닙니다. 이때 UIPanGestureRecognizer는 뷰가 이동했다는 사실을 알지 못하므로bottomSheetView이 이동된 좌표의 값을 zero로 초기화 해주었습니다. 이말은 즉 translationY(recognizer.translation(in: bottomSheetView).y)의 값을 bottomSheetView.frame.origin.y의 값에 계속해서 더해 주어야 하므로 self.bottomSheetView.frame.origin.y += translationY 와 같은 코드를 쓰게 됩니다.
- UIView.animate 함수 내부, 외부에서 구현의 차이
@objc private func didPan(_ recognizer: UIPanGestureRecognizer) {
let translationY = recognizer.translation(in: bottomSheetView).y
recognizer.setTranslation(CGPoint.zero, in: bottomSheetView)
self.bottomSheetView.frame.origin.y += translationY
if recognizer.state == .ended {
let top = self.view.frame.height * 0.8
let middle = self.view.frame.height / 2
let bottom = self.view.frame.height * 0.2
if self.bottomSheetView.frame.origin.y > (top + middle) / 2 {
self.bottomSheetView.frame.origin.y = top
} else if self.bottomSheetView.frame.origin.y <= (top + middle) / 2 &&
self.bottomSheetView.frame.origin.y > (middle + bottom) / 2{
self.bottomSheetView.frame.origin.y = middle
} else {
self.bottomSheetView.frame.origin.y = bottom
}
}
UIView.animate(withDuration: 0.2) {
self.view.layoutIfNeeded()
}
}
@objc private func didPan(_ recognizer: UIPanGestureRecognizer) {
let translationY = recognizer.translation(in: bottomSheetView).y
recognizer.setTranslation(CGPoint.zero, in: bottomSheetView)
UIView.animate(withDuration: 0.2) {
self.bottomSheetView.frame.origin.y += translationY
if recognizer.state == .ended {
let top = self.view.frame.height * 0.8
let middle = self.view.frame.height / 2
let bottom = self.view.frame.height * 0.2
if self.bottomSheetView.frame.origin.y > (top + middle) / 2 {
self.bottomSheetView.frame.origin.y = top
} else if self.bottomSheetView.frame.origin.y <= (top + middle) / 2 &&
self.bottomSheetView.frame.origin.y > (middle + bottom) / 2{
self.bottomSheetView.frame.origin.y = middle
} else {
self.bottomSheetView.frame.origin.y = bottom
}
}
}
}
상단의 코드는 외부에서 구현하여 animations 블럭에 레이아웃을 변경하라는 코드를 작성하였고 하단의 코드는 animations 블럭 내부에 직접 구현한 형태입니다. 상단의 코드에서는 bottomSheetView의 변화에 대해 animation이 적용되지 않았고 하단의 코드에서는 적용되었습니다.
그 이유를 알아보았는데(추측) 다음은 animations 내부에 정의 할수 있는 것들입니다
animations (필수)
실제로 애니메이션이 될 부분을 정의한다.
frame / bounds / center : 뷰의 위치와 크기
transform : 좌표 행렬값
alpha : 투명도
backgroundColor: 배경색
contentStretch : 확대 / 축소 영역
hidden과 같이 중간값 계산이 불가능한 속성은 애니메이션이 안된다.
위의 내용과 layoutIfNeeded라는 이름에서 유추하여 생각해보면 제약조건을 통해 뷰의 위치를 구성하였을때는 animations 내부에 layoutIfNeeded를 통해 뷰의 레이아웃을 요청할 수 있지만 frame이나 bounds를 통해 뷰의 위치를 구성할때는 animations 내에 직접구현을 하면 되지 않을까라고 추측해봤습니다.
결과 화면
'ios > UI구현' 카테고리의 다른 글
[iOS, Swift] AppStore layout만들어보기(UICollectionView, CompositionalLayout 활용) (0) | 2023.07.27 |
---|---|
[iOS, Swift] 네이버 웹툰 메인 페이지(UIPageViewController, UISegnmentControl 활용) (0) | 2023.07.27 |
[iOS, Swift] Sticky Header(배달의민족, 당근마켓) (0) | 2023.07.27 |
[Swift] UICollectionView - Compositional Layout section의 동적인 높이 조절 (0) | 2023.05.30 |