如何在 swift iOS 中调整 UIBezierPath 绘制的对象的大小?

问题描述 投票:0回答:1

我使用 UIBezierPath 来绘制圆、线、自由线等对象...... 我有一个功能,我必须从“func drawPoints(bounds:CGRect)”中的 topRight 和 BottomRight 变量调整圆形和直线对象的大小。

import UIKit

enum ShapeType {
    case circle, rectangle, line, pencil
}

class DrawingView: UIView {
    
    private var path = UIBezierPath()
    private var startPoint: CGPoint?
    private var shapeType: ShapeType?
    private var strokeColor = #colorLiteral(red: 0.2274509804, green: 0.7098039216, blue: 0.2862745098, alpha: 1)
    private var lineWidth: CGFloat = 3.0
    private var paths: [(path: UIBezierPath, color: UIColor)] = []
    private var selectedPathIndex: Int?
    private var offset = CGPoint.zero

    override init(frame: CGRect) {
        super.init(frame: frame)
        setupView()
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setupView()
    }
    
    private func setupView() {
        backgroundColor = .clear
        isMultipleTouchEnabled = false
        
    }
        
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        guard let touch = touches.first else { return }
        startPoint = touch.location(in: self)
        
        print("selectPath ==> \(point)")
        selectedPathIndex = nil
        setNeedsDisplay()
        for (index, path) in paths.enumerated() {
            if path.path.bounds.contains(startPoint ?? CGPoint()){
                selectedPathIndex = index
                self.path = path.path
                print(offset)
                offset = CGPoint(x: (startPoint?.x ?? 0.0) - path.path.bounds.origin.x, y: (startPoint?.y ?? 0) - path.path.bounds.origin.y)
                print(offset)
                print("did touch index", selectedPathIndex)
                setNeedsDisplay()
                return
            }
        }
        
        path = UIBezierPath()
        
        if shapeType == .pencil {
            path.move(to: startPoint!)
        }
    }
    
    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        guard let touch = touches.first, let shapeType = shapeType else { return }
        let currentPoint = touch.location(in: self)
        print(currentPoint)
        if let index = selectedPathIndex {
            let newOrigin = CGPoint(x: currentPoint.x - offset.x, y: currentPoint.y - offset.y)
            print(newOrigin)
            let translation = CGAffineTransform(translationX: newOrigin.x - path.bounds.origin.x, y: newOrigin.y - path.bounds.origin.y)
            print(translation)
            self.paths[index].path.apply(translation)
            setNeedsDisplay()
        }else{
            if shapeType == .pencil {
                if selectedPathIndex != nil{
                    path.move(to: currentPoint)
                }else{
                    path.addLine(to: currentPoint)
                }
               
               
                setNeedsDisplay()
            } else {
                path.removeAllPoints()
                
                guard let startPoint = startPoint else { return }
                
                switch shapeType {
                case .line:
                    path.move(to: startPoint)
                    path.addLine(to: currentPoint)
                case .rectangle:
                    path = UIBezierPath(rect: CGRect(origin: startPoint, size: CGSize(width: currentPoint.x - startPoint.x, height: currentPoint.y - startPoint.y)))
                case .circle:
                    let radius = hypot(currentPoint.x - startPoint.x, currentPoint.y - startPoint.y)
                    path = UIBezierPath(arcCenter: startPoint, radius: radius, startAngle: 0, endAngle: 2 * .pi, clockwise: true)
                case .pencil:
                    break
                }
                setNeedsDisplay()
        }
      }
    }
    
    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        guard let shapeType = shapeType else { return }
        if self.selectedPathIndex == nil{
            if shapeType != .pencil {
                paths.append((path.copy() as! UIBezierPath, strokeColor))
            } else {
                paths.append((path, strokeColor))
            }
        }
        setNeedsDisplay()
    }
    
    override func draw(_ rect: CGRect) {
        for pathData in paths {
            pathData.color.setStroke()
            pathData.path.lineWidth = lineWidth
            pathData.path.stroke()
        }
        
        strokeColor.setStroke()
        path.lineWidth = lineWidth
        path.stroke()
        
        if selectedPathIndex != nil{
            for (index, pathData) in paths.enumerated() {
                pathData.color.setStroke()
                pathData.path.lineWidth = lineWidth
                pathData.path.stroke()
                
                if let selectedIndex = selectedPathIndex, selectedIndex == index {
                    let bounds = path.bounds
                    // Draw a rectangle around the selected path
                    if isPencil(path: path) {
                        print("This path is a line.")
                        self.drawSquare(bounds: bounds)
                    } else if isCurve(path: path) {
                        print("This path is a curve.")
                        self.drawSquare(bounds: bounds)
                        self.drawPoints(bounds: bounds)
                    } else if isLine(path: path){
                        self.drawPoints(bounds: bounds)
                    }
                }
            }
        }
    }
    
    func drawSquare(bounds:CGRect){
        let selectionRect = UIBezierPath(rect: bounds) // Create a rectangle slightly larger than the path
        #colorLiteral(red: 0.1490196078, green: 0.5019607843, blue: 0.9215686275, alpha: 1).setStroke()
        selectionRect.lineWidth = 1 // Set the line width for the rectangle
        selectionRect.stroke() // Draw the rectangle
    }
       
    func drawPoints(bounds:CGRect){
        let topRight = CGPoint(x: bounds.maxX, y: bounds.minY)
        let bottomRight = CGPoint(x: bounds.minX, y: bounds.maxY)
        
        self.drawPoint(at: topRight, color: #colorLiteral(red: 0.1490196078, green: 0.5019607843, blue: 0.9215686275, alpha: 1)) // Change color and size as needed
        self.drawPoint(at: bottomRight, color: #colorLiteral(red: 0.1490196078, green: 0.5019607843, blue: 0.9215686275, alpha: 1))
    }
       
    func drawPoint(at point: CGPoint, color: UIColor) {
        let pointSize: CGFloat = 16
        let pointRect = CGRect(x: point.x - pointSize / 2, y: point.y - pointSize / 2, width: pointSize, height: pointSize)
        let pointPath = UIBezierPath(ovalIn: pointRect)
        color.setFill()
        UIColor.white.setStroke()
        pointPath.lineWidth = 5
        pointPath.stroke()
        color.setStroke()
        pointPath.fill()
    }
    
    func clear() {
        path.removeAllPoints()
        paths.removeAll()
        setNeedsDisplay()
    }
    
    func setStrokeColor(_ color: UIColor) {
        strokeColor = color
    }
    
    func setLineWidth(_ width: CGFloat) {
        lineWidth = width
    }
    
    func setShapeType(_ shape: ShapeType) {
        shapeType = shape
    }
    
    func undo() {
        if !paths.isEmpty {
            path.removeAllPoints()
            paths.removeLast()
            self.setNeedsDisplay()
        }
        
    }
    
    func isLine(path: UIBezierPath) -> Bool {
        var isLine = true
        path.cgPath.applyWithBlock { element in
            let type = element.pointee.type
            if type == .addCurveToPoint || type == .addQuadCurveToPoint {
                isLine = false
            }
        }
        return isLine
    }

    
    func isPencil(path: UIBezierPath) -> Bool {
        var isLine = true
        var isLineCount = 0
        path.cgPath.applyWithBlock { element in
            let type = element.pointee.type
            isLineCount += 1
            if type == .addCurveToPoint || type == .addQuadCurveToPoint {
                isLine = false
            }
        }
        return isLine && isLineCount > 2
    }
    
    func isCurve(path: UIBezierPath) -> Bool {
        var isCurve = false

        path.cgPath.applyWithBlock { element in
            let type = element.pointee.type
            if type == .addCurveToPoint || type == .addQuadCurveToPoint {
                isCurve = true
            }
        }

        return isCurve
    }
}

我已经实现了这一点:https://drive.google.com/file/d/1ZipL-fPNS2y-X-BWr5SgKYIRr1o0RXUg/view?usp=sharing

我想实现这样的目标: https://drive.google.com/file/d/1aLAPbpjDzYPI5mCF2IQ00RJ02NU00YGc/view?usp=sharing

如何实现此功能?过去两天我一直在寻找解决方案,但没有找到与我的功能相关的任何内容。任何类型的指导将不胜感激。

谢谢!

swift resize drawing shapes uibezierpath
1个回答
0
投票

我们可以使用

CGAffineTransform
来移动和缩放路径。

这是一个扩展示例:

extension UIBezierPath {
    func pathIn(targetRect: CGRect) -> UIBezierPath? {
        // get a copy of the path
        guard let newPath = copy() as? UIBezierPath else { return nil }
        
        // get current bounding rect
        let origRect = newPath.bounds
        
        // we want to translate (move) the path to 0,0
        let zeroTR = CGAffineTransform(translationX: -origRect.origin.x, y: -origRect.origin.y)
        newPath.apply(zeroTR)
        
        // now transform the path to new x,y and width,height
        let tr = CGAffineTransform(translationX: targetRect.origin.x, y: targetRect.origin.y)
            .scaledBy(x: targetRect.size.width / origRect.width, y: targetRect.size.height / origRect.height)
        newPath.apply(tr)
        
        return newPath
    }
}

因此,如果我们有一条路径,其边界框为

(40.0, 40.0, 90.0, 60.0)
,并且我们想将其缩放到
180 x 120
的大小,我们可以这样做:

let targetRect: CGRect = .init(x: 40.0, y: 40.0, width: 180.0, height: 120.0)
let transformedPath = originalPath.pathIn(targetRect: targetRect)

示例:

example anim

这里有一些示例代码来创建它......

绘图视图 - 在原始状态下绘制带有虚线边界框的蓝色路径,并在

targetRect

中绘制红色变换路径(带有虚线边界框)
class SampleDrawView: UIView {
    
    public var currentPath: UIBezierPath! { didSet { setNeedsDisplay() } }
    public var targetRect: CGRect = .zero { didSet { setNeedsDisplay() } }
    
    override func draw(_ rect: CGRect) {
        
        // don't try to draw if we don't have a path
        guard currentPath != nil else { return }
        
        // let's draw a dashed-outline rect, 2-points larger than the original path's bounding box
        UIColor.systemBlue.setStroke()
        let boxPath = UIBezierPath(rect: currentPath.bounds.insetBy(dx: -2.0, dy: -2.0))
        boxPath.setLineDash([8, 8], count: 1, phase: 0.0)
        boxPath.stroke()
        
        // draw the original path in blue
        UIColor.blue.setStroke()
        currentPath.lineWidth = 3
        currentPath.stroke()
        
        // make sure targetRect has been set
        guard targetRect != .zero else { return }
        
        // make sure we get a valid transformed path that fits in the target rectangle
        guard let transformedPath = currentPath.pathIn(targetRect: targetRect) else { return }
        
        // let's draw a dashed-outline rect, 2-points larger than the target rect
        UIColor.lightGray.setStroke()
        let targetBoxPath = UIBezierPath(rect: targetRect.insetBy(dx: -2.0, dy: -2.0))
        targetBoxPath.setLineDash([8, 8], count: 1, phase: 0.0)
        targetBoxPath.stroke()

        // now we draw the transformed path in red
        UIColor.red.setStroke()
        transformedPath.lineWidth = 3
        transformedPath.stroke()
        
    }

}

示例控制器 - 点击任意位置即可循环浏览各种目标矩形

class SamplePathVC: UIViewController {
    
    var samplePath: UIBezierPath!
    var someRects: [CGRect] = []
    
    let exampleView = SampleDrawView()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .systemBackground
        
        exampleView.backgroundColor = UIColor(white: 0.975, alpha: 1.0)
        exampleView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(exampleView)
        
        let g = view.safeAreaLayoutGuide
        NSLayoutConstraint.activate([
            exampleView.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
            exampleView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
            exampleView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
            exampleView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -20.0),
        ])

        samplePath = UIBezierPath()
        samplePath.move(to: .init(x: 40.0, y: 40.0))
        samplePath.addLine(to: .init(x: 100.0, y: 40.0))
        samplePath.addQuadCurve(to: .init(x: 100.0, y: 100.0), controlPoint: .init(x: 160.0, y: 70.0))
        samplePath.addLine(to: .init(x: 80.0, y: 80.0))
        samplePath.addLine(to: .init(x: 60.0, y: 100.0))
        samplePath.addLine(to: .init(x: 40.0, y: 80.0))

        exampleView.currentPath = samplePath
        
        let x: CGFloat = samplePath.bounds.origin.x
        let y: CGFloat = samplePath.bounds.origin.y
        let w: CGFloat = samplePath.bounds.size.width
        let h: CGFloat = samplePath.bounds.size.height

        someRects = [
            // scale by 2x
            .init(x: x, y: y, width: w * 2.0, height: h * 2.0),
            // scale by 3x
            .init(x: x, y: y, width: w * 3.0, height: h * 3.0),
            // move but no scale
            .init(x: x + 100.0, y: y + 100.0, width: w, height: h),
            // the rest are move AND scale
            .init(x:  40.0, y: 120.0, width: 120.0, height: 120.0),
            .init(x:  40.0, y: 120.0, width: 240.0, height: 240.0),
            .init(x:  40.0, y: 120.0, width: 240.0, height: 500.0),
            .init(x: 200.0, y:  60.0, width:  60.0, height: 560.0),
            .init(x:  20.0, y: 300.0, width: 280.0, height:  80.0),
        ]
        
    }
    
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        // cycle through the sample target rects on each tap
        let r = someRects.removeFirst()
        someRects.append(r)
        exampleView.targetRect = r
    }
    
}
最新问题
© www.soinside.com 2019 - 2025. All rights reserved.