SwiftUI实现可拖拽边角调整大小的文字扫描矩形
实现可拖拽调整大小的扫描矩形(SwiftUI)
问题描述
我正在开发文字扫描功能,需要实现一个用户可通过拖拽边角调整大小的矩形,效果和Photomath里的矩形一致。目前已经完成了矩形的UI代码,但还没法实现拖拽调整大小的功能,代码如下:
import SwiftUI struct ViewCamera: View { var body: some View { GeometryReader { geometry in let cutoutWidth: CGFloat = min(geometry.size.width, geometry.size.height) / 1.5 ZStack { Rectangle() .fill(Color.black.opacity(0.5)) RoundedRectangle(cornerRadius: 20) .fill(Color.black) .frame(width: cutoutWidth, height: cutoutWidth, alignment: .center) .blendMode(.destinationOut) }.compositingGroup() Path { path in let left = (geometry.size.width - cutoutWidth) / 2.0 let right = left + cutoutWidth let top = (geometry.size.height - cutoutWidth) / 2.0 let bottom = top + cutoutWidth path.addPath( createCornersPath( left: left, top: top, right: right, bottom: bottom, cornerRadius: 40, cornerLength: 20 ) ) } .stroke(Color("AccentColor2"), lineWidth: 8) .frame(width: cutoutWidth, height: cutoutWidth, alignment: .center) .aspectRatio(1, contentMode: .fit) } } private func createCornersPath( left: CGFloat, top: CGFloat, right: CGFloat, bottom: CGFloat, cornerRadius: CGFloat, cornerLength: CGFloat ) -> Path { var path = Path() // top left path.move(to: CGPoint(x: left, y: (top + cornerRadius / 2.0))) path.addArc( center: CGPoint(x: (left + cornerRadius / 2.0), y: (top + cornerRadius / 2.0)), radius: cornerRadius / 2.0, startAngle: Angle(degrees: 180.0), endAngle: Angle(degrees: 270.0), clockwise: false ) path.move(to: CGPoint(x: left + (cornerRadius / 2.0), y: top)) path.addLine(to: CGPoint(x: left + (cornerRadius / 2.0) + cornerLength, y: top)) path.move(to: CGPoint(x: left, y: top + (cornerRadius / 2.0))) path.addLine(to: CGPoint(x: left, y: top + (cornerRadius / 2.0) + cornerLength)) // top right path.move(to: CGPoint(x: right - cornerRadius / 2.0, y: top)) path.addArc( center: CGPoint(x: (right - cornerRadius / 2.0), y: (top + cornerRadius / 2.0)), radius: cornerRadius / 2.0, startAngle: Angle(degrees: 270.0), endAngle: Angle(degrees: 360.0), clockwise: false ) path.move(to: CGPoint(x: right - (cornerRadius / 2.0), y: top)) path.addLine(to: CGPoint(x: right - (cornerRadius / 2.0) - cornerLength, y: top)) path.move(to: CGPoint(x: right, y: top + (cornerRadius / 2.0))) path.addLine(to: CGPoint(x: right, y: top + (cornerRadius / 2.0) + cornerLength)) // bottom left path.move(to: CGPoint(x: left + cornerRadius / 2.0, y: bottom)) path.addArc( center: CGPoint(x: (left + cornerRadius / 2.0), y: (bottom - cornerRadius / 2.0)), radius: cornerRadius / 2.0, startAngle: Angle(degrees: 90.0), endAngle: Angle(degrees: 180.0), clockwise: false ) path.move(to: CGPoint(x: left + (cornerRadius / 2.0), y: bottom)) path.addLine(to: CGPoint(x: left + (cornerRadius / 2.0) + cornerLength, y: bottom)) path.move(to: CGPoint(x: left, y: bottom - (cornerRadius / 2.0))) path.addLine(to: CGPoint(x: left, y: bottom - (cornerRadius / 2.0) - cornerLength)) // bottom right path.move(to: CGPoint(x: right, y: bottom - cornerRadius / 2.0)) path.addArc( center: CGPoint(x: (right - cornerRadius / 2.0), y: (bottom - cornerRadius / 2.0)), radius: cornerRadius / 2.0, startAngle: Angle(degrees: 0.0), endAngle: Angle(degrees: 90.0), clockwise: false ) path.move(to: CGPoint(x: right - (cornerRadius / 2.0), y: bottom)) path.addLine(to: CGPoint(x: right - (cornerRadius / 2.0) - cornerLength, y: bottom)) path.move(to: CGPoint(x: right, y: bottom - (cornerRadius / 2.0))) path.addLine(to: CGPoint(x: right, y: bottom - (cornerRadius / 2.0) - cornerLength)) return path } } struct ViewCamera_Previews: PreviewProvider { static var previews: some View { ViewCamera() } }
解决方案
要实现拖拽调整功能,我们需要用状态变量控制矩形的位置和尺寸,结合DragGesture处理拖拽交互。以下是修改后的完整代码:
import SwiftUI struct ViewCamera: View { // 存储矩形的位置和尺寸,初始值在onAppear中计算 @State private var rectFrame: CGRect = .zero // 矩形最小尺寸限制 private let minRectSize: CGFloat = 150 var body: some View { GeometryReader { geometry in ZStack { // 半透明遮罩层 Rectangle() .fill(Color.black.opacity(0.5)) .overlay( // 镂空区域 RoundedRectangle(cornerRadius: 20) .fill(Color.black) .frame(width: rectFrame.width, height: rectFrame.height) .position(x: rectFrame.midX, y: rectFrame.midY) .blendMode(.destinationOut) ) .compositingGroup() // 边角路径 Path { path in path.addPath( createCornersPath( left: rectFrame.minX, top: rectFrame.minY, right: rectFrame.maxX, bottom: rectFrame.maxY, cornerRadius: 40, cornerLength: 20 ) ) } .stroke(Color("AccentColor2"), lineWidth: 8) // 四个可拖拽的角(用透明按钮接收手势) // 左上角 Color.clear .frame(width: 40, height: 40) .position(x: rectFrame.minX, y: rectFrame.minY) .gesture( DragGesture() .onChanged { value in let newX = max(value.location.x, 0) let newY = max(value.location.y, 0) let newWidth = rectFrame.maxX - newX let newHeight = rectFrame.maxY - newY if newWidth >= minRectSize && newHeight >= minRectSize { rectFrame = CGRect( x: newX, y: newY, width: newWidth, height: newHeight ) } } ) // 右上角 Color.clear .frame(width: 40, height: 40) .position(x: rectFrame.maxX, y: rectFrame.minY) .gesture( DragGesture() .onChanged { value in let newX = min(value.location.x, geometry.size.width) let newY = max(value.location.y, 0) let newWidth = newX - rectFrame.minX let newHeight = rectFrame.maxY - newY if newWidth >= minRectSize && newHeight >= minRectSize { rectFrame = CGRect( x: rectFrame.minX, y: newY, width: newWidth, height: newHeight ) } } ) // 左下角 Color.clear .frame(width: 40, height: 40) .position(x: rectFrame.minX, y: rectFrame.maxY) .gesture( DragGesture() .onChanged { value in let newX = max(value.location.x, 0) let newY = min(value.location.y, geometry.size.height) let newWidth = rectFrame.maxX - newX let newHeight = newY - rectFrame.minY if newWidth >= minRectSize && newHeight >= minRectSize { rectFrame = CGRect( x: newX, y: rectFrame.minY, width: newWidth, height: newHeight ) } } ) // 右下角 Color.clear .frame(width: 40, height: 40) .position(x: rectFrame.maxX, y: rectFrame.maxY) .gesture( DragGesture() .onChanged { value in let newX = min(value.location.x, geometry.size.width) let newY = min(value.location.y, geometry.size.height) let newWidth = newX - rectFrame.minX let newHeight = newY - rectFrame.minY if newWidth >= minRectSize && newHeight >= minRectSize { rectFrame = CGRect( x: rectFrame.minX, y: rectFrame.minY, width: newWidth, height: newHeight ) } } ) } .onAppear { // 初始化矩形位置和尺寸 let cutoutWidth = min(geometry.size.width, geometry.size.height) / 1.5 let x = (geometry.size.width - cutoutWidth) / 2 let y = (geometry.size.height - cutoutWidth) / 2 rectFrame = CGRect(x: x, y: y, width: cutoutWidth, height: cutoutWidth) } } } private func createCornersPath( left: CGFloat, top: CGFloat, right: CGFloat, bottom: CGFloat, cornerRadius: CGFloat, cornerLength: CGFloat ) -> Path { var path = Path() // top left path.move(to: CGPoint(x: left, y: (top + cornerRadius / 2.0))) path.addArc( center: CGPoint(x: (left + cornerRadius / 2.0), y: (top + cornerRadius / 2.0)), radius: cornerRadius / 2.0, startAngle: Angle(degrees: 180.0), endAngle: Angle(degrees: 270.0), clockwise: false ) path.move(to: CGPoint(x: left + (cornerRadius / 2.0), y: top)) path.addLine(to: CGPoint(x: left + (cornerRadius / 2.0) + cornerLength, y: top)) path.move(to: CGPoint(x: left, y: top + (cornerRadius / 2.0))) path.addLine(to: CGPoint(x: left, y: top + (cornerRadius / 2.0) + cornerLength)) // top right path.move(to: CGPoint(x: right - cornerRadius / 2.0, y: top)) path.addArc( center: CGPoint(x: (right - cornerRadius / 2.0), y: (top + cornerRadius / 2.0)), radius: cornerRadius / 2.0, startAngle: Angle(degrees: 270.0), endAngle: Angle(degrees: 360.0), clockwise: false ) path.move(to: CGPoint(x: right - (cornerRadius / 2.0), y: top)) path.addLine(to: CGPoint(x: right - (cornerRadius / 2.0) - cornerLength, y: top)) path.move(to: CGPoint(x: right, y: top + (cornerRadius / 2.0))) path.addLine(to: CGPoint(x: right, y: top + (cornerRadius / 2.0) + cornerLength)) // bottom left path.move(to: CGPoint(x: left + cornerRadius / 2.0, y: bottom)) path.addArc( center: CGPoint(x: (left + cornerRadius / 2.0), y: (bottom - cornerRadius / 2.0)), radius: cornerRadius / 2.0, startAngle: Angle(degrees: 90.0), endAngle: Angle(degrees: 180.0), clockwise: false ) path.move(to: CGPoint(x: left + (cornerRadius / 2.0), y: bottom)) path.addLine(to: CGPoint(x: left + (cornerRadius / 2.0) + cornerLength, y: bottom)) path.move(to: CGPoint(x: left, y: bottom - (cornerRadius / 2.0))) path.addLine(to: CGPoint(x: left, y: bottom - (cornerRadius / 2.0) - cornerLength)) // bottom right path.move(to: CGPoint(x: right, y: bottom - cornerRadius / 2.0)) path.addArc( center: CGPoint(x: (right - cornerRadius / 2.0), y: (bottom - cornerRadius / 2.0)), radius: cornerRadius / 2.0, startAngle: Angle(degrees: 0.0), endAngle: Angle(degrees: 90.0), clockwise: false ) path.move(to: CGPoint(x: right - (cornerRadius / 2.0), y: bottom)) path.addLine(to: CGPoint(x: right - (cornerRadius / 2.0) - cornerLength, y: bottom)) path.move(to: CGPoint(x: right, y: bottom - (cornerRadius / 2.0))) path.addLine(to: CGPoint(x: right, y: bottom - (cornerRadius / 2.0) - cornerLength)) return path } } struct ViewCamera_Previews: PreviewProvider { static var previews: some View { ViewCamera() } }
核心改动说明
- 状态变量管理:新增
rectFrame状态变量,动态存储矩形的位置和尺寸,替代原有的固定值计算 - 拖拽手势实现:在四个角添加透明的可交互区域,通过
DragGesture监听拖拽动作,实时更新rectFrame的边界值 - 边界限制:设置
minRectSize确保矩形不会被拖到过小,同时限制拖拽范围不超出父视图边界 - 动态UI更新:遮罩层和边角路径的绘制逻辑改为依赖
rectFrame,实现UI跟随拖拽实时变化
内容的提问来源于stack exchange,提问作者DiamondLion
相关产品推荐
相关产品推荐

