从两点创建花括号曲线

时间:2016-09-14 12:24:41

标签: ios swift math

我试图从两点开始在Swift中创建一个花括号。这个想法很好,有一条直线,因为它无论如何都不是动态的。我的问题在于根据p1和p2点的位置找到动态控制点和中心。

这是我目前的代码:

override func viewDidLoad() {
    super.viewDidLoad()

    let path = UIBezierPath()

    let p1 = CGPointMake(100, 100)
    let p2 = CGPointMake(300, 100)

    let c1 = CGPointMake(150, 80)
    let c2 = CGPointMake(250, 80)

    var midPoint = midPointForPoints(p1, p2: p2)

    var midP1 = midPoint
    midP1.x -= 10

    var midP2 = midPoint
    midP2.x += 10

    midPoint.y -= 20

    path.moveToPoint(p1)
    path.addQuadCurveToPoint(midP1, controlPoint: c1)
    path.addLineToPoint(midPoint)
    path.addLineToPoint(midP2)
    path.addQuadCurveToPoint(p2, controlPoint: c2)

    let shape = CAShapeLayer()
    shape.lineWidth = 5
    shape.strokeColor = UIColor.redColor().CGColor
    shape.fillColor = UIColor.clearColor().CGColor
    shape.path = path.CGPath

    self.view.layer.addSublayer(shape)

}


func midPointForPoints(p1: CGPoint, p2: CGPoint)->CGPoint{
    let deltaX = (p1.x + p2.x)/2
    let deltaY = (p1.y + p2.y)/2

    let midPoint = CGPointMake(deltaX, deltaY)

    return midPoint
}

这不考虑积分的程度,所以如果我要创建两点作为:

let p1 = CGPointMake(100, 100)
let p2 = CGPointMake(300, 300)

它找不到合适的控制点和中点。

希望有人可以帮助我朝着正确的方向前进。这个想法当然最终只是知道两点(p1,p2)并动态创建其他所有点,我只是输入当前的值,以使我自己更容易。我已经添加了问题的图像,以便更好地向您展示。 enter image description here enter image description here

2 个答案:

答案 0 :(得分:1)

首先为从(0,0)开始并以(1,0)结束的大括号创建路径。然后应用仿射变换,移动,缩放和旋转路径以跨越您设计的端点。它需要将(0,0)转换为起点,将(1,0)转换为终点。有效地创建转换需要一些三角函数,但我已经完成了功课:

extension UIBezierPath {

    class func brace(from start: CGPoint, to end: CGPoint) -> UIBezierPath {
        let path = self.init()
        path.move(to: .zero)
        path.addCurve(to: CGPoint(x: 0.5, y: -0.1), controlPoint1: CGPoint(x: 0, y: -0.2), controlPoint2: CGPoint(x: 0.5, y: 0.1))
        path.addCurve(to: CGPoint(x: 1, y: 0), controlPoint1: CGPoint(x: 0.5, y: 0.1), controlPoint2: CGPoint(x: 1, y: -0.2))

        let scaledCosine = end.x - start.x
        let scaledSine = end.y - start.y
        let transform = CGAffineTransform(a: scaledCosine, b: scaledSine, c: -scaledSine, d: scaledCosine, tx: start.x, ty: start.y)
        path.apply(transform)
        return path
    }

}

结果:

interactive brace demo

以下是我用来制作演示的整个Swift游乐场:

import UIKit
import PlaygroundSupport

extension UIBezierPath {

    class func brace(from start: CGPoint, to end: CGPoint) -> UIBezierPath {
        let path = self.init()
        path.move(to: .zero)
        path.addCurve(to: CGPoint(x: 0.5, y: -0.1), controlPoint1: CGPoint(x: 0, y: -0.2), controlPoint2: CGPoint(x: 0.5, y: 0.1))
        path.addCurve(to: CGPoint(x: 1, y: 0), controlPoint1: CGPoint(x: 0.5, y: 0.1), controlPoint2: CGPoint(x: 1, y: -0.2))

        let scaledCosine = end.x - start.x
        let scaledSine = end.y - start.y
        let transform = CGAffineTransform(a: scaledCosine, b: scaledSine, c: -scaledSine, d: scaledCosine, tx: start.x, ty: start.y)
        path.apply(transform)
        return path
    }

}

class ShapeView: UIView {

    override class var layerClass: Swift.AnyClass { return CAShapeLayer.self }

    lazy var shapeLayer: CAShapeLayer = { self.layer as! CAShapeLayer }()

}

class ViewController: UIViewController {

    override func loadView() {
        let view = UIView(frame: CGRect(x: 0, y: 0, width: 600, height: 200))
        view.backgroundColor = .white

        for (i, handle) in handles.enumerated() {
            handle.autoresizingMask = [ .flexibleTopMargin, .flexibleTopMargin, .flexibleBottomMargin, .flexibleRightMargin ]
            let frame = CGRect(x: view.bounds.width * 0.1 + CGFloat(i) * view.bounds.width * 0.8 - 22, y: view.bounds.height / 2 - 22, width: 44, height: 44)
            handle.frame = frame
            handle.shapeLayer.path = CGPath(ellipseIn: handle.bounds, transform: nil)
            handle.shapeLayer.lineWidth = 2
            handle.shapeLayer.lineDashPattern = [2, 6]
            handle.shapeLayer.lineCap = kCALineCapRound
            handle.shapeLayer.strokeColor = UIColor.blue.cgColor
            handle.shapeLayer.fillColor = nil
            view.addSubview(handle)

            let panner = UIPanGestureRecognizer(target: self, action: #selector(pannerDidFire(panner:)))
            handle.addGestureRecognizer(panner)
        }

        brace.shapeLayer.lineWidth = 2
        brace.shapeLayer.lineCap = kCALineCapRound
        brace.shapeLayer.strokeColor = UIColor.black.cgColor
        brace.shapeLayer.fillColor = nil
        view.addSubview(brace)
        setBracePath()

        self.view = view
    }

    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        setBracePath()
    }

    private let handles: [ShapeView] = [
        ShapeView(),
        ShapeView()
    ]

    private let brace = ShapeView()

    private func setBracePath() {
        brace.shapeLayer.path = UIBezierPath.brace(from: handles[0].center, to: handles[1].center).cgPath
    }

    @objc private func pannerDidFire(panner: UIPanGestureRecognizer) {
        let view = panner.view!
        let offset = panner.translation(in: view)
        panner.setTranslation(.zero, in: view)
        var center = view.center
        center.x += offset.x
        center.y += offset.y
        view.center = center
        setBracePath()
    }
}

let vc = ViewController()
PlaygroundPage.current.liveView = vc.view

答案 1 :(得分:0)

问题的关键是当图形旋转时,base vectors会旋转。当您的figure 轴对齐时,您的基本向量为u (1, 0)v (0, 1)

因此,当您执行midPoint.y -= 20时,您可以将其视为与midPoint.x -= v.x * 20; midPoint.y -= v.y * 20 v (0, 1)相同的axis independent。结果是一样的,请自己检查。

此实现将执行您的代码所执行的操作,仅let path = UIBezierPath() let p1 = CGPointMake(100, 100) let p2 = CGPointMake(300, 100) let o = p1.plus(p2).divide(2.0) // origo let u = p2.minus(o) // base vector 1 let v = u.turn90() // base vector 2 let c1 = o.minus(u.times(0.5)).minus(v.times(0.2)) // CGPointMake(150, 80) let c2 = o.plus(u.times(0.5)).minus(v.times(0.2)) // CGPointMake(250, 80) var midPoint = o.minus(v.times(0.2)) var midP1 = o.minus(u.times(0.2)) var midP2 = o.plus(u.times(0.2))

CGPoint extension

注意:我将因子设置为与您实施中的初始值相匹配。

为方便起见,还添加了此extension CGPoint { public func plus(p: CGPoint) -> (CGPoint) { return CGPoint(x: self.x + p.x, y: self.y + p.y) } public func minus(p: CGPoint) -> (CGPoint) { return CGPoint(x: self.x - p.x, y: self.y - p.y) } public func times(f: CGFloat) -> (CGPoint) { return CGPoint(x: self.x * f, y: self.y * f) } public func divide(f: CGFloat) -> (CGPoint) { return self.times(1.0/f) } public func turn90() -> (CGPoint) { return CGPoint(x: -self.y, y: x) } } 。希望它有所帮助。

backside rounded div .back .side