on
내 인터렉션은 왜 어색할까?
내 인터렉션은 왜 어색할까?
어떤 UI는 이상하게 ‘어색하다’고 느껴질 때가 있다.
손가락은 분명히 움직이고 있는데, 화면은 마치 내 의도를 못 알아챈 것처럼 반응한다.
반대로 어떤 UI는 딱히 화려하지 않아도 이상하게 ‘자연스럽다’고 느껴진다.
나도 그게 궁금했다.
왜 이런 차이가 생기는 걸까?
여러 실험을 해보며 하나씩 직접 구현해봤고,
그 과정에서 ‘어색하다’는 감각 뒤에는 몇 가지 공통된 원인이 있다는 걸 알게 됐다.
이 글은 그 탐색의 기록이다.
🧩 내가 직접 겪은, 어색하게 느껴지는 인터랙션 3가지
1. “왜 갑자기 이래?” – Threshold 기반 전환의 부자연스러움
📌 내가 느낀 사용자 감각
화면을 아래로 천천히 당기고 있는데, 갑자기 화면이 “휙” 하고 닫혀버릴 때가 있다.
마치 내가 닫으려고 한 게 아닌데, 내 손길을 뺏어간 느낌이랄까.
“아직 내가 조작하고 있었는데, 왜 마음대로 닫혀?”
이건 내가 직접 테스트하다가 가장 자주 느낀 어색함 중 하나다.
⚠️ 어색함의 원인, 내 생각
많은 UI가 translation > 100
같은 임계값(Threshold)만을 기준으로 화면 전환을 트리거한다.
문제는 이 임계값을 넘는 순간, 내 의도와 상관없이 UI가 무조건 전환된다는 점이다.
이 방식은 흐름이나 맥락보다는 단순한 조건만을 본다.
그래서 사용자는 자신의 동작이 뚝 끊긴 것 같은 어색함을 느낀다.
✅ 내가 시도해본 개선
처음엔 나도 대부분의 예제처럼 translation.y > 150
같은 임계값으로만 화면을 닫았다.
// Before
@objc func handlePan(_ gesture: UIPanGestureRecognizer) {
let translation = gesture.translation(in: view)
if gesture.state == .changed {
if translation.y > 150 {
dismiss(animated: true)
}
}
}
그런데 이렇게 하면 손을 천천히 내리고 있을 때도 갑자기 화면이 사라진다.
사용자의 진행 의도나 속도를 전혀 고려하지 않기 때문이다.
이건 내가 테스트하다가 직접 느낀 부분이다.
그래서 아래처럼, 진행 중엔 화면이 손가락을 따라오게 만들고
마지막에는 이동 거리뿐 아니라 속도까지 함께 고려하도록 바꿔봤다.
// After
@objc func handlePan(_ gesture: UIPanGestureRecognizer) {
let translation = gesture.translation(in: view)
switch gesture.state {
case .changed:
view.transform = CGAffineTransform(translationX: 0, y: translation.y)
case .ended:
let velocity = gesture.velocity(in: view)
if translation.y > 100 || velocity.y > 500 {
UIView.animate(withDuration: 0.2, animations: {
self.view.transform = CGAffineTransform(translationX: 0, y: self.view.bounds.height)
}, completion: { _ in
self.dismiss(animated: false)
})
} else {
UIView.animate(withDuration: 0.2) {
self.view.transform = .identity
}
}
default:
break
}
}
이렇게 하면
- 손가락을 빠르게 flick하면 거리와 상관없이 자연스럽게 dismiss되고,
- 천천히 움직이면 화면이 내 손길을 계속 따라오다가,
- 마지막에만 판단해서 사라진다.
즉, “내가 조작하고 있다”는 감각이 끊기지 않는다.
Before
After
—
2. “더 세게 던졌는데 왜 똑같이 돌아와?” – 자연의 물리 법칙이 없는 UI
📌 내가 겪은 사용자 감각
상자를 멀리 던졌는데, 돌아오는 속도나 움직임이 항상 똑같다.
아무리 세게 밀어도, 항상 똑같은 반응. 뭔가 좀 이상하다.
“내가 더 세게 던졌는데, 왜 항상 똑같이 돌아오지?”
이런 순간, UI가 현실과 동떨어진 것처럼 느껴진다.
⚠️ 내가 발견한 문제의 본질
많은 UI 애니메이션은 항상 일정한 속도로 원래 자리로 되돌아온다.
하지만 실제 세상에서는, 멀리 밀었다면 더 세게 흔들리고, 가까이서 놓으면 부드럽게 돌아온다.
즉, 움직인 거리나 세기에 따라 반작용의 크기가 달라지는 게 자연스럽다.
그런데 UI는 이런 감각을 무시하고, 항상 똑같은 타이밍과 damping으로 돌아온다.
✅ 내가 시도해본 개선
// Before
UIView.animate(withDuration: 0.4, delay: 0, options: [.curveEaseOut], animations: {
self.box.center = CGPoint(x: 150, y: 300)
}, completion: nil)
이 방식은 아무리 멀리 던져도 항상 같은 애니메이션이 나온다.
내가 느끼기에, 이건 실제 손맛과 너무 다르다.
그래서 spring damping을 거리 기반으로 다르게 적용해봤다.
// After
@objc func handlePan(_ gesture: UIPanGestureRecognizer) {
let translation = gesture.translation(in: view)
switch gesture.state {
case .changed:
box.center = CGPoint(x: origin.x + translation.x, y: origin.y + translation.y)
case .ended, .cancelled:
let to = origin
let from = box.center
let delta = CGPoint(x: to.x - from.x, y: to.y - from.y)
let distance = hypot(delta.x, delta.y)
// 거리 기반으로 damping 계산
let normalizedDistance = min(distance / 300, 1.0)
let damping = 0.5 + (1.0 - normalizedDistance) * 0.5 // 거리가 짧으면 부드럽고, 멀면 더 통통 튐
UIView.animate(
withDuration: 0.4,
delay: 0,
usingSpringWithDamping: damping,
initialSpringVelocity: 0,
options: [],
animations: {
self.box.center = to
},
completion: nil
)
default:
break
}
}
이렇게 바꾸고 나니,
- 가까운 위치에서 손을 떼면 부드럽게 돌아오고,
- 멀리 끌었다가 놓으면 더 크게 흔들리며 돌아온다.
직접 구현해보니, 이런 물리적인 손맛?이 주는 느낌이 꽤나 좋았다.
즉 실제 세상처럼 “세게 밀면 더 튀고, 천천히 놓으면 부드럽게 돌아오는” 반응을 설계해야 한다.
Before
After
3. “닿긴 했는데, 왜 반응 안 해?” – ‘입력’만 보고 ‘의도’를 무시하는 UI
📌 내가 자주 느끼는 사용자 감각
버튼을 분명히 눌렀는데 아무 반응이 없다.
화면이 멀쩡한데, 제스처가 안 먹힌다.
“분명히 눌렀는데? 뭔가 고장났나? 내가 누른게 맞나?”
이럴 때, UI가 내 손끝의 의도를 전혀 이해하지 못하는 것 같다.
⚠️ 문제의 본질 (내가 생각한)
UIKit에서 흔하게 touchUpInside
만 처리하면,
버튼을 누르는 “순간”에는 아무런 감각이 없다.
즉, 입력만 보고, 의도나 조작 중이라는 피드백을 주지 않는다.
✅ 내가 바꿔본 방식
사용자가 버튼을 누른 “순간”, 정말 눌렸다는 확실한 피드백을 주는 게 중요하다고 느꼈다.
즉, 버튼을 누르고 있다는 것, 그리고 정상적으로 입력되었다는 감각을 즉각 전달해야 한다.
UIKit 예제
button.addTarget(self, action: #selector(touchDown), for: .touchDown)
button.addTarget(self, action: #selector(touchUp), for: [.touchUpInside, .touchCancel, .touchDragExit])
@objc func touchDown(_ sender: UIButton) {
UIView.animate(withDuration: 0.1) {
sender.transform = CGAffineTransform(scaleX: 0.95, y: 0.95)
}
let generator = UIImpactFeedbackGenerator(style: .light)
generator.impactOccurred()
}
@objc func touchUp(_ sender: UIButton) {
UIView.animate(withDuration: 0.2) {
sender.transform = .identity
}
}
이건 단순히 입력만 감지하는 게 아니라,
사용자에게 “눌렸다!”는 감각을 순간적으로 전달해준다.
내가 직접 써보니, 이런 작은 차이가 버튼의 신뢰감을 크게 바꾼다.
Before
After
🎥 데모
- 모든 예제는 전체 코드 참고
마무리: 자연스러움은 감각의 흐름을 끊지 않는 것
이건 직접 비교해보지 않으면 잘 안 느껴지는 감각이다. 내가 이걸 구현하면서 가장 크게 느낀 건, ‘조작하고 있다는 감각’이 사라지는 순간 UI가 이상해진다는 점이었다.
자연스럽다
는 느낌은 정교한 애니메이션이 아니라, 사용자의 리듬을 존중하고 끊김 없이 반응하는 흐름에서 나온다.
화려함보다 중요한 건, “내가 조작하고 있다”는 감각이 끊기지 않도록 만드는 것이다.