Update: partially working implementation below.
I've asked a couple questions on this previously here and here.
The first works great to determine if the "image" rect is sufficiently contained inside the "crop" rect. The second works a little bit, but something's off in my implementation of it that it doesn't really work.
I'm now looking at the problem a little differently, and would like to change the behavior:
- When the user begins to rotate the image, I'll run the check method (below) to determine if it needs fixing or not.
- If it does need fixing, rather than waiting until the user has finished rotating it, I'd like to resize the image simultaneously to fit the bounds. Is there a simpler (or more reliable) way to implement this behavior?
- I'm going to block rotation greater than 35ยบ in either direction so that we don't have to worry about severe enlargements.
Assumptions/Constraints:
- I'm using AutoLayout
- Point of rotation will be the center of the crop rect, which may or may not be the center of the image rect.
- This demonstrates it working with a square crop, but the user can resize it to whatever, so I imagine it's going to bite me in the ass even more so when it's not square.
Code:
- (BOOL)rotatedView:(UIView*)rotatedView containsViewCompletely:(UIView*)cropView {
// If this method returns YES, good! if NO, bad!
CGPoint cropRotated[4];
CGRect rotatedBounds = rotatedView.bounds;
CGRect cropBounds = cropView.bounds;
// Convert corner points of cropView to the coordinate system of rotatedView:
cropRotated[0] = [cropView convertPoint:cropBounds.origin toView:rotatedView];
cropRotated[1] = [cropView convertPoint:CGPointMake(cropBounds.origin.x + cropBounds.size.width, cropBounds.origin.y) toView:rotatedView];
cropRotated[2] = [cropView convertPoint:CGPointMake(cropBounds.origin.x + cropBounds.size.width, cropBounds.origin.y + cropBounds.size.height) toView:rotatedView];
cropRotated[3] = [cropView convertPoint:CGPointMake(cropBounds.origin.x, cropBounds.origin.y + cropBounds.size.height) toView:rotatedView];
// Check if all converted points are within the bounds of rotatedView:
return (CGRectContainsPoint(rotatedBounds, cropRotated[0]) &&
CGRectContainsPoint(rotatedBounds, cropRotated[1]) &&
CGRectContainsPoint(rotatedBounds, cropRotated[2]) &&
CGRectContainsPoint(rotatedBounds, cropRotated[3]));
}
Taking even yet a different spin on this, I'm getting there. But as you can see in the .gif, eventually the calculations get out of whack because the rotation really isn't what I should be using to calculate the new size. How can I implement this with the correct geometry to ensure the image always resizes correctly? For time saving, I put this into an Xcode project so you don't have to fiddle around building your own: https://github.com/r3mus/RotationCGRectFix.git
- (IBAction)gestureRecognized:(UIRotationGestureRecognizer *)gesture {
CGFloat maxRotation = 40;
CGFloat rotation = gesture.rotation;
CGFloat currentRotation = atan2f(_imageView.transform.b, _imageView.transform.a);;
NSLog(@"%0.4f", RADIANS_TO_DEGREES(rotation));
if ((currentRotation > DEGREES_TO_RADIANS(maxRotation) && rotation > 0) || (currentRotation < DEGREES_TO_RADIANS(-maxRotation) && rotation < 0)) {
return;
}
CGAffineTransform rotationTransform = CGAffineTransformRotate(self.imageView.transform, rotation);
gesture.rotation = 0.0f;
if (gesture.state == UIGestureRecognizerStateChanged) {
CGFloat scale = sqrt(_imageView.transform.a * _imageView.transform.a + _imageView.transform.c * _imageView.transform.c);
if ((currentRotation > 0 && rotation > 0) || (currentRotation < 0 && rotation < 0))
scale = 1 + fabs(rotation);
else if (currentRotation == 0)
scale = 1;
else
scale = 1 - fabs(rotation);
CGAffineTransform sizeTransform = CGAffineTransformMakeScale(scale, scale);
CGPoint center = _imageView.center;
_imageView.transform = CGAffineTransformConcat(rotationTransform, sizeTransform);
_imageView.center = center;
}
}
Gif: