본문 바로가기

iOS

UIResponder

728x90

Responder Chain이 무엇일까?

우리가 처음 xcode 프로젝트를 만들면 생기는 파일 중 어쩌면 가장 중요하다고 할 수 있는 파일인 AppDelegate와 SceneDelegate를 보면 접할 수 있다.

이 두개의 클래스 모두 동일하게 UIResponder를 상속 받는다.

그렇다면, 먼저 UIResponder가 무엇인지 살펴보자.

 

UIResponder는 An abstract interface for responding to and handling events. 이다. (뭔말이야)

이벤트에 응답 및 처리를 위한 추상 인터페이스 .. 이다.

 

Responder 객체는 UIResponder의 인스턴스이다.

더보기

Responder 객체가 뭔데 ..

 

Responder는 이벤트를 처리하고 반응할 수 있는 객체이다.

그러므로 모든 Responder 객체는 UIResponder에서 상속된 클래스들의 인스턴스이다.(= UIResponder 클래스의 인스턴스들을 Responder 객체라고 한다.)

대표적으로 UIVIew, UIView의 서브 클래스인 UIWindow, UIViewController, UIApplication 등이 있다.

그리고 해당 인스턴스가 UIKit에서 이벤트 처리의 중추 역할을 한다. UIKit의 많은 객체들, 그러니까 UIApplication, UIViewController, UIView와 같은 객체들이 모두 Responder이다. 이들은 이벤트가 발생하게 되면 이벤트 Responder 객체에게 전달해서 특정한 역할을 처리한다.

 

여기서 말하는 이벤트 발생은 다음과 같다.

화면을 터치하거나 제스처를 취하는 등의 다양한 이벤트가 있다. 어떤 특정한 이벤트를 처리하려면 반드시 해당 이벤트에 대한 메서드를 Responder 객체가 오버라이드 해서 내용을 구현한다.

UIViewController와 UIView에서 touchsBegan, touchsMoved와 같은 메서드를 정의할 수 있는 이유도 모두 객체들(뷰컨과 뷰가)이 Responder이기 때문이다. 

그러므로, 터치 이벤트가 발생하면 responder 객체는 UIKit에서 제공하는 정보를 기반으로 앱의 인터페이스를 업데이트 한다.

 

단순히 이벤트만 처리하는 것이 아니다.

이벤트 처리와 함께 UIKit의 responder들은 자신이 처리하지 않은 이벤트를 앱의 다른 요소들에게 전달하는 역할도 수행한다.

더보기

이벤트를 처리 또는 전달 .. ?

 

Responder는 이벤트를 전달 받았을 시, 반드시 그것을 처리하거나 다른 Responder 객체로 넘겨주어야 한다.

앱이 이벤트를 전달 받았을 때, UIKit는 자동으로 이벤트를 처리하기에 가장 적절한 Responder 객체로 이벤트를 전달한다.

 

그리고 이 때, 이벤트를 처리하기에 가장 적절한 Responder 객체를 First Responder라고 한다. 

만약, 어떤 responder가 이벤트를 처리하지 않으면 이 객체가 Responder Chain의 다음 Responder에게 이벤트를 전달한다. 이 체인은 UIKit에 의해 미리 정해진 규칙에 따라 동적으로 관리된다.

예를 들어서, View는 자신의 SuperView에게 이벤트를 전달하고 최상위에 있는 View는 UIViewController에게 이벤트를 전달한다.

 

Responder 객체들은 UIEvent 객체들을 처리하지만 InputView를 통해서 사용자에게 입력을 받을 수 있다. 

가장 대표적인 사용자 커스텀 입력의 예시는 키보드이다. 사용자가 화면에 있는 UITextField와 UITextView를 탭하면 해당 뷰는 First Responder가 되어서 입력뷰(키보드)를 표시한다. 이처럼 개발자는 커스텀 입력뷰를 만들어서 어떤 responder 객체가 활성화되면 화면에 표시되도록 할 수 있다.

 

더보기

커스텀 입력뷰를 활용한 버튼

 

// 커스텀 입력창을 이용하면 input view가 있는 버튼을 구현할 수 있다.
// 해당 버튼을 누르면 날짜를 입력할 수 있는 inputview가 화면에 나타난다.

import UIKit

class RespondingButton: UIButton {
    
    // MARK: - Properties
    
    private var myView: UIView? = UIView()
    private var toolBarView: UIView? = UIView()
    
    override var inputView: UIView? {
        get {
            myView
        }
        
        set {
            myView = newValue
        }
    }
    
    override var inputAccessoryView: UIView? {
        get {
            toolBarView
        }
        set {
            toolBarView = newValue
        }
    }
    
    override var canBecomeFirstResponder: Bool {
        return true
    }
    
    // MARK: - Initializers
    
    override init(frame: CGRect) {
        super.init(frame: frame)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }  
}

 

UIResponder

짧게 한 문장으로 말하자면 이벤트를 처리하는 것이다.

  • UIResponder는 이벤트가 발생하면 처리해서 내용을 구현하는 일을 한다.
  • 이벤트를 처리하는 것 뿐만 아니라 자신이 처리하지 않는 이벤트는 Responder Chain 규칙에 의해서 다음 Responder에게 이벤트를 전달하는 역할도 한다.
  • input view를 통해서 커스텀 입력을 받을 수 있다.

 

Responder Chain

앞에 말한 것처럼 처리 되지 않은 이벤트에 대해서는 다른 Responder로 이동한다. 이 때 Active Responder Chain을 따라서 이동한다. 그리고 Responder Chain은 앱의 Responder 객체에 따라서 동적으로 구성된다.

 

아래 그림은 UILabel, UITextField, UIButton 그리고 두 개의 백그라운드 View로 UI를 구성한 것이다.

여기서 UILabel, UITextField, UIButton UIView 모두 Responder이다.

 

그리고 그림에서 볼 수 있는 것처럼 화살표 방향으로 Responder 객체들이 Responder Chain을 구성한다.

(화살표 방향으로 처리되지 않은 이벤트들이 전달된다고 생각하면 된다.)

만약 UIButton이 전달받은 이벤트를 처리하지 않는다면 UIKit는 Button의 SuperView인 UIView로 우선 전달한다.

이 UIView에서도 이벤트를 처리하지 않는다면 UIView의 루트뷰인 두번째 UIView로 전달되고, 

여기서도 이벤트를 처리하지 않는다면, UIWindow .. 그리고 그 다음은 UIApplication .. 으로 연쇄적으로 전달된다.

그리고 최종적으로 UIApplication에서도 처리가 되지 않는 이벤트는 삭제된다.

 

아래의 메소드들은 우리가 주로 UITextField 또는 UITextView 등과 같이 키보드와 같은 input view를 다룰 때 많이 접한 메소드들이다.

여기서 정말 자주 사용하는 두가지는 다음과 같다.

  • becomeFirstResponder : window의 첫번째 responder로 만들기 위해서 사용
  • resignFirstResdoner : window의 최초 responder로서 상태를 양도하도록 요청 받았음을 알림 
    • 대표적인 예시로 UITextField에서 resignFirstResponder()를 호출하면 responder의 상태를 양도해야 하므로 키보드가 사라지게 된다.

 

First Responder

그렇다면, UIKit는 어떤 기준으로 first responder를 설정할까?

UIKit는 발생한 이벤트의 타입을 기준으로 first responder를 설정한다.

 

여기서 말하는 이벤트는 UIEvent의 인스턴스인데 여러 타입의 이벤트가 존재한다.

위에서 자주 발생하는 이벤트는 터치 이벤트이다.

  • press - 물리 키에서 발생하는 이벤트
  • remote control - 헤드폰 등 외부 기기에서 누르는 버튼에서 발생하는 이벤트
  • editing menu - 핸드폰에서 text를 짧게 혹은 길게 누를 때 뜨는 메뉴
  • shake motion - 흔들 때 발생하는 이벤트 (-> 이 이벤트의 경우 responder chain에서 처리하지 않고 core motion 객체로 전달한다.)

 

추가로, 어떤 Responder가 이벤트를 포함하고 있는지 결정하는 방법을 알아보자

UIKit는 뷰 기반의 hit-testing을 사용하여 터치 이벤트가 어디서 발생했는지 결정한다.

 

UIView의 hitTest(_:with:)메서드는 view 계층을 돌아다니면서 이벤트가 발생한 터치 지점을 포함하고 있는 view의 계층 가장 최하단 subview를 찾고 그것을 touch 이벤트의 first responder로 지정한다.

그리고 만약 터치 이벤트가 발생한 위치가 view의 bounds 밖에 있다면 hitTest(_:with:)는 해당 뷰와 서브 뷰를 모두 무시한다.

 

결과적으로 view의 속성 clipsToBounds가 false로 설정되어 있다면 view의 bounds를 벗어난 서브뷰에서 터치가 발생했더라도 hitTest는 view를 리턴하지 않는다.

 

더보기

'iOS' 카테고리의 다른 글

Custom Font를 적용하고 싶다면?  (0) 2022.08.01
ATS(App Transport Security)  (0) 2022.07.28
Delegate? DatsSource?  (0) 2022.07.28
UIViewPropertyAnimator  (0) 2022.04.29
Swift Package Manager Update  (0) 2022.04.26