Rotate CGPath without changing its position

前端 未结 3 1251
小蘑菇
小蘑菇 2020-12-28 09:40

I want to rotate a CGPath, I\'m using the following code:

CGAffineTransform transform = CGAffineTransformMakeRotation(angleInRadians);
CGPathRef         


        
相关标签:
3条回答
  • 2020-12-28 09:42

    A Swift 5 version:

    func rotate(path: UIBezierPath, degree: CGFloat) {
        let bounds: CGRect = path.cgPath.boundingBox
        let center = CGPoint(x: bounds.midX, y: bounds.midY)
    
        let radians = degree / 180.0 * .pi
        var transform: CGAffineTransform = .identity
        transform = transform.translatedBy(x: center.x, y: center.y)
        transform = transform.rotated(by: radians)
        transform = transform.translatedBy(x: -center.x, y: -center.y)
        path.apply(transform)
    }
    
    0 讨论(0)
  • 2020-12-28 09:50

    A path doesn't have “a position”. A path is a set of points (defined by line and curve segments). Every point has its own position.

    Perhaps you want to rotate the path around a particular point, instead of around the origin. The trick is to create a composite transform that combines three individual transforms:

    1. Translate the origin to the rotation point.
    2. Rotate.
    3. Invert the translation from step 1.

    For example, here's a function that takes a path and returns a new path that is the original path rotated around the center of its bounding box:

    static CGPathRef createPathRotatedAroundBoundingBoxCenter(CGPathRef path, CGFloat radians) {
        CGRect bounds = CGPathGetBoundingBox(path); // might want to use CGPathGetPathBoundingBox
        CGPoint center = CGPointMake(CGRectGetMidX(bounds), CGRectGetMidY(bounds));
        CGAffineTransform transform = CGAffineTransformIdentity;
        transform = CGAffineTransformTranslate(transform, center.x, center.y);
        transform = CGAffineTransformRotate(transform, radians);
        transform = CGAffineTransformTranslate(transform, -center.x, -center.y);
        return CGPathCreateCopyByTransformingPath(path, &transform);
    }
    

    Note that this function returns a new path with a +1 retain count that you are responsible for releasing when you're done with it. For example, if you're trying to rotate the path of a shape layer:

    - (IBAction)rotateButtonWasTapped:(id)sender {
        CGPathRef path = createPathRotatedAroundBoundingBoxCenter(shapeLayer_.path, M_PI / 8);
        shapeLayer_.path  = path;
        CGPathRelease(path);
    }
    

    UPDATE

    Here's a demonstration using a Swift playground. We'll start with a helper function that displays a path and marks the origin with a crosshair:

    import UIKit
    import XCPlayground
    
    func showPath(label: String, path: UIBezierPath) {
        let graph = UIBezierPath()
        let r = 40
        graph.moveToPoint(CGPoint(x:0,y:r))
        graph.addLineToPoint(CGPoint(x:0,y:-r))
        graph.moveToPoint(CGPoint(x:-r,y:0))
        graph.addLineToPoint(CGPoint(x:r,y:0))
        graph.appendPath(path)
        XCPCaptureValue(label, graph)
    }
    

    Next, here's our test path:

    var path = UIBezierPath()
    path.moveToPoint(CGPoint(x:1000,y:1000))
    path.addLineToPoint(CGPoint(x:1000,y:1200))
    path.addLineToPoint(CGPoint(x:1100,y:1200))
    showPath("original", path)
    

    original path

    (Remember, the crosshair is the origin and is not part of the path we're transforming.)

    We get the center and transform the path so it's centered at the origin:

    let bounds = CGPathGetBoundingBox(path.CGPath)
    let center = CGPoint(x:CGRectGetMidX(bounds), y:CGRectGetMidY(bounds))
    
    let toOrigin = CGAffineTransformMakeTranslation(-center.x, -center.y)
    path.applyTransform(toOrigin)
    showPath("translated center to origin", path)
    

    path translated to origin

    Then we rotate it. All rotations happen around the origin:

    let rotation = CGAffineTransformMakeRotation(CGFloat(M_PI / 3.0))
    path.applyTransform(rotation)
    showPath("rotated", path)
    

    path rotated

    Finally, we translate it back, exactly inverting the original translation:

    let fromOrigin = CGAffineTransformMakeTranslation(center.x, center.y)
    path.applyTransform(fromOrigin)
    showPath("translated back to original center", path)
    

    path translated from origin

    Note that we must invert the original translation. We don't translate by the center of its new bounding box. Recall that (in this example), the original center is at (1050,1100). But after we've translated it to the origin and rotated it, the new bounding box's center is at (-25,0). Translating the path by (-25,0) will not put it anywhere close to its original position!

    0 讨论(0)
  • 2020-12-28 09:58

    A Swift 3 method to rotate a rectangle in place around its center:

    func createRotatedCGRect(rect: CGRect, radians: CGFloat) -> CGPath {
        let center = CGPoint(x: rect.midX, y: rect.midY)
        let path = UIBezierPath(rect: rect)
        path.apply(CGAffineTransform(translationX: center.x, y: center.y).inverted())
        path.apply(CGAffineTransform(rotationAngle: radians))
        path.apply(CGAffineTransform(translationX: center.x, y: center.y))
        return path.cgPath
    }
    

    All of rob mayoff's code for applying this to a path and its bounding box would still apply, so I didn't see cause to repeat them.

    0 讨论(0)
提交回复
热议问题