한참 SwiftUI에서 발생한 이슈중 하나
UIWindow를 통해 SwiftUIView를 또 하나 띄었을때 위로 띄운 윈도우의
View가 터치가 되지 않는 이슈가 있었습니다.
저도 이글을 보고 문제를 해결했었는데
꽤 재밌는 문제가 생겨서 공유해 보고자 합니다.
네. 제목 그대로 직접만든 Path를 clipShap를 하게 되면
다시 터치가 되지 않는 이슈가 발생합니다.
final class UIPassthroughWindow: UIWindow {
private var encounteredEvents = Set<UIEvent>()
override final func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
// If we don't have a root controller or it does not have a view we are done and can exit
guard let rootViewController, let rootView = rootViewController.view else { return nil }
guard let event else {
assertionFailure("hit testing without an event is not supported at this time")
return super.hitTest(point, with: nil)
}
// We next check the base implementation for a hitView, if none is found we are done
guard let hitView = super.hitTest(point, with: event) else {
// defensive clearing of encountered events
encounteredEvents.removeAll()
return nil
}
defer {
print (encounteredEvents)
print(rootView.layer.hitTest(point)?.name)
}
if encounteredEvents.contains(event) {
encounteredEvents.removeAll()
return hitView
} else if #available(iOS 26, *), rootView.layer.hitTest(point)?.name == nil {
encounteredEvents.insert(event)
return hitView
} else if hitView == rootView {
return nil
} else if #available(iOS 18, *) {
encounteredEvents.insert(event)
return hitView
} else {
return hitView
}
}
}
iOS 26 코드만 요약하면
터치 좌표의 뷰중에서 Layer에 이름이 달려 있지 않다면, 터치할 뷰다.
라는 이야기 입니다.
네, 현재 SwiftUI 에서 UIWindow를 다루는 행위는 사실 도박에 가깝습니다.
왜일까요?
A couple people have asked what implementation details were being depended on which has now resulted in broken behavior in the implementation.
Mainly, the assumption that, when you embed a SwiftUI view in a UIHostingController, the SwiftUI Views are represented as a UIView hierarchy. That behavior is not guaranteed for any particular SwiftUI View type, and should not be depended on.
There are also assumptions about how UIHostingController's view implements hitTest, which is also not guaranteed.
So, again, my recommendation for anyone interested in implementing this sort of behavior is to please file an enhancement request using Feedback Assistant.
--Greg
몇몇 분들이 구현 세부 사항에 의존한 결과 현재 구현에서 오류가 발생하고 있다고 문의하셨습니다.
주요 원인은 SwiftUI 뷰를 UIHostingController에 임베드할 때 SwiftUI 뷰가 UIView 계층 구조로 표현된다는 가정입니다.
이 동작은 특정 SwiftUI 뷰 유형에 대해 보장되지 않으며, 의존해서는 안 됩니다.
또한 UIHostingController의 뷰가 hitTest를 구현하는 방식에 대한 가정도 존재하는데, 이 역시 보장되지 않습니다.
따라서, 이러한 동작을 구현하려는 분들께 다시 한번 권고드립니다: Feedback Assistant를 통해 기능 개선 요청을 제출해 주시기 바랍니다.
그렇다면 다른 대안이 있을까요?
저도 찾아보고 있지만 현재 마땅한 방법이 없어서
아래와 같은 방법으로 문제를 일단 막을 수는 있습니다.
func asRoundedCorner(radius: CGFloat, corners: UIRectCorner) -> some View {
self
.clipShape(RoundedCornerShape(corners: corners, radius: radius))
.background {
Color.black.opacity(0.000001)
}
위와같이 직접만든 Shape등 터치가 안될때 컬러를 뒤에 두어주면 터치가 또 됩니다. 놀랍죠?
struct WindowTouchView: UIViewRepresentable {
func makeUIView(context: Context) -> UIView {
let view = UIView()
view.backgroundColor = .clear
return view
}
func updateUIView(_ uiView: UIView, context: Context) {
guard let layer = uiView.layer.sublayers?.first else { return }
layer.frame = uiView.bounds
}
}
func asRoundedCorner(radius: CGFloat, corners: UIRectCorner) -> some View {
self
.clipShape(RoundedCornerShape(corners: corners, radius: radius))
.background(WindowTouchView())
}
위와 같이 UIView를 달아주니 또 정상동작 합니다.
다만, SwiftUI 에서 UIKit 를 이런 경우에도 달아주는게 성능상이나, 구조적으로
보기 않좋은 부분이라 생각이 듭니다.
한참 이슈가 많은 우리 iOS26 참 재밌는 것 같습니다.
하루 빨리 다른 대안이나, 새로운 API 라도 iOS 측에서 제공해주길 바랄 뿐입니다.
여담 - Reactor Kit + AlarmKit 과, WebRTC 중에 무엇이 먼저 나갈지 고민입니다.
감사합니다.