4

I have a very simple UIImageView which is contained in a container view. They are the same size, no auto layout used. When user presses a button, UIImageView should be rotated by 90 degrees. When UIImageView (not UIImageView.image) is rotated it should fit its superview (.ScaleAspectFit). So far I've figured out the easiest part: transform rotation.

    self.currentRotation += 90
    if self.currentRotation >= 360 {
        self.currentRotation = 0
    }

    let angleInRadians: Double
    switch self.currentRotation {
    case 0:
        angleInRadians = 0
    case 90:
        angleInRadians = M_PI_2
    case 180:
        angleInRadians = M_PI
    case 270:
        angleInRadians = M_PI + M_PI_2
    default:
        angleInRadians = 0
        break;
    }
    let newTransform = CGAffineTransformRotate(CGAffineTransformIdentity, CGFloat(angleInRadians))

By getting some information from this question, I was able to write Swift versions of helper extensions, but I still can't figure out how to find UIImageView's frame.

extension UIView {
    var originalFrame: CGRect {
        let currentTransform = self.transform
        self.transform = CGAffineTransformIdentity
        let originalFrame = self.frame
        self.transform = currentTransform
        return originalFrame
    }

    var transformedTopLeftPoint: CGPoint {
        return self.transformedPointForOriginalPoint(originalFrame.topLeftPoint)
    }

    var transformedTopRightPoint: CGPoint {
        return self.transformedPointForOriginalPoint(originalFrame.topRightPoint)
    }

    var transformedBottomLeftPoint: CGPoint {
        return self.transformedPointForOriginalPoint(originalFrame.bottomLeftPoint)
    }

    var transformedBottomRightPoint: CGPoint {
        return self.transformedPointForOriginalPoint(originalFrame.bottomRightPoint)
    }

    // helper to get point offset from center
    func centerOffset(point: CGPoint) -> CGPoint {
        return CGPoint(x: point.x - self.center.x, y: point.y - self.center.y)
    }

    // helper to get point back relative to center
    func pointRelativeToCenter(point: CGPoint) -> CGPoint {
        return CGPoint(x: point.x + self.center.x, y: point.y + self.center.y)
    }

    // helper to get point relative to transformed coords
    func transformedPointForOriginalPoint(point: CGPoint) -> CGPoint {
        // get offset from center
        let offset = self.centerOffset(point)
        // get transformed point
        let transformedPoint = CGPointApplyAffineTransform(offset, self.transform)
        // make relative to center
        return self.pointRelativeToCenter(transformedPoint)
    }
}

extension CGRect {
    var topLeftPoint: CGPoint {
        return self.origin
    }

    var topRightPoint: CGPoint {
        return CGPoint(x: self.origin.x + self.size.width, y: self.origin.y)
    }

    var bottomLeftPoint: CGPoint {
        return CGPoint(x: self.origin.x, y: self.origin.y + self.size.height)
    }

    var bottomRightPoint: CGPoint {
        return CGPoint(x: self.origin.x + self.size.width, y: self.origin.y + self.size.height)
    }
}

For better clarification, I'm attaching an image.

image for better understanding

It shows a rotated view on top of its original frame (the smallest of the outlines) and the updated frame (the largest gray outline). The circles indicate the view's top-right corner before and after rotation. After the transform is applied, the frame updates to the minimum bounding box that encloses the view. Its new origin (the top-left corner of the gray outside box) has essentially nothing to do with the original view origin (the top-left corner of the black unrotated inner box). iOS does not provide a way to retrieve that adjusted point. Here are a series of view methods that perform that math for you. (UIView extension above)

I need to find the gray box's frame. But even with those methods I still can't figure out how to do that. What's the formula/algorithm to achieve what I want?

Community
  • 1
  • 1
xinatanil
  • 1,085
  • 2
  • 13
  • 23
  • Simple. Transformed view frame is a bounding box of that view. So just use frame. – kirander Nov 02 '15 at 12:11
  • 1
    @kirander But according to documentation when you set a custom transform to a UIView, frame property becomes undefined and should not be used at all. – xinatanil Nov 02 '15 at 12:25
  • Yes, that's true, but the hint is the frame of transformed view describes the minimal bounding rectangle. Why not use this? – kirander Nov 02 '15 at 12:29
  • Hey did you ever find the solution to this one? I am trying to determine that gray frame but no success. Thanks! – Joaquin Pereira Jun 09 '22 at 23:17

2 Answers2

2

After transformation centre of the transformed view remains unchanged. So you need to calculate things in terms of the centre. Something like this:

func originalFrame() -> CGRect {
    CGAffineTransform currentTransform = self.transform;
    self.transform = CGAffineTransformIdentity;
    let originalFrame = self.frame;
    self.transform = currentTransform;

    return originalFrame;
}

func centerOffset(point:CGPoint) -> CGPoint {
    return CGPointMake(point.x - self.center.x, point.y - self.center.y);
}

func pointRelativeToCenter(point:CGPoint) -> CGPoint {
    CGPointMake(point.x + self.center.x, point.y + self.center.y);
}


func newPointInView(point:CGPoint) -> CGPoint {
    let offset = self.centerOffset(point)
    let transformedPoint = CGPointApplyAffineTransform(offset, self.transform);
    return self.pointRelativeToCenter(transformedPoint)
}

func newTopLeft() -> CGPoint {
    let frame = self.originalFrame()
    return self.newPointInView(frame)
}

func newTopRight() -> CGPoint {
    let frame = self.originalFrame()
    var point = frame.origin
    point.x = point.x + frame.size.width
    return self.newPointInView(point)
}


func newBottomLeft() -> CGPoint {
    let frame = self.originalFrame()
    var point = frame.origin
    point.y = point.y + frame.size.height
    return self.newPointInView(point)
}


func newBottomRight() -> CGPoint {
    let frame = self.originalFrame()
    var point = frame.origin
    point.x = point.x + frame.size.width
    point.y = point.y + frame.size.height
    return self.newPointInView(point)
}

Refer to this SO Thread for more details.

Community
  • 1
  • 1
Abhinav
  • 37,684
  • 43
  • 191
  • 309
-1

The hint is the frame of transformed view describes the minimal bounding rectangle. You can use it.

// after transform
// obj-c
CGRect boundingBoxFrame = myTransformedImageView.frame;
kirander
  • 2,202
  • 20
  • 18
  • Sorry, still don't get it. Do you suggest finding transformed view's frame by combining its bounds with something else? Could you please provide a code sample? I had a long night and I would appreciate a more detailed answer. – xinatanil Nov 02 '15 at 12:49
  • Thanks for the detailed answer, it does work. But could you please explain the dangers of this method? According to documentation "If transform property is not the identity transform, the value of the frame property is undefined and therefore should be ignored." UIImageView's transform is definitely not CGAffineTransformIdentity, and I'm afraid that sooner or later I'll have to find another way to calculate transformed view's frame. – xinatanil Nov 02 '15 at 13:07
  • Frame property is calculated property, so it will work anyway and anytime. The danger is you should not SET frame property, only read. – kirander Nov 02 '15 at 13:09