SwiftUI中如何让遮罩Overlay的镂空区域支持交互?
SwiftUI聚光灯遮罩的交互穿透实现方案
问题描述
我在SwiftUI中通过遮罩实现了聚光灯效果,需求是:用户仅能在遮罩的镂空高亮区域与下方视图(如滑动Slider、点击Button)交互,其他区域完全屏蔽交互。但当前由于遮罩层的存在,镂空区域也无法和底层视图交互,求解决方法。
效果展示

原实现代码
ZStack { VStack { Slider( value: $speed, in: 0...100, onEditingChanged: { editing in isEditing = editing } ) Text("\(speed)") .foregroundColor(isEditing ? .red : .blue) } .padding(.top, 100) Rectangle() .fill(Color.black.opacity(0.5)) .mask { Rectangle() .overlay(alignment: .topLeading) { let rect = CGRect(x: 0, y: 500, width:390, height: 80) RoundedRectangle(cornerRadius: 10, style: .continuous) .frame(width: rect.width, height: rect.height) .offset(y:90) .blendMode(.destinationOut) } } }
解决方案
核心是让遮罩层的非镂空区域屏蔽交互,镂空透明区域允许点击穿透到底层视图,只需对遮罩视图做两处修改:
- 添加
.compositingGroup():将遮罩后的视图组合成一个渲染组,让系统正确识别透明区域(因为使用了blendMode(.destinationOut),必须组化才能让点击测试生效) - 保留
.allowsHitTesting(true):让非透明的黑色遮罩区域接收点击,从而屏蔽底层交互,透明镂空区域则自动忽略点击,让事件透传到底层
修改后的完整代码
ZStack { VStack { Slider( value: $speed, in: 0...100, onEditingChanged: { editing in isEditing = editing } ) Text("\(speed)") .foregroundColor(isEditing ? .red : .blue) } .padding(.top, 100) Rectangle() .fill(Color.black.opacity(0.5)) .mask { Rectangle() .overlay(alignment: .topLeading) { let rect = CGRect(x: 0, y: 500, width:390, height: 80) RoundedRectangle(cornerRadius: 10, style: .continuous) .frame(width: rect.width, height: rect.height) .offset(y:90) .blendMode(.destinationOut) } } .compositingGroup() // 关键:组化视图,确保透明区域的点击测试正确 .allowsHitTesting(true) }
原理说明
SwiftUI中,默认会忽略完全透明区域的点击事件,但当使用blendMode这类修改像素的操作时,必须通过.compositingGroup()将视图及其遮罩打包成一个独立渲染单元,这样系统才能准确判断哪些区域是可交互(非透明)、哪些是可穿透(透明)的。
内容的提问来源于stack exchange,提问作者Asadullah Ali
相关产品推荐
相关产品推荐

