如何将多个矩形UIBezierPath合并为单一路径并取区域并集?
问题原因
你当前的实现代码如下:
let path = UIBezierPath(rect: blurView.bounds) path.usesEvenOddFillRule = true path.append(UIBezierPath(rect: CGRect(x: 100, y: 100, width: 100, height: 100))) path.append(UIBezierPath(rect: CGRect(x: 150, y: 150, width: 100, height: 100))) //here you can add more paths, but the number is not known let layer = CAShapeLayer() layer.path = path.cgPath layer.fillRule = .evenOdd blurView.layer.mask = layer
该实现使用的evenOdd(奇偶填充)规则判断逻辑为:从区域内任意点向外发射射线,若射线穿过路径边界的次数为奇数则填充该区域,为偶数则不填充。你直接将两个重叠矩形追加到外层大路径中时,两个矩形的重叠区域会被路径边界穿过2次(偶数),因此被判定为填充区域,也就是你看到的重叠部分没有被镂空。
你当前实现的效果如下:
这个需求完全可以实现,下面给出两种稳定可复用的方案,支持任意数量的镂空矩形,自动计算区域并集,不会扣除重叠部分。
方案1:图形上下文生成Mask(推荐,全版本兼容)
不需要处理复杂的路径填充规则和路径方向问题,直接通过绘制生成Mask图片,逻辑简单零出错,支持任意形状的镂空区域:
func setupBlurMask(for blurView: UIView, holeRects: [CGRect]) { let maskLayer = CALayer() maskLayer.frame = blurView.bounds UIGraphicsBeginImageContextWithOptions(blurView.bounds.size, false, 0) guard let context = UIGraphicsGetCurrentContext() else { UIGraphicsEndImageContext() blurView.layer.mask = maskLayer return } // 先填充整个区域为不透明黑色,对应毛玻璃正常显示的部分 context.setFillColor(UIColor.black.cgColor) context.fill(blurView.bounds) // 将所有镂空矩形区域填充为透明,对应毛玻璃消失的镂空部分 context.setBlendMode(.clear) context.setFillColor(UIColor.clear.cgColor) holeRects.forEach { context.fill($0) } let maskImage = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() maskLayer.contents = maskImage?.cgImage blurView.layer.mask = maskLayer } // 调用示例 let holeRects = [ CGRect(x: 100, y: 100, width: 100, height: 100), CGRect(x: 150, y: 150, width: 100, height: 100) // 可追加任意数量的矩形,无需处理重叠问题 ] setupBlurMask(for: blurView, holeRects: holeRects)
方案2:路径合并实现(iOS16+可用)
如果不想使用图片生成Mask,可以先将所有镂空矩形合并为单个并集路径,反转路径方向后追加到外层大路径,使用nonZero填充规则即可:
let outerPath = UIBezierPath(rect: blurView.bounds) var holePath = UIBezierPath() let holeRects = [ CGRect(x: 100, y: 100, width: 100, height: 100), CGRect(x: 150, y: 150, width: 100, height: 100) // 可追加任意数量矩形 ] holeRects.forEach { holePath.append(UIBezierPath(rect: $0)) } // 合并所有镂空路径为并集 if let mergedPath = holePath.cgPath.union(holePath.cgPath) { holePath = UIBezierPath(cgPath: mergedPath) } // 反转镂空路径方向,追加到外层路径 outerPath.append(holePath.reversing()) let maskLayer = CAShapeLayer() maskLayer.path = outerPath.cgPath maskLayer.fillRule = .nonZero blurView.layer.mask = maskLayer
注意:如果直接追加反转后的单个矩形路径而不做并集合并,重叠区域依然会因为缠绕值计算问题出现填充异常,因此必须先将所有镂空区域合并为单一路径再处理。
内容的提问来源于stack exchange,提问作者kunass2
相关产品推荐
相关产品推荐

