You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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()
    }
}

核心改动说明

  1. 状态变量管理:新增rectFrame状态变量,动态存储矩形的位置和尺寸,替代原有的固定值计算
  2. 拖拽手势实现:在四个角添加透明的可交互区域,通过DragGesture监听拖拽动作,实时更新rectFrame的边界值
  3. 边界限制:设置minRectSize确保矩形不会被拖到过小,同时限制拖拽范围不超出父视图边界
  4. 动态UI更新:遮罩层和边角路径的绘制逻辑改为依赖rectFrame,实现UI跟随拖拽实时变化

内容的提问来源于stack exchange,提问作者DiamondLion

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 02:17:17