【问题标题】:Draw circle with ten or more control points in swift快速绘制具有十个或更多控制点的圆圈
【发布时间】:2021-09-22 05:49:07
【问题描述】:

我正在尝试了解如何实现像this video 这样的具有十多个控制点的圆形,可以将其调整为任何形状并用swift语言实现。

我有foundjavascript类似的效果,但是不知道怎么下手。我也试过用贝塞尔路径实现,代码如下,但是不知道怎么完成。

class MyBezierPathView: UIView {
    
    private var path: UIBezierPath?
    
    // start point
    var startP = CGPoint.zero
    
    // end point
    var endP = CGPoint.zero
    
    // control point
    var controlP = CGPoint.zero
    
    var pathColor: UIColor?
     
    var pathWidth: CGFloat = 0.0
    
    // current touch point
    private var currentTouchP = 0
    
    // init
    override init(frame: CGRect) {

        super.init(frame: frame)
    }
    
    // draw BezierPath
    override func draw(_ rect: CGRect) {

        path = UIBezierPath()
        path?.move(to: startP)

        path?.addQuadCurve(to: endP, controlPoint: controlP)

        path?.lineWidth = pathWidth
        pathColor?.setStroke()
        path?.stroke()

        path = UIBezierPath()

        path?.lineWidth = 1
        UIColor.gray.setStroke()
        
        let lengths: [CGFloat] = [5]
        path?.setLineDash(lengths, count: 1, phase: 1)
        path?.move(to: controlP)
        path?.addLine(to: startP)
        path?.stroke()

        path?.move(to: controlP)
        path?.addLine(to: endP)
        path?.stroke()
    
        
        path = UIBezierPath(arcCenter: startP, radius: 4, startAngle: 0, endAngle: .pi * 2, clockwise: true)
        UIColor.black.setStroke()
        path?.fill()
        
        path = UIBezierPath(arcCenter: endP, radius: 4, startAngle: 0, endAngle: .pi * 2, clockwise: true)
        UIColor.black.setStroke()
        path?.fill()
        
        path = UIBezierPath(arcCenter: controlP, radius: 3, startAngle: 0, endAngle: .pi * 2, clockwise: true)
        path?.lineWidth = 2
        UIColor.black.setStroke()
        path?.stroke()
        
        let startMsgRect = CGRect(x: startP.x + 8, y: startP.y - 7, width: 50, height: 20)
        "start point".draw(in: startMsgRect, withAttributes: nil)

        let endMsgRect = CGRect(x: endP.x + 8, y: endP.y - 7, width: 50, height: 20)
        "end point".draw(in: endMsgRect, withAttributes: nil)

        let control1MsgRect = CGRect(x: controlP.x + 8, y: controlP.y - 7, width: 50, height: 20)
        "control point".draw(in: control1MsgRect, withAttributes: nil)
    }
    
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        
        let startPoint = touches.first?.location(in: self)     
        
        let startR = CGRect(x: startP.x - 4, y: startP.y - 4, width: 80, height: 80)
        let endR = CGRect(x: endP.x - 4, y: endP.y - 4, width: 80, height: 80)
        let controlR = CGRect(x: controlP.x - 4, y: controlP.y - 4, width: 80, height: 80)
        
        guard let startPoint = startPoint else {
            print("startPoint is nil.")
            return
        }
        
        if startR.contains(startPoint) {
            currentTouchP = 1
        } else if endR.contains(startPoint) {
            currentTouchP = 2
        } else if controlR.contains(startPoint) {
            currentTouchP = 3
        }
    }
    

    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
       
        var touchPoint = touches.first?.location(in: self)
        
        
        if touchPoint!.x < 0 {
           touchPoint!.x = 0
        }

        if touchPoint!.x > bounds.size.width {
            touchPoint!.x = bounds.size.width
        }
        if touchPoint!.y < 0 {
            touchPoint!.y = 0
        }

        if touchPoint!.y > bounds.size.height {
            touchPoint!.y = bounds.size.height
        }
        
        switch currentTouchP {
        case 1:
            startP = touchPoint!
        case 2:
            endP = touchPoint!
        case 3:
            controlP = touchPoint!
        default:
            break
        }
               
        setNeedsDisplay()
    }
    
    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        currentTouchP = 0
    }
    
    override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
        touchesEnded(touches, with: event)
    }
    
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    
}
class ViewController: UIViewController {

    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let frame = CGRect(x: 0, y: 0, width: view.bounds.size.width, height: view.bounds.size.height)

        let pathView = MyBezierPathView(frame: frame)
        
        pathView.startP = CGPoint(x: 110, y: 150)
        pathView.endP = CGPoint(x: 258.47, y: 211.53)
        pathView.controlP = CGPoint(x: 196.94, y: 150)
        pathView.pathColor = #colorLiteral(red: 1, green: 0.1491314173, blue: 0, alpha: 1)
        pathView.pathWidth = 2

        pathView.backgroundColor = UIColor.white
        pathView.layer.borderWidth = 1

        view.addSubview(pathView)
        
    }
}

【问题讨论】:

    标签: ios swift uibezierpath


    【解决方案1】:

    解决这个问题的一种方法

    • 从多个曲线段创建一个圆(使用UIBezierPath 类的addQuadCurve()addCurve()
    • addQuadCurve() 用一个control point 添加一条曲线,而addCurve() 用2 个control points 添加一条曲线(您展示的视频似乎使用带有2 个control points 的路径,所以使用addCurve() 会更好)
    • 然后用户需要能够移动这些曲线中的start/endcontrol points
    • 对于每个这些更改,您都必须重新绘制曲线

    我用这个想法创建了一个示例游乐场。在这个操场上,我使用addQuadCurve() 用四条曲线创建了一个红色圆圈(不是一个完美的圆圈)。这个圆圈有 8 个点可以用来改变形状。如果你使用 4 条曲线和addCurve(),那么你将有 12 个点来改变形状。

    然后我改变了红色圆圈的一个点,并在原来的红色圆圈下方添加了绿色的更新形状。

    import UIKit
    import PlaygroundSupport
    
    let container = UIView(frame: CGRect(x: 0, y: 0, width: 500, height: 700))
    
    let view1 = UIView(frame: CGRect(x: 50, y: 50, width: 500, height: 350))
    
    let layer1 = CAShapeLayer()
    layer1.fillColor = UIColor.clear.cgColor
    layer1.lineWidth = 5
    layer1.strokeColor = UIColor.red.cgColor
    
    //create a circle wich has 8 points to change it's shape (4 control points and 4 start/end points of curves)
    let originalPath = UIBezierPath()
    originalPath.move(to: CGPoint(x: 100, y: 0))
    originalPath.addQuadCurve(to: CGPoint(x: 200, y: 100), controlPoint: CGPoint(x: 190, y: 10))
    originalPath.addQuadCurve(to: CGPoint(x: 100, y: 200), controlPoint: CGPoint(x: 190, y: 190))
    originalPath.addQuadCurve(to: CGPoint(x: 0, y: 100), controlPoint: CGPoint(x: 10, y: 190))
    originalPath.addQuadCurve(to: CGPoint(x: 100, y: 0), controlPoint: CGPoint(x: 10, y: 10))
    
    //add this path to the layer1
    layer1.path = originalPath.cgPath
    
    
    //suppose user move the CGPoint(x: 200, y: 100) to CGPoint(x: 220, y: 100)
    //then we can redraw the 4 curves again
    
    let view2 = UIView(frame: CGRect(x: 50, y: 350, width: 500, height: 350))
    
    let layer2 = CAShapeLayer()
    layer2.fillColor = UIColor.clear.cgColor
    layer2.lineWidth = 5
    layer2.strokeColor = UIColor.green.cgColor
    
    //changedPath is almost same as originalPath except CGPoint(x: 250, y: 100)
    let changedPath = UIBezierPath()
    changedPath.move(to: CGPoint(x: 100, y: 0))
    changedPath.addQuadCurve(to: CGPoint(x: 250, y: 100), controlPoint: CGPoint(x: 190, y: 10)) // <---- user has moved point CGPoint(x: 200, y: 100) to CGPoint(x: 250, y: 100). So add this curve to the new point
    changedPath.addQuadCurve(to: CGPoint(x: 100, y: 200), controlPoint: CGPoint(x: 190, y: 190))
    changedPath.addQuadCurve(to: CGPoint(x: 0, y: 100), controlPoint: CGPoint(x: 10, y: 190))
    changedPath.addQuadCurve(to: CGPoint(x: 100, y: 0), controlPoint: CGPoint(x: 10, y: 10))
    
    //adding changed path to layer2
    layer2.path = changedPath.cgPath
    
    view1.layer.addSublayer(layer1)
    view2.layer.addSublayer(layer2)
    
    container.addSubview(view1)
    container.addSubview(view2)
    
    PlaygroundPage.current.liveView = container
    

    【讨论】:

      猜你喜欢
      • 2015-12-03
      • 2014-12-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-06-06
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多