1. UIPanGestureRecognizer 설정
private func setupPanGesture() {
panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePanGesture(_:)))
panGesture.delegate = self
addGestureRecognizer(panGesture)
}
2. 스와이프 액션 UI 구성
- 빨간색 배경: 삭제 액션을 나타내는 배경 뷰
- 삭제 아이콘: 휴지통 아이콘으로 삭제 의도를 명확히 표시
- 버튼 영역: 실제 삭제 동작을 처리하는 투명 버튼
private let swipeActionView = UIView().then {
$0.backgroundColor = .error
$0.layer.cornerRadius = Metrics.cornerRadius
$0.isHidden = true
}
private let deleteIconView = UIImageView().then {
let image = UIImage(named: "trash")?.withRenderingMode(.alwaysTemplate)
$0.image = image
$0.tintColor = .appWhite
$0.isHidden = true
}
3. 스와이프 제스처 처리 로직
제스처 상태별 처리
@objc private func handlePanGesture(_ gesture: UIPanGestureRecognizer) {
let translation = gesture.translation(in: self)
let velocity = gesture.velocity(in: self)
switch gesture.state {
case .began:
originalTransform = mainContentView.transform
case .changed:
guard translation.x <= 0 else { return }
let leftInset = frame.minX
let safeLeft = window?.safeAreaInsets.left ?? 0
let maxTranslation: CGFloat = -(80 + leftInset + safeLeft)
let clampedTranslation = max(translation.x, maxTranslation)
mainContentView.transform = CGAffineTransform(translationX: clampedTranslation, y: 0)
let shouldShowAction = translation.x < -25
if shouldShowAction != isSwipeActionVisible {
isSwipeActionVisible = shouldShowAction
swipeActionView.isHidden = !shouldShowAction
deleteIconView.isHidden = !shouldShowAction
}
case .ended, .cancelled:
if translation.x < -40 || velocity.x < -400 {
showSwipeAction()
} else {
resetSwipeAction()
}
}
}
4. 애니메이션 처리
삭제 액션 표시
private func showSwipeAction() {
UIView.animate(withDuration: 0.25, delay: 0, usingSpringWithDamping: 0.85, initialSpringVelocity: 0.6) {
let leftInset = self.frame.minX
let safeLeft = self.window?.safeAreaInsets.left ?? 0
self.mainContentView.transform = CGAffineTransform(translationX: -(80 + leftInset + safeLeft), y: 0)
}
swipeActionView.isHidden = false
deleteIconView.isHidden = false
}
원래 위치로 복원
private func resetSwipeAction() {
UIView.animate(withDuration: 0.25, delay: 0, usingSpringWithDamping: 0.85, initialSpringVelocity: 0.6) {
self.mainContentView.transform = .identity
}
deleteIconView.isHidden = true
isSwipeActionVisible = false
swipeActionView.isHidden = true
}
5. 삭제 확인 및 실행
셀에서 뷰컨트롤러로 이벤트 전달
cell.onDeleteTapped = { [weak self] in
guard let self = self else { return }
let timer = self.timers[indexPath.item]
self.showDeleteConfirmation(for: timer, at: indexPath)
}
삭제 확인 다이얼로그
private func showDeleteConfirmation(for timer: TimerModel, at indexPath: IndexPath) {
let presenter = navigationController ?? self
PodoAlertController.presentDeleteTimerAlert(
from: presenter,
title: "이 타이머를 삭제할까요?",
message: "삭제한 타이머는 복구할 수 없어요.",
cancelTitle: "취소",
confirmTitle: "삭제하기"
) { [weak self] in
self?.deleteTimer(timer, at: indexPath)
}
}
실제 삭제 실행
private func deleteTimer(_ timer: TimerModel, at indexPath: IndexPath) {
do {
try repository.delete(id: timer.timerID)
timers.remove(at: indexPath.item)
collectionView.deleteItems(at: [indexPath])
updateUI()
updateHeaderTotalFocusTime()
} catch {
PodoAlertController.presentErrorAlert(
from: presenter,
title: "삭제 실패",
message: "타이머 삭제 중 오류 발생"
)
}
}