2016-09-14 94 views
0

我想从两点在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 
} 

这doesen't拿度点考虑进去,所以如果我是作为创建两点:

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

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

希望有人能帮助我在正确的方向。这个想法当然最后只需要知道两点(p1,p2)并动态创建其他所有点,我只需键入当前的值,以便让自己更容易。我添加了该问题的图片以更好地向您展示。 enter image description here enter image description here

+0

您可以在线的末端添加四分之一的椭圆以获得完美的曲线。 – Khundragpan

+0

谢谢你的回答,你能举一个例子来说明你的意思吗? – Imbue

回答

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) 
    } 
} 
+0

这是完美的,正是我期待的! – Imbue

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

这是我用来做演示整个斯威夫特游乐场:

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 
+0

这是一个非常酷的实现。非常感谢! – Imbue