게다가 아이폰에서는 적용되는데, 동작하지 않는 것이 아이패드 뿐이라면..!
디스플레이에 touch down 이벤트가 입력되면 터치된 뷰에서부터 가장 먼 뷰를 찾아서 반환하는 메서드이다.
func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if self.hidden
|| !self.userInteractionEnabled
|| self.alpha < 0.01
|| ![self pointInside:point withEvent:event]
|| ![self _isAnimatedUserInteractionEnabled] {
return nil
}
if /* point is in our bounds */ {
for /* each subview, in reverse order */ {
let hitView = /* recursive call on subview */
if hit view != nil {
return hitView
}
}
return self
}
return nil
}
수도로는 이렇게 구현되어 있다. 대충 뷰가 가진 프로퍼티에 대해 합리적인 조건을 검사한 다음 DFS 방식으로 뷰를 찾아서 반환한다.
hitTest에 대한 WWDC14 영상이 공홈에는 공개되어있지 않은 것 같은데 여기저기 찾아보면 영상 링크를 찾을 수 있다고 한다. (공유받아서 링크가 없다...!) 대신 wwdc note 도큐먼트는 여기 🔗
팀에서는 특정 뷰가 hit되지 않도록 이 메서드를 오버라이드해 사용하는데, 내가 하려던 것은 아래의 그림과 같이 A 윈도우 위에 B를 배치하는 상황에서 B가 아닌 영역의 인터랙션은 A에서 동작하는 것이다.
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let hitView = super.hitTest(point, with: event)
if hitView == self {
return nil
}
return hitView
}
이렇게 오버라이드해서 갖다쓰면 아이폰에서는 아주 잘 동작하지만 아이패드에서는 B's UIWindow만 인터랙티브했다 .. A는 어디를 눌러도 전혀 반응이 없었다. 디버깅을 하루종일 하고서야 무슨 차이가 있는지 알 수 있었는데
아이패드는 뷰 hierarchy에 UIView 하나가 껴있다. hitTest에서 윈도우가 아닌 이 UIView를 자꾸 반환해서 제대로 적용이 안되었던 것이다. 해결한 방법은 히트된 영역이 window(포함)와 viewController 사이에 포함되어 있는지를 반환하도록 수정했다. 사진에서 빨간 영역에 포함되는지를 반환하는 것이다.
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let hitView = super.hitTest(point, with: event)
if hitView == self {
return nil
}
guard let hitView, let rootViewController else { return hitView }
if self.contains(hitView) && !rootViewController.contains(hitView) {
return nil
}
return hitView
}
이런 식으로 수정해주면 문제를 해결할 수 있따! 이와 관련된 자료를 단 하나도 찾을 수가 없어서 요게 정석인지는 정말 모르겠다. 일단 UIWindow를 여러개 사용한다거나 윈도우의 hitTest를 오버라이드해서 사용하는 것도 흔치않은 사례라고 하니...
디버깅하면서 뷰들의 주소값으로 어떤 뷰인지 알아내거나, 기기별로 뷰 계층구조의 차이가 있을 수 있다는 것을 알게 되었다~ 계층 구조간 UIView 하나만큼의 차이가 발생한 이유도 정확히는 모르겠지만 아이패드가 멀티 씬을 지원하기 위해 존재하는 UIView가 아닐까까지 추측하는 것으로 오늘의 레슨
끗-🧞