从两点创build一个大括号曲线

我试图从两个点Swift中创build一个大括号。 这个想法很好,直线,因为它目前不是dynamic的。 我的问题在于根据p1和p2点的位置finddynamic控制点和中心。

这是我现在的代码:

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) 

它不会find适当的控制点和中点。

希望有人能帮助我正确的方向。 这个想法当然最后只需要知道两点(p1,p2)并dynamic地创build其他所有的点,我只需键入当前的值,使自己更容易。 我已经添加了这个问题的图片,以更好地展示给你。 在这里输入图像说明 在这里输入图像说明

问题的关键是当数字旋转时,您的base vectors将旋转。 当你的figure轴alignment的,你的基向量是u (1, 0)v (0, 1)

所以当你正在执行midPoint.y -= 20你可以看到它与midPoint.x -= vx * 20; midPoint.y -= vy * 20 midPoint.x -= vx * 20; midPoint.y -= vy * 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 + px, y: self.y + py) } public func minus(p: CGPoint) -> (CGPoint) { return CGPoint(x: self.x - px, y: self.y - py) } 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) } } 

首先创build一个从(0,0)开始到(1,0)结束的大括号。 然后应用仿射变换来移动,缩放和旋转path以跨越您devise的端点。 它需要将(0,0)转换为您的起点和(1,0)到您的终点。 有效地创build转换需要一些三angular函数,但我已经为你做了功课:

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

结果:

互动大括号演示

下面是我用来演示的整个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