문제 상황
UIKit을 사용하여 UIScrollView 내부에서 터치 이벤트(touchesBegan
)가 호출되지 않는 이슈가 발생할 수 있다. 특히, UITextField나 UITextView가 포함된 ScrollView에서 키보드를 닫기 위해 터치 이벤트를 감지하려 할 때 이 문제가 종종 발생한다.
문제의 원인:
UIScrollView
는 기본적으로 터치이벤트를 인터셉트(Intercept)하여 스크롤 동작을 우선 처리한다.따라서,
touchesBegan(_: with:)
같은 일반적인 터치 이벤트가 호출되지 않을 수 있다.키보드를 닫으려면 터치 이벤트를 인식해야 하는데, 이 과정에서 문제가 발생한다.
이 문제를 해결하기 위해 UITapGestureRecognizer를 활용하여 터치 이벤트를 ScrollView에서 감지하는 방법을 사용할 수 있다.
해결 방법
다음과 같이 UITapGestureRecognizer
를 사용하여 ScrollView에서 터치 이벤트를 감지하고, 키보드를 닫을 수 있도록 설정할 수 있다.
private func setupTapGesture() {
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard))
tapGesture.cancelsTouchesInView = false // 터치 이벤트를 다른 UI 요소로 전달
scrollView.addGestureRecognizer(tapGesture)
}
@objc private func dismissKeyboard() {
view.endEditing(true) // 키보드 닫기
}
UITapGestureRecognizer
를scrollView
에 추가하여 터치를 감지한다.cancelsTouchesInView = false
설정을 통해 터치 이벤트가 다른 UI 요소에도 전달되도록 한다.endEditing(true)
를 호출하여 현재 활성화된 키보드를 닫는다.
cancelsTouchesInView = false
의 역할
이 코드에서 가장 중요한 부분은 cancelsTouchesInView = false
설정이다.
tapGesture.cancelsTouchesInView = false
cancelsTouchesInView = true
(기본값)
터치 이벤트가 GestureRecognizer에서 처리된 후, 다른 UI 요소로 전달되지 않는다.
즉, 터치를 감지할 수 있지만 ScrollView의 터치 이벤트(
touchesBegan
)가 호출되지 않음.
cancelsTouchesInView = false
GestureRecognizer가 터치 이벤트를 가로채지 않고, 다른 UI 요소에도 전달한다.
따라서
touchesBegan(_:with:)
과 같은 기본 터치 이벤트가 호출될 수 있다.
이 설정을 적용하면 사용자가 ScrollView를 터치했을 때, 터치 이벤트가 정상적으로 동작하며 동시에 키보드도 닫을 수 있다.
추가적인 고려 사항
터치 이벤트를 더 명확하게 처리하고 싶다면?
gestureRecognizer(_:shouldRecognizeSimultaneouslyWith:)
을 구현하여 여러 제스처가 동시에 동작할 수 있도록 조정할 수 있다.func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { return true }
UIScrollView 내부의 특정 영역에서만 키보드를 닫고 싶다면?
tapGesture
를 추가할 때 특정 뷰에서만 동작하도록 설정할 수 있다.tapGesture.delegate = self
SwiftUI에서 같은 기능을 구현하려면?
SwiftUI에서는
onTapGesture
를 사용하여 비슷한 기능을 구현할 수 있다.ScrollView { VStack { TextField("입력", text: $text) .textFieldStyle(RoundedBorderTextFieldStyle()) } } .onTapGesture { UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) }