Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
Tags
- Refactoring
- swift documentation
- Clean Code
- 리펙터링
- swiftUI
- Observable
- 스위프트
- 리팩토링
- tableView
- 클린 코드
- MVVM
- uiscrollview
- uitableview
- rxswift
- 애니메이션
- UITextView
- ios
- clean architecture
- collectionview
- HIG
- combine
- Xcode
- ribs
- 리펙토링
- map
- UICollectionView
- Human interface guide
- RxCocoa
- Protocol
- SWIFT
Archives
- Today
- Total
김종권의 iOS 앱 개발 알아가기
[iOS - swift] 5. long press gesture와 애니메이션 - gesture 도중 화면 끝으로 가면 자동으로 스크롤되는 기능 본문
UI 컴포넌트 (swift)
[iOS - swift] 5. long press gesture와 애니메이션 - gesture 도중 화면 끝으로 가면 자동으로 스크롤되는 기능
jake-kim 2023. 8. 4. 23:501. long press gesture와 애니메이션 - 드래그 구현 방법 (snapshotView, CGAffineTransform)
2. long press gesture와 애니메이션 - 드래그할때 다른 뷰 줄어들고, 해당 뷰 크게하기 (UIView.animate, CGAffineTransform, concatenating)
3. long press gesture와 애니메이션 - 드래그와 cornerRadius, shadow 효과 (CABasicAnimation)
4. long press gesture와 애니메이션 - UIStackView에 DragDrop 적용 (DragDropStackView 구현)
5. long press gesture와 애니메이션 - gesture 도중 화면 끝으로 가면 자동으로 스크롤되는 기능 구현 (#Horizontal Scroll, #수평 스크롤)
long press 도중 자동스크롤 구현
- 이전 포스팅 글에서는 UIScrollView + UIStackView로 long press하여 아이템을 이동하는 기능을 구현
- 이번 포스팅 글에서는, long press하여 아이템을 이동할 때 화면 끝으로 갔을 때 자동으로 스크롤 되도록 하는 기능 구현이 목표]
구현 아이디어
- long press의 changed 이벤트에서 UILongPressGestureRecognizer의 location(in:)을 사용하면 이동된 좌표를 구할 수 있는데, 이 좌표사용하여 화면 왼쪽, 상단에 닿았는지 확인이 가능
- UIScrollView를 가지고 있는 ViewController에서 화면 양쪽 끝에 가까워졌다는 이벤트를 받으면 그만큼 scrollView를 setContentOffset()으로 스크롤 이동하면 완료
구현
- drag & drop하면서 ViewController쪽에 이벤트를 던져줄 Delegate에 드래그 관련된 필드 추가
- containerView: long press gesture의 .changed이벤트에서 location(in:)을 사용하여 화면 기준 x좌표를 구해야하는데, 이때 들어갈 뷰
- didDragOverMin: 왼쪽에 가까워진 경우 호출될 함수
- didDragOverMax: 오른쪽에 가까워진 경우 호출될 함수
protocol DragDropStackViewDelegate {
var containerView: UIView { get } // <-
func didBeginDrag()
func didDragOverMin(diff: Double) // <-
func didMove(fromIndex: Int, toIndex: Int)
func didDragOverMax(diff: Double) // <-
func didEndDrop()
}
- drag & drop의 config에 스크롤 해야하는 시점에 관한 값 추가
- thresholdRatioForDeviceWidth 필드: UIScreen.main.bounds.width에 곱하여 스크롤 되어야하는 기준값을 계산할때 사용
struct HorizontalDragDropConfig {
...
let thresholdRatioForDeviceWidth: Double
init(
...
thresholdRatioForDeviceWidth: Double = 0.9
) {
...
self.thresholdRatioForDeviceWidth = thresholdRatioForDeviceWidth
}
}
- long press의 .changed 이벤트에서 오른쪽으로 스크롤 하는 것인지, 왼쪽으로 스크롤하는 것인지 판단 한 후, 각 스크롤 처리
- 여기서 absolutedX를 구하여 right, left 스크롤 처리하는 함수에 각각 전달
func handleChanged(gesture: UILongPressGestureRecognizer) {
let newLocation = gesture.location(in: self)
let xOffset = newLocation.x - originalPosition.x
let translation = CGAffineTransform(translationX: xOffset, y: 0) // 0을 대입해야 영역 안에서만 드래깅 가능
guard let snapshotView else { return }
let scale = CGAffineTransform(scaleX: config.dargViewScale, y: config.dargViewScale)
snapshotView.transform = scale.concatenating(translation)
let maxX = snapshotView.frame.maxX
let midX = snapshotView.frame.midX
let minX = snapshotView.frame.minX
let absolutedX = gesture.location(in: delegate?.containerView ?? UIView()).x // <-
let index = arrangedSubviews
.firstIndex(where: { $0 == actualView }) ?? 0
if midX > pointForDragDrop.x {
handleChangedWhenDraggingRight(index: index, maxX: maxX, midX: midX, minX: minX, absolutedX: absolutedX)
} else {
handleChangedWhenDraggingLeft(index: index, maxX: maxX, midX: midX, minX: minX, absolutedX: absolutedX)
}
}
- left, right 스크롤 처리 함수에서 각각 threshold 값을 확인한 후 델리게이트로 이벤트 전달
func handleChangedWhenDraggingRight(index: Int, maxX: Double, midX: Double, minX: Double, absolutedX: Double) {
let thresholdX = UIScreen.main.bounds.width * config.thresholdRatioForDeviceWidth
if thresholdX < absolutedX {
delegate?.didDragOverMax(diff: absolutedX - thresholdX)
}
...
}
func handleChangedWhenDraggingLeft(index: Int, maxX: Double, midX: Double, minX: Double, absolutedX: Double) {
let thresholdX = UIScreen.main.bounds.width * abs(1 - config.thresholdRatioForDeviceWidth)
if absolutedX < thresholdX {
delegate?.didDragOverMin(diff: thresholdX - absolutedX)
}
...
}
- UIScrollView를 가지고 있는 ViewController에서 이 이벤트를 받아서, scrollView의 offset을 갱신하면 완료
- diff * 1.5를 해주는 이유는 스크롤이 자연스럽게 보이도록 휴리스틱하게 값을 설정한 것
func didDragOverMin(diff: Double) {
let lastOffsetX = scrollView.contentOffset.x
let scrollLeftOffsetX = lastOffsetX - diff * 1.5
guard 0 < scrollLeftOffsetX else { return }
scrollView.setContentOffset(.init(x: scrollLeftOffsetX, y: 0), animated: true)
}
func didDragOverMax(diff: Double) {
let lastOffsetX = scrollView.contentOffset.x
let scrollLeftOffsetX = lastOffsetX + diff * 1.5
guard scrollLeftOffsetX < scrollView.contentSize.width else { return }
scrollView.setContentOffset(.init(x: scrollLeftOffsetX, y: 0), animated: true)
}
(완성)
* 전체 코드: https://github.com/JK0369/ExHorizontalDragDropStackView
'UI 컴포넌트 (swift)' 카테고리의 다른 글
Comments