FirstResponder, Responder chain

Responder Chain

Responder란 터치 이벤트를 처리할 수 있는 객체를 의미합니다. 터치 이벤트가 발생할 경우 iOS는 터치이벤트가 발생한 지점의 최상위 뷰를 찾습니다. 최상위 뷰를 First Responder이라 하는데 만약 First Responder가 이벤트를 처리하지 못할경우 최상위 뷰에서 하위뷰로 파고들며 이벤트를 처리 가능한 Responder를 찾게 되는데 이 과정을 Responder Chain이라 합니다.

First Responder

First Responder는 hitTest(_:with) 메서드로 찾는습니다. 이 메서드는 터치 이벤트가 발생했을 시점에 최상위 뷰의 hitTest가 호출될때까지 UIWindow부터 터치된 지점의 subView들의 hitTest 메서드를 순차적으로 호출됩니다. 만약 더이상 터치한 지점의 뷰에서 subView가 존재하지 않을경우 return self를 하며 해당 뷰가 First Responder가 됩니다. 

hitTest는 다음과 같은 매커니즘으로 작동합니다.

override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
    if !isUserInteractionEnabled || isHidden || alpha <= 0.01 {
        return nil
 
    }
 
    if self.point(inside: point, with: event) {
        for subview in subviews.reversed() {
            let convertedPoint = subview.convert(point, from: self)
            if let hitTestView = subview.hitTest(convertedPoint, with: event) {
            
                return hitTestView
            }
        }
        return self
    }
    return nil
}

 

한줄로 정리하자면 FirstResponder는 해당 이벤트를 처리할 객체를 상단(기기쪽)부터 찾는것이고 ResponserChain은 해당 이벤트를 처리할 Responder를 하단(사용자쪽)부터 찾는 것입니다. 

 

예제

화면에서 현재 화살표가 가리키는곳을 터치 했을때 아래와 같은 방법으로 FirstResponder를 찾습니다.

  1. 최상단의 View의 hitTest가 실행
  2. 터치된 위치가 내부에 포함되는지 체크
  3. 내부에 포함되므로 해당 뷰의 서브뷰의 역순으로 탐색
  4. View의 point를 BlueView의 좌표로 변환
  5. 터치된 위치가 BlueView에 포함이 되는지 체크
  6. 포함이 되지 않으므로 nil반환
  7. View의 point를 GreenView의 좌표로 변환
  8. 터치된 위치가 GreenView에 포함이 되는지 체크
  9. 포함이 되지 않으므로 nil반환
  10. View의 subView 모두 nil이 반환되므로 해당 이벤트의 firstResponder는 View가 됨