This code worked for me.
The benefit here is that it uses the default behavior of the hitTest on iOS. It checks the alpha of the point, and also other conditions like whether the hit view is interactive and whether the rootViewController.view has subviews.
The alpha point check was inspired by the swiftui-window-overlay by sunghyun-k.
enum PassThroughWindowType {
case ignoreAllTouches
case allowInteractiveTouches
}
class PassThroughWindow: UIWindow {
let type: PassThroughWindowType
init(windowScene: UIWindowScene, type: PassThroughWindowType) {
self.type = type
super.init(windowScene: windowScene)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
guard type == .allowInteractiveTouches else { return nil }
if #available(iOS 18, *) {
// In iOS >= 18 versions, prevent rootViewController.view by check the alpha Of point
guard let rootView = rootViewController?.view,
let hitView = super.hitTest(point, with: event) else { return nil }
// Avoid hit testing if there are no subviews or if view is non-interactive
guard !rootView.subviews.isEmpty, hitView.isUserInteractionEnabled, !hitView.isHidden, hitView.alpha > 0 else { return nil }
// if hitView == rootViewController.view && the alpha at the point < 0.01 .. then prevent rootViewController.view from capturing the hit
if hitView == rootView, alphaOfPoint(in: rootView, at: point) < 0.01 {
return nil
}
return hitView
} else {
// In older iOS versions, prevent rootViewController.view from capturing the hit
guard let hitView = super.hitTest(point, with: event) else { return nil }
return rootViewController?.view == hitView ? nil : hitView
}
}
private func alphaOfPoint(in view: UIView, at point: CGPoint) -> CGFloat {
var pixelData: [UInt8] = [0, 0, 0, 0]
guard let context = CGContext(
data: &pixelData,
width: 1,
height: 1,
bitsPerComponent: 8,
bytesPerRow: 4,
space: CGColorSpaceCreateDeviceRGB(),
bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue
) else {
return .zero
}
context.translateBy(x: -point.x, y: -point.y)
view.layer.render(in: context)
return CGFloat(pixelData[3]) / 255.0
}
}
Topic:
UI Frameworks
SubTopic:
General
Tags: