Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Get the start point of a UIBezierPath

I created a UIBezierPath but I don't know how to access the start point of it. I tried doing this:

let startPoint = path.currentPoint

The property currentPoint gives me the last point, not the start point. The reason I need the start point is because I want to place an image at the starting point of the path.

Any ideas?

like image 741
JEL Avatar asked Apr 18 '16 19:04

JEL


2 Answers

You need to drop down to CGPath and use CGPathApply to walk the elements. You only want the first one, but you have to look at them all.

I'm assuming that your path is well-formed, and starts with a "move." That should always be true for a UIBezierPath (I'm not aware of any way to make it not be true.)

You'll need some help from rob mayoff's CGPath.forEach, which is quite tricky, but with that in place it's pretty straightforward:

// rob mayoff's CGPath.foreach
extension CGPath {
    func forEach(@noescape body: @convention(block) (CGPathElement) -> Void) {
        typealias Body = @convention(block) (CGPathElement) -> Void
        func callback(info: UnsafeMutablePointer<Void>, element: UnsafePointer<CGPathElement>) {
            let body = unsafeBitCast(info, Body.self)
            body(element.memory)
        }
        let unsafeBody = unsafeBitCast(body, UnsafeMutablePointer<Void>.self)
        CGPathApply(self, unsafeBody, callback)
    }
}

// Finds the first point in a path
extension UIBezierPath {
    func firstPoint() -> CGPoint? {
        var firstPoint: CGPoint? = nil

        self.CGPath.forEach { element in
            // Just want the first one, but we have to look at everything
            guard firstPoint == nil else { return }
            assert(element.type == .MoveToPoint, "Expected the first point to be a move")
            firstPoint = element.points.memory
        }
        return firstPoint
    }
}

In Swift 3, it's basically the same:

// rob mayoff's CGPath.foreach
extension CGPath {
    func forEach( body: @convention(block) (CGPathElement) -> Void) {
        typealias Body = @convention(block) (CGPathElement) -> Void
        func callback(info: UnsafeMutableRawPointer?, element: UnsafePointer<CGPathElement>) {
            let body = unsafeBitCast(info, to: Body.self)
            body(element.pointee)
        }
        let unsafeBody = unsafeBitCast(body, to: UnsafeMutableRawPointer.self)
        self.apply(info: unsafeBody, function: callback)
    }
}

// Finds the first point in a path
extension UIBezierPath {
    func firstPoint() -> CGPoint? {
        var firstPoint: CGPoint? = nil

        self.cgPath.forEach { element in
            // Just want the first one, but we have to look at everything
            guard firstPoint == nil else { return }
            assert(element.type == .moveToPoint, "Expected the first point to be a move")
            firstPoint = element.points.pointee
        }
        return firstPoint
    }
}
like image 113
Rob Napier Avatar answered Oct 22 '22 16:10

Rob Napier


Simplest way is

-Subclass the UIBezierPath

-Create the custom property as startPoint

-Override the moveToPoint method and set value for startPoint there

class MyBezierPath: UIBezierPath {
    var startPoint :CGPoint?

    override func moveToPoint(point: CGPoint) {
        super.moveToPoint(point)
        startPoint=point;
    }
}

    var myBezier = MyBezierPath()
    myBezier.moveToPoint(CGPoint(x: 0, y: 0))
    myBezier.addLineToPoint(CGPoint(x: 100, y: 0))
    myBezier.addLineToPoint(CGPoint(x: 50, y: 100))
like image 37
Surya Subenthiran Avatar answered Oct 22 '22 18:10

Surya Subenthiran