SwiftUI中如何将图片遮罩矩形从黑色半透明改为目标样式?
问题描述
现有SwiftUI代码运行正常,但需要将当前黑色透明度0.3的遮罩矩形替换为下图的样式(中间透明、四周深色遮罩,带白色边框及边角标记)。
原代码如下:
struct CropFrame: Shape { let isActive: Bool func path(in rect: CGRect) -> Path { guard isActive else { return Path(rect) } // full rect for non active let size = CGSize(width: UIScreen.screenWidth * 0.7, height: UIScreen.screenHeight/5) let origin = CGPoint(x: rect.midX - size.width / 2, y: rect.midY - size.height / 2) return Path(CGRect(origin: origin, size: size).integral) } } struct CropImage: View { @State private var currentPosition: CGSize = .zero @State private var newPosition: CGSize = .zero @State private var clipped = false var body: some View { VStack { ZStack { Image("test_pic") .resizable() .scaledToFit() .offset(x: self.currentPosition.width, y: self.currentPosition.height) Rectangle() .fill(Color.black.opacity(0.3)) .frame(width: UIScreen.screenWidth * 0.7 , height: UIScreen.screenHeight/5) .overlay(Rectangle().stroke(Color.white, lineWidth: 3)) } .clipShape( CropFrame(isActive: clipped) ) .gesture(DragGesture() .onChanged { value in self.currentPosition = CGSize(width: value.translation.width + self.newPosition.width, height: value.translation.height + self.newPosition.height) } .onEnded { value in self.currentPosition = CGSize(width: value.translation.width + self.newPosition.width, height: value.translation.height + self.newPosition.height) self.newPosition = self.currentPosition }) Button (action : { self.clipped.toggle() }) { Text("Crop Image") .padding(.all, 10) .background(Color.blue) .foregroundColor(.white) .shadow(color: .gray, radius: 1) .padding(.top, 50) } } } }
目标样式:
解决方案
要实现目标遮罩样式,需修改遮罩逻辑,采用反向遮罩(四周深色填充、中间挖空),同时添加边框和边角标记,具体修改如下:
1. 自定义反向遮罩Shape
替换原CropFrame,创建生成"四周填充、中间挖空"路径的Shape:
struct CropMask: Shape { let cropRect: CGRect func path(in rect: CGRect) -> Path { var path = Path(rect) // 覆盖整个屏幕区域 // 通过evenOdd规则减去中间裁剪区域,实现反向遮罩 path.addPath(Path(cropRect), windingRule: .evenOdd) return path } }
2. 重构CropImage视图
移除原黑色矩形遮罩,改用自定义CropMask实现深色背景,同时添加边框和边角标记:
struct CropImage: View { @State private var currentPosition: CGSize = .zero @State private var newPosition: CGSize = .zero @State private var clipped = false // 裁剪区域尺寸 private let cropSize = CGSize(width: UIScreen.main.bounds.width * 0.7, height: UIScreen.main.bounds.height / 5) var body: some View { VStack { ZStack { // 可拖动的图片 Image("test_pic") .resizable() .scaledToFit() .offset(x: currentPosition.width, y: currentPosition.height) if clipped { // 1. 深色遮罩层(反向遮罩) Color.black.opacity(0.7) .mask(CropMask(cropRect: cropRect(in: UIScreen.main.bounds))) // 2. 白色边框 Rectangle() .stroke(Color.white, lineWidth: 2) .frame(size: cropSize) // 3. 边角标记 CropCorners() .frame(size: cropSize) } } .gesture(DragGesture() .onChanged { value in currentPosition = CGSize(width: value.translation.width + newPosition.width, height: value.translation.height + newPosition.height) } .onEnded { value in newPosition = currentPosition }) Button(action: { clipped.toggle() }) { Text("Crop Image") .padding(.all, 10) .background(Color.blue) .foregroundColor(.white) .shadow(color: .gray, radius: 1) .padding(.top, 50) } } } // 计算裁剪区域的位置和尺寸 private func cropRect(in screenRect: CGRect) -> CGRect { CGRect( x: screenRect.midX - cropSize.width / 2, y: screenRect.midY - cropSize.height / 2, width: cropSize.width, height: cropSize.height ).integral } } // 边角标记视图 struct CropCorners: View { var body: some View { ZStack { // 左上角标记 Rectangle() .fill(Color.white) .frame(width: 20, height: 3) .offset(x: -cropSize.width/2 + 10, y: -cropSize.height/2 + 1.5) Rectangle() .fill(Color.white) .frame(width: 3, height: 20) .offset(x: -cropSize.width/2 + 1.5, y: -cropSize.height/2 + 10) // 右上角标记 Rectangle() .fill(Color.white) .frame(width: 20, height: 3) .offset(x: cropSize.width/2 - 10, y: -cropSize.height/2 + 1.5) Rectangle() .fill(Color.white) .frame(width: 3, height: 20) .offset(x: cropSize.width/2 - 1.5, y: -cropSize.height/2 + 10) // 左下角标记 Rectangle() .fill(Color.white) .frame(width: 20, height: 3) .offset(x: -cropSize.width/2 + 10, y: cropSize.height/2 - 1.5) Rectangle() .fill(Color.white) .frame(width: 3, height: 20) .offset(x: -cropSize.width/2 + 1.5, y: cropSize.height/2 - 10) // 右下角标记 Rectangle() .fill(Color.white) .frame(width: 20, height: 3) .offset(x: cropSize.width/2 - 10, y: cropSize.height/2 - 1.5) Rectangle() .fill(Color.white) .frame(width: 3, height: 20) .offset(x: cropSize.width/2 - 1.5, y: cropSize.height/2 - 10) } } private var cropSize: CGSize { CGSize(width: UIScreen.main.bounds.width * 0.7, height: UIScreen.main.bounds.height / 5) } } // 扩展View,方便通过CGSize设置frame extension View { func frame(size: CGSize) -> some View { self.frame(width: size.width, height: size.height) } }
3. 代码说明
CropMask通过evenOdd缠绕规则实现反向遮罩,让中间裁剪区域透明,四周显示深色背景CropCorners单独实现四个边角的白色标记,配合边框还原目标样式- 移除原
clipShape逻辑,改为在clipped为true时显示遮罩、边框和边角标记,逻辑更清晰
内容的提问来源于stack exchange,提问作者stefanosn
相关产品推荐
相关产品推荐

