[TIL] Responder Chain 유저의 인터랙션 처리에 관해

rbw·2022년 9월 4일
0

TIL

목록 보기
42/99

Responder Chain (앱은 유저의 인터랙션을 어떻게 처리하는가 ?)

https://onemoonstudio.tistory.com/8?category=845836 블로그 게시글 내용을 제게 맞게 정리해서 올린 글이라 자세한 내용은 이 블로그 주소로 들어가면 되겠습니당

UIResponder

"이벤트에 반응하고 다룰 수 있도록 하는 추상 인터페이스"

이것의 인스턴스를 Responder Object라고 부릅니다. UIKit app에서 이벤트를 다루는 근간으로 이루어져있습니다. UIApplication, UIViewController, UIView 같은 핵심 객체들 또한 리스폰더 입니다.

이벤트가 발생시 UIKitresponder object로 이벤트를 전달합니다.

특정한 이벤트를 다루기위해 상응하는 메서드를 override 해야하고, 예시로 터치를 다루기 위해서는 touchesBegan, touchesEnded 등을 재정의 해야 합니다.

이벤트를 다루는 것 외에도, 리스폰더는 다루지 못한 이벤트를 앱의 다른 파트로 넘길 수 있습니다. 들어온 이벤트를 처리하지 않는다면, responder chain을 따라서 다음으로 넘깁니다.

UIKit은 리스폰더 체인을 동적으로 관리하는데, 사전에 정의된 룰에 따라서 어떤 오브젝트가 next가 되어서 이벤트를 받을지 결정 합니다. 예로, viewsuperview로, rootviewvc로 진행 됩니다.

기본적으로 리스폰더는 UIEvent를 처리하지만, input view를 통해 커스텀 인풋을 받을 수도 있습니다. 예로, UITextField를 유저가 누르는 경우, first responder가 되면서 inputview(시스템 키보드)를 보여줍니다. 이와 비슷하게 custom view를 직접 만들어, 리스폰더가 active 되었을 때 보여주는 식으로도 가능하다.

UIEvent

"앱에서 하나의 유저 인터랙션을 설명하는 객체"

앱은 터치, 모션, 리모트 컨트롤 같은 여러타입에 이벤트를 받습니다. 이러한 이벤트는 타입과 서브 타입의 프로퍼티를 통해서 타입을 결정할 수 있습니다.

실제로 타입에는 터치, 모션, 리모트 컨트롤, 프레스 등이 있고, 서브타입에는 모션, 리모트 컨트롤의 세분화 되어 분류된것을 확인할 수 있습니다.

터치 이벤트는 해당 이벤트와 관련된 터치를 하나 이상 가지고 있고, 각 터치는 UITouchObject입니다. 이 이벤트가 발생시, 시스템은 적절한 리스폰더를 찾아서 적절한 메소드를 실행시킵니다.

멀티 터치 관계에서는 UIKit이 동일한 UIEvent객체의 터치데이터를 업데이트 하면서 재사용합니다.

해당 객체를 가지고 있어서는 안되고, 만약에 갖고 있어야 한다면, 값을 복사하여 들고 있어야 합니다.

UIEvent란 여러개의 터치를 분석하거나, 유저 인터랙션의 대해서 타입을 분류해 전달 되는 것이라 볼 수 있습니다.

UIControl

"유저 인터랙션을 통해 특별한 액션과 의도를 갖춘 컨트롤을 관리하는 클래스"

UIButton, UISlider를 포함 이 때 컨트롤은 target-action mechanism을 이용하여 앱과 상호작용 합니다.

예로, 슬라이더의 경우, 유저가 드래그를 통해 값을 변경하는 UX를 정리해 놓은 것이라고 볼 수 있고, UI를 통해 구현한 것이 UIControl이라고 설명이 가능하다.

만약 커스텀 이벤트 컨트롤이 필요한 경우에는 이를 서브클래싱 하는 것이 좋다. 확장이 필요시, 이미 존재하는 control class를 상속받자.

Controls state`를 통해서 컨트롤의 외관과 유저 인터랙션을 지원하는 것이 바뀐다. 이 상태에 따라서 컨트롤은 여러 상태중 하나의 상태를 가진다.

The Target-Action Mechanism

"앱을 컨트롤하기 위한 코드를 단순화 시켜준다"

터치이벤트를 쫓아다니면서 코드를 작성하는 대신, control-specific events(TouchUpInside 같은 것들)에 대해서 action method만 정리하면 된다.

액션 메소드를 컨트롤에 추가하기 위해서는, 액션 메소드, addTarget(_:action:for:) 메소드를 정의한 객체를 명시해야 한다.

타겟 오브젝트는 어떠한 것도 가능하다. 하지만 전형적으로 컨트롤을 포함하는 뷰컨트롤러의 루트뷰가 된다. 타겟 오브젝트를 nil로 설정한다면, 컨트롤은 리스폰더 체인을 따라서 액션 메소드를 정의한 객체를 찾아 나선다.

예시로 하나 보자면

@IBAction func doSomething(sender: UIButton, forEvent event: UIEvent) {} 

위 코드에서 sender는 액션 메소드를 호출한 control이고, eventcontrol-related를 발생 시키는 이벤트가 된다.

시스템은 컨트롤이 유저와 특정한 방식으로 인터랙션을 할 때 액션 메소드를 발생시킨다.

UIControl 이란 특정한 유저 인터랙션을 도와주는 UIView이다

Using responders and responder chain to Handle Events

앱은 리스폰더를 통해서 이벤트를 받고 처리한다. 리스폰더는 UIResponder class의 어떠한 인스턴스도 될 수 있고, UIView, UIViewController, UIApplication이 그 예시이다.

리스폰더는 row event data를 받게 되면 반드시 이를 처리하거나 다른 리스폰더로 전달해야만 한다.

이벤트를 받으면 UIKit은 자동으로 가장 적절한 리스폰더를 찾아 전달하는데 이를 First Responder라고 한다.

이벤트 전달 기본 흐름으로, view -> superview -> ... -> rootview -> vc -> window -> UIapplication -> Appdelegate 로 보면 된다.

Determining an Event`s First Reponder

UIkit은 이벤트 유형에 따라, 이벤트의 First Responder를 지정한다. (정해져 있는 듯 함)

accelerometers, gyroscopes, magnetometer 같은 모션 이벤트는 리스폰더 체인을 따르지 않는다. 대신 코어 모션이 특정한 객체로 이벤트를 전달한다.

UIControl도 결국 UIView의 서브 클래스로서, 리스폰더이며, 액션 메시지를 처리하기 위해서 리스폰더 체인을 사용하는 것으로 확인된다.

Gesture Recognizer는 터치와 누르는 이벤트를 뷰보다 먼저 받는다. 만약 얘가 연속되는 터치 이벤트를 받지 못한다면, UIKit은 이를 뷰로 전달한다.

만약 뷰가 터치를 처리하지 못한다면, UIKit은 리스폰더 체인을 따라서 터치이벤트를 전달한다.

Determining which responder contained a Touch Event

"UIKit은 터치 이벤트가 어디에서 발생 했는지를 확인하기 위해서 view-based hit-testing을 사용한다""

개인적으로 가장 이해가 잘되고 알고 싶었던 부분

UIKittouch locationview hierarchy에 있는 뷰 객체의 바운드를 비교한다.

UIViewhitTest(_:with:) 메소드는 뷰 계층(view hierarchy)을 순회하며 특정 터치를 포함하는 가장 깊은 서브뷰를 찾으며, 해당 뷰는 터치 이벤트의 First Responder가 된다.

hitTest는 히든이거나, 비활성화거나, 투명도값이 0.01 미만이면 해당 뷰를 무시한다

만약 터치 이벤트가 뷰의 바운드 영역을 벗어나면, hitTest는 해당 뷰 그리고 해당 뷰의 모든 서브뷰들을 무시한다. clipsToBoundsfalse인 경우, 뷰 바운드 바깥에 있는 서브 뷰들은 터치를 포함하고 있더라도 리턴이 되지 않는다.

터치 이벤트가 발생시, UIKit은 터치 객체를 만들어 뷰와 연결시킨다. 터치의 위치가 바뀌거나, 다른 파라미터가 변경된다면, UIKit은 동일한 터치 객체를 새로운 정보로 업데이트 한다. 오로지 뷰만 변경되지 않는다 터치가 끝나면 UIKit은 터치 객체를 릴리즈한다.

터치이벤트가 전달이 안되는 예로, 레이아웃이 깨진 경우를 찾아볼 수 있다. 터치로케이션이 뷰의 바운드 영역에서 벗어나게 되면 해당 뷰와 모든 서브뷰를 무시하므로, first respondernil이 되면서 터치 이벤트의 전달이 되지 않는 것이다.

Altering the Responder Chain

리스폰더의 next 프로퍼티를 오버라이드해서, 리스폰더 체인을 변경할 수 있다. 이를 할 때, next responder는 리턴하는 값이 된다.

예시로 살펴보자면

  1. UIView가 뷰 컨트롤러의 루트 뷰라면, next responder는 뷰 컨트롤러가 된다. 루트 뷰가 아니라면 슈퍼 뷰가 된다.

  2. UIViewController의 뷰가 window의 루트 뷰라면, next responderwindow가 된다. 만약 뷰 컨트롤러가 다른 뷰 컨트롤러에 의해서 띄워졌다면, next responderpresenting viewcontroller가 된다.

  3. UIWindow에서 다음 응답자는 UIApplication이다.

  4. UIApplication에서 딜리게이트가 UIResponder의 인스턴스이고, view, vc, app이 아닌 경우에만 다음 응답자가 appdelegate가 된다.

리스폰더 체인은 리스폰더로 이루어진 연결리스트 이다. 리스폰더는 next라는 프로퍼티가 존재. 이를 통해 다음 리스폰더를 찾을 수 있다.

요약

  • 유저의 인터랙션이 발생 -> UIEvent 생성
  • UIKit은 이 이벤트를 처리할 가장 적절한 Responder를 찾음
  • first responder가 이벤트를 처리할 수도 있지만, 이벤트를 처리하지 못하는 경우 responder의 특성에 따라 리스폰더 체인을 통해 이벤트를 처리 가능한 리스폰더를 찾는다. (처리 또는 무시)

아직 이해가 잘 안되는 부분이 있어서 더 읽어보고, 찾아봐야 할둣

profile
hi there 👋

0개의 댓글