이번에는 슬랙 따라잡기 기능을 보면서 앱에 적용하면 좋겠다 싶은 부분이 있어서 가져와봤습니다.

최근에 슬랙을 사용할 일이 있었는데, 여기에서 따라잡기 라고해서 카드 스와이프형태로 알림을 확인하는 기능이 있더라구요! 근데 이게 좌우로 스와이프할때 이게 적용되는 임계점에서 햅틱 피드백을 주는것을보고, 제 앱에서도 사진을 스와이프할때 햅틱이 필요했던 것을 뒤늦게야 알아차려서 이번에 적용해보려고 합니다!

앱이 ios26을 타겟으로하고 있어서 기존의 UIImpactFeedbackGenerator로 UiKit방식을 쓸필요는 없고, ios17부터 새로 추가된 swiftui방식인 .sensoryFeedback을 사용하려합니다.

.sensoryFeedback이란?
.sensoryFeedback(.impact(weight: .medium), trigger: someValue)
trigger로 지정한 값이 변할 때마다 햅틱이 자동으로 발생하게 할 수 있다. 심지어 이미 앱상에서 보류/삭제를 감지하는 임계점이 정의되어있어서 거기에 그냥 잘 녹여주면 끝이다!
카드 스와이프할때 임계점-threshold를
100pt로 되어있는데, 100만큼 넘겼으면 이제 손을떼도 이사람이 그 방향으로 100pt했으니 이건 보류/삭제하려는 의도다 하고 알아차리기 위함이다! 말만해서는 이해가 덜 될 수 있어서 좀 더 코드랑 함께 설명을 하자면..!
실제 드래그 감지하는 width를 쓰는 DragGesture이랑 handleSwipeEnd를 보면 대강 이해가 가능하다.
.gesture(
DragGesture()
.onChanged { gesture in
offset = gesture.translation
onSwipeChanged(gesture.translation.width)
}
.onEnded { gesture in
handleSwipeEnd(translation: gesture.translation, geometry: geometry)
}
)
private func handleSwipeEnd(translation: CGSize, geometry: GeometryProxy) {
let swipeDistance = translation.width // 드래그한 양
if abs(swipeDistance) > swipeThreshold {
withAnimation(.spring(response: 0.3, dampingFraction: 0.7)) {
offset = CGSize(
width: swipeDistance > 0 ? geometry.size.width * 1.5 : -geometry.size.width * 1.5,
height: translation.height
)
} completion: {
onSwipeChanged(0)
if swipeDistance > 0 {
onSwipeRight()
} else {
onSwipeLeft()
}
}
} else {
withAnimation(.spring(response: 0.3, dampingFraction: 0.7)) {
offset = .zero
}
onSwipeChanged(0)
}
}
이렇게 DragGesture에서 얼만큼 움직였는지가 이제 handleSwipeEnd메소드에 인자로 들어가서, treshold값을 넘기냐 안넘기냐로 판단해주고 있으니, 그냥 자연스럽게 추가해주면될것이다.
그래서 그냥 DragGesture에 잘 들어갈 수 있게 updateHapticTrigger을 추가해줬다.
@State private var hasTriggeredHaptic: Bool = false
private func updateHapticTrigger(translationWidth: CGFloat) {
let crossedThreshold = abs(translationWidth) >= swipeThreshold // 임계점 재활용
if crossedThreshold, !hasTriggeredHaptic {
hasTriggeredHaptic = true
} else if !crossedThreshold, hasTriggeredHaptic {
hasTriggeredHaptic = false
}
}
true -> 햅틱 반대면 reset근데이렇게하니까 보류/삭제 일때 햅틱이 잘 되는데, 또 문제는 내려놓았을때도 햅틱이 발생한다. 이벤트가 발생한게아닌데 햅틱을 주면 UX에도 혼선을 줄 수 있는 것이라서 condition클로저를 추가해서 진짜 이벤트가 발생했을때만 적용되게 해놨다.
.sensoryFeedback(.impact(weight: .medium), trigger: hasTriggeredHaptic) { old, new in
new == true // true로 변할 때만 햅틱발생시키자!
}
{ old, new in } 클로저로 이전값/새값을 받아서 원하는 조건일 때만 햅틱을 허용할 수 있어요. 덕분에 threshold 진입 시에만 딱 한 번 피드백이 오고, 복귀할 때는 조용하게 reset만 돼요.
old new 는 무슨 기준으로 나오는가?
trigger에 넣는 메소드 리턴값이 바뀔때마다 old -> new 로 값에 접근이 가능하다.
사실 따지면 코드 6줄? 정도만 추가한건데, 핵심 코어 기능에 햅틱이 추가된 것이다. UIImpactFeedbackGenerator도 그리 코드가 길지않긴한데, 무엇보다 새로 배워서 적용하기에 참 간단하다..
최신기술은 역시 최고..

잘 추가가되었다! 근데, 영상에서는 이 햅틱의 맛이 느껴지지 않아 아쉬울뿐..