SwiftUI中如何获取遮罩的可见量或可见百分比?
解决方案
SwiftUI的mask修饰符本身没有直接提供获取可见百分比的API,不过我们可以通过计算刮擦路径的覆盖面积来实现需求。核心思路是:计算用户刮擦路径的总面积,和卡片总面积对比,当比例达到80%时,直接显示完整的内容视图。
具体实现步骤:
- 添加状态变量,控制内容显示状态、记录刮擦面积:
@State private var isRevealed = false @State private var scratchArea: CGFloat = 0 private let totalCardArea = 250 * 250 // 卡片固定总面积 private let revealThreshold: CGFloat = 0.8 // 80%显示阈值
- 实现刮擦面积计算方法:
将用户拖拽生成的路径,转换成stroke对应的填充区域,通过CoreGraphics计算该区域的实际面积:
private func calculateScratchArea() -> CGFloat { guard !points.isEmpty else { return 0 } // 创建用户拖拽路径 let dragPath = Path { path in path.addLines(points) } // 将stroke路径转换为可填充的轮廓路径 guard let strokeContourPath = dragPath.stroke( StrokeStyle(lineWidth: 50, lineCap: .round, lineJoin: .round), fill: .color(.black) ).cgPath else { return 0 } let boundingBox = strokeContourPath.boundingBoxOfPath // 创建临时上下文统计填充像素 guard let context = CGContext( data: nil, width: Int(boundingBox.width), height: Int(boundingBox.height), bitsPerComponent: 8, bytesPerRow: Int(boundingBox.width) * 4, space: CGColorSpaceCreateDeviceRGB(), bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue ) else { return 0 } context.setFillColor(UIColor.black.cgColor) context.addPath(strokeContourPath) context.fillPath() // 统计填充像素数量,转换为实际面积 let pixelData = context.data!.bindMemory(to: UInt8.self, capacity: Int(boundingBox.width) * Int(boundingBox.height) * 4) var filledPixels = 0 for i in 0..<Int(boundingBox.width) * Int(boundingBox.height) { if pixelData[i * 4] == 255 { filledPixels += 1 } } let pixelScaleX = boundingBox.width / CGFloat(Int(boundingBox.width)) let pixelScaleY = boundingBox.height / CGFloat(Int(boundingBox.height)) return CGFloat(filledPixels) * pixelScaleX * pixelScaleY }
- 修改视图结构,添加阈值判断:
当刮擦面积占比达到阈值时,直接显示完整的黄色内容;否则保持刮擦交互逻辑:
struct ScratchCardView: View { @State var points = [CGPoint]() @State private var isRevealed = false @State private var scratchArea: CGFloat = 0 private let totalCardArea = 250 * 250 private let revealThreshold: CGFloat = 0.8 var body: some View { ZStack { if isRevealed { // 完全显示内容 RoundedRectangle(cornerRadius: 20) .fill(.yellow) .frame(width: 250, height: 250) } else { // 刮擦覆盖层 RoundedRectangle(cornerRadius: 20) .fill(.red) .frame(width: 250, height: 250) // 隐藏的内容视图 RoundedRectangle(cornerRadius: 20) .fill(.yellow) .frame(width: 250, height: 250) .mask( Path { path in path.addLines(points) }.stroke(style: StrokeStyle(lineWidth: 50, lineCap: .round, lineJoin: .round)) ) .gesture( DragGesture(minimumDistance: 0, coordinateSpace: .local) .onChanged({ value in points.append(value.location) }) .onEnded({ _ in scratchArea = calculateScratchArea() if scratchArea / CGFloat(totalCardArea) >= revealThreshold { isRevealed = true } }) ) } } } // 面积计算方法 private func calculateScratchArea() -> CGFloat { // 实现代码同上 guard !points.isEmpty else { return 0 } let dragPath = Path { path in path.addLines(points) } guard let strokeContourPath = dragPath.stroke( StrokeStyle(lineWidth: 50, lineCap: .round, lineJoin: .round), fill: .color(.black) ).cgPath else { return 0 } let boundingBox = strokeContourPath.boundingBoxOfPath guard let context = CGContext( data: nil, width: Int(boundingBox.width), height: Int(boundingBox.height), bitsPerComponent: 8, bytesPerRow: Int(boundingBox.width) * 4, space: CGColorSpaceCreateDeviceRGB(), bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue ) else { return 0 } context.setFillColor(UIColor.black.cgColor) context.addPath(strokeContourPath) context.fillPath() let pixelData = context.data!.bindMemory(to: UInt8.self, capacity: Int(boundingBox.width) * Int(boundingBox.height) * 4) var filledPixels = 0 for i in 0..<Int(boundingBox.width) * Int(boundingBox.height) { if pixelData[i * 4] == 255 { filledPixels += 1 } } let pixelScaleX = boundingBox.width / CGFloat(Int(boundingBox.width)) let pixelScaleY = boundingBox.height / CGFloat(Int(boundingBox.height)) return CGFloat(filledPixels) * pixelScaleX * pixelScaleY } }
注意事项:
- 面积计算有一定性能开销,建议只在拖拽结束时计算;若需实时反馈,可每隔N次拖拽事件计算一次。
- 若卡片尺寸为动态值,需通过
GeometryReader获取实际尺寸,替换固定的totalCardArea。
内容的提问来源于stack exchange,提问作者batman
相关产品推荐
相关产品推荐

