2016-09-14 69 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