Modifying the height and width of UIView after rotation - ios

I have a UIView which is added as a subview to my view controller. I want to rotate the UIView to some arbitrary angle and then modify the width and height of view. I have a UITextField and button to set angles.
-(void) setAngle: (UIButton *) sender
{
angle = [myText.text floatValue];
angle *= (M_PI)/180.0;
self.transform = CGAffineTransformMakeRotation(angle);
}
And two UISliders to modify the height and width accordingly.
-(void)sliderAction1:(id)sender
{
w = (CGFloat)slider1.value;
CGRect myRect= CGRectMake(x, y, w, h);
self.bounds = myRect;
}
-(void)sliderAction2:(id)sender
{
h = (CGFloat)slider2.value;
CGRect myRect= CGRectMake(x, y, w, h);
self.bounds = myRect;
}
I have done my research and found out that after using CGAffineTransformMakeRotation() I can not use setFrame. The problem with "bounds" is that it is modifying the width and height of UIView from both ends. i.e. the origin of UIView is also changing. Is there any way I can extend the width only from one end?

You shouldn't be modifying bounds like that. Typically you do not modify the bounds property.
Try concatenating your transforms using
CGAffineTransformScale (not CGAffineTransformMakeScale). Using "Make" you are effectively resetting the transform matrix.
CGAffineTransformScale(self.rotationTransform, x, y);

-(void)sliderAction1:(id)sender
{
CGFloat w = (CGFloat)slider1.value;
CGRect myRect= CGRectMake(self.view.frame.origin.x, self.view.frame.origin.y, w, self.view.frame.size.height);
self.view.frame = myRect;
}
it's working for me changes width from one side. change is very minute cause slider value is in between 0-1 so if multiply w by 100 or 1000 it will reflect in major
use this after multiply
CGRect myRect= CGRectMake(self.view.frame.origin.x, self.view.frame.origin.y, w*100, self.view.frame.size.height);

Related

Scaling down the width of a view after doing rotation by using CGAffineTransformScale

I have drawn a view(black rectangle) straight to the x-axis then I am applying some rotation by using CGAffineTransformMakeRotation.Please see image 1 after rotation.
After this I need to reduce the width from that rotated position.
For that I am using CGAffineTransformScale but image is not scaling down along the slope.Please see image 2.
If I change width from bound property then it works fine.
Any idea why CGAffineTransformScale behaves like that.
Below is my sample code.
**__block CGAffineTransform rotationTransform;
rotationTransform = CGAffineTransformMakeRotation(angle);
lineview.transform = rotationTransform;
[UIView animateWithDuration:10 animations:^{
CGAffineTransform finalTransform = CGAffineTransformScale(lineview.transform, 0.4, 1);
lineview.transform = finalTransform;
} completion:^(BOOL finished) {
}];
Try this to move the frame and change the width :)
VIEW.frame = CGRectMake(0,39, 320, 111);
This is the format
CGRectMake(CGFloat x, CGFloat y, CGFloat width, CGFloat height)

Convert scroll view frame to window coordinates

I am trying to convert scroll view coordinates to window coordinates. However the resulting frame seems to be shifted by status bar height, what's confusing is that the height remains the same which is not right.
CGRect visibleBounds = CGRectMake(0, 0, CGRectGetWidth(self.scrollView.frame), CGRectGetHeight(self.scrollView.frame));
CGRect scrollViewFrame = [self.scrollView convertRect:visibleBounds toView:nil];
lldb log:
Printing description of visibleBounds: (CGRect) visibleBounds =
(origin = (x = 0, y = 0), size = (width = 320, height = 568))
Printing description of scrollViewFrame: (CGRect) scrollViewFrame = (origin =
(x = 0, y = 20), size = (width = 320, height = 568))
Turns out scroll view bounds can be used to calculate the frame for scroll view in window coordinates, regardless the fact that I see negative bounds, produced frame will be correct anyway.
[self.scrollView convertRect:self.scrollView.bounds toView:nil];

How to call drawRect after an orientation rotation animation is completed

I am trying to use drawRect to draw a rectangle in the space below a collectionView contained in another view (self in the code below) using the frames of both views to define the rectangle.
This code works fine until the device orientation changes. Everything I have tried calls drawRect before the animated rotation begins. This means the collectionView.frame and self.frame have not yet been adjusted by autoLayout and the resulting rectangle is wrong.
CGRect frame = self.collectionViwOutlet.frame;
CGFloat x = frame.origin.x;
CGFloat y = frame.origin.y + frame.size.height + 8;
CGFloat w = frame.size.width;
CGFloat h = self.frame.origin.y + self.frame.size.height - y - 8;
CGRect drawableRect = CGRectMake(x, y, w, h);
Any suggestions would be appreciated.
On your custom UIView override there layoutSubviews, which will be called for you by the system only when needed and when all the variables (orientation, size, etc.)
PS.: Consider it: https://stackoverflow.com/a/19505582/846780

Zoom a rotated image inside scroll view to fit (fill) frame of overlay rect

Through this question and answer I've now got a working means of detecting when an arbitrarily rotated image isn't completely outside a cropping rect.
The next step is to figure out how to correctly adjust it's containing scroll view zoom to ensure that there are no empty spaces inside the cropping rect. To clarify, I want to enlarge (zoom in) the image; the crop rect should remain un-transformed.
The layout hierarchy looks like this:
containing UIScrollView
UIImageView (this gets arbitrarily rotated)
crop rect overlay view
... where the UIImageView can also be zoomed and panned inside the scrollView.
There are 4 gesture events that occur that need to be accounted for:
Pan gesture (done): accomplished by detecting if it's been panned incorrectly and resets the contentOffset.
Rotation CGAffineTransform
Scroll view zoom
Adjustment of the cropping rect overlay frame
As far as I can tell, I should be able to use the same logic for 2, 3, and 4 to adjust the zoomScale of the scroll view to make the image fit properly.
How do I properly calculate the zoom ratio necessary to make the rotated image fit perfectly inside the crop rect?
To better illustrate what I'm trying to accomplish, here's an example of the incorrect size:
I need to calculate the zoom ratio necessary to make it look like this:
Here's the code I've got so far using Oluseyi's solution below. It works when the rotation angle is minor (e.g. less than 1 radian), but anything over that and it goes really wonky.
CGRect visibleRect = [_scrollView convertRect:_scrollView.bounds toView:_imageView];
CGRect cropRect = _cropRectView.frame;
CGFloat rotationAngle = fabs(self.rotationAngle);
CGFloat a = visibleRect.size.height * sinf(rotationAngle);
CGFloat b = visibleRect.size.width * cosf(rotationAngle);
CGFloat c = visibleRect.size.height * cosf(rotationAngle);
CGFloat d = visibleRect.size.width * sinf(rotationAngle);
CGFloat zoomDiff = MAX(cropRect.size.width / (a + b), cropRect.size.height / (c + d));
CGFloat newZoomScale = (zoomDiff > 1) ? zoomDiff : 1.0 / zoomDiff;
[UIView animateWithDuration:0.2
delay:0.05
options:NO
animations:^{
[self centerToCropRect:[self convertRect:cropRect toView:self.zoomingView]];
_scrollView.zoomScale = _scrollView.zoomScale * newZoomScale;
} completion:^(BOOL finished) {
if (![self rotatedView:_imageView containsViewCompletely:_cropRectView])
{
// Damn, it's still broken - this happens a lot
}
else
{
// Woo! Fixed
}
_didDetectBadRotation = NO;
}];
Note I'm using AutoLayout which makes frames and bounds goofy.
Assume your image rectangle (blue in the diagram) and crop rectangle (red) have the same aspect ratio and center. When rotated, the image rectangle now has a bounding rectangle (green) which is what you want your crop scaled to (effectively, by scaling down the image).
To scale effectively, you need to know the dimensions of the new bounding rectangle and use a scale factor that fits the crop rect into it. The dimensions of the bounding rectangle are rather obviously
(a + b) x (c + d)
Notice that each segment a, b, c, d is either the adjacent or opposite side of a right triangle formed by the bounding rect and the rotated image rect.
a = image_rect_height * sin(rotation_angle)
b = image_rect_width * cos(rotation_angle)
c = image_rect_width * sin(rotation_angle)
d = image_rect_height * cos(rotation_angle)
Your scale factor is simply
MAX(crop_rect_width / (a + b), crop_rect_height / (c + d))
Here's a reference diagram:
Fill frame of overlay rect:
For a square crop you need to know new bounds of the rotated image which will fill the crop view.
Let's take a look at the reference diagram:
You need to find the altitude of a right triangle (the image number 2). Both altitudes are equal.
CGFloat sinAlpha = sin(alpha);
CGFloat cosAlpha = cos(alpha);
CGFloat hypotenuse = /* calculate */;
CGFloat altitude = hypotenuse * sinAlpha * cosAlpha;
Then you need to calculate the new width for the rotated image and the desired scale factor as follows:
CGFloat newWidth = previousWidth + altitude * 2;
CGFloat scale = newWidth / previousWidth;
I have implemented this method here.
I will answer using sample code, but basically this problem becomes really easy, if you will think in rotated view coordinate system.
UIView* container = [[UIView alloc] initWithFrame:CGRectMake(80, 200, 100, 100)];
container.backgroundColor = [UIColor blueColor];
UIView* content2 = [[UIView alloc] initWithFrame:CGRectMake(-50, -50, 150, 150)];
content2.backgroundColor = [[UIColor greenColor] colorWithAlphaComponent:0.5];
[container addSubview:content2];
[self.view setBackgroundColor:[UIColor blackColor]];
[self.view addSubview:container];
[container.layer setSublayerTransform:CATransform3DMakeRotation(M_PI / 8.0, 0, 0, 1)];
//And now the calculations
CGRect containerFrameInContentCoordinates = [content2 convertRect:container.bounds fromView:container];
CGRect unionBounds = CGRectUnion(content2.bounds, containerFrameInContentCoordinates);
CGFloat midX = CGRectGetMidX(content2.bounds);
CGFloat midY = CGRectGetMidY(content2.bounds);
CGFloat scaleX1 = (-1 * CGRectGetMinX(unionBounds) + midX) / midX;
CGFloat scaleX2 = (CGRectGetMaxX(unionBounds) - midX) / midX;
CGFloat scaleY1 = (-1 * CGRectGetMinY(unionBounds) + midY) / midY;
CGFloat scaleY2 = (CGRectGetMaxY(unionBounds) - midY) / midY;
CGFloat scaleX = MAX(scaleX1, scaleX2);
CGFloat scaleY = MAX(scaleY1, scaleY2);
CGFloat scale = MAX(scaleX, scaleY);
content2.transform = CGAffineTransformScale(content2.transform, scale, scale);

Update frame's origin after applying CGAffineTransformRotate

I have the x and y coordinates, and the rotation of a UIImageView that I need to place on its original position. The coordinates correspond to the ones of the view once it had been rotated.
The problem that I have found is that if I initialize the view with the given x and y, and perform the rotation afterwards, the final position is not correct, because the order in which the transformations were applied was not correct:
float x, y, w, h; // These values are given
UIImageView *imageView = [[UIImageView alloc] init];
// Apply transformations
imageView.frame = CGRectMake(x, y, w, h);
imageView.transform = CGAffineTransformRotate(imageView.transform, a.rotation);
If I try to use the x and y to translate the view once it has been rotated, then the final x and y are completely wrong:
float x, y, w, h; // These values are given
UIImageView *imageView = [[UIImageView alloc] init];
imageView.frame = CGRectMake(0, 0, w, h);
// Apply transformations
imageView.transform = CGAffineTransformTranslate(imageView.transform, x, y);
imageView.transform = CGAffineTransformRotate(imageView.transform, a.rotation);
I have tried updating the center of the view after applying the rotation with incorrect results too.
I am looking for some advice or tips on how to deal with this in order to achieve the result that I need.
Thanks in advanced!
I'm using this C function to make rotation transform around center:
static inline CGAffineTransform CGAffineTransformMakeRotationAroundCenter(double width, double height, double rad) {
CGAffineTransform t = CGAffineTransformMakeTranslation(height/2, width/2);
t = CGAffineTransformRotate(t, rad);
t = CGAffineTransformTranslate(t, -width/2, -height/2);
return t;
}
You need to specify width, height and angle in radians.
Does this solve you problem?
I was able to fix this by calculating the offset in the Y axis between the original Y position where the frame should be, and the origin of the transformed view.
The functions provided in this answer for a similar question provide a way to calculate the new origin of the frame by creating a point with the minimum X and Y among all the new corners:
-(CGPoint)frameOriginAfterTransform
{
CGPoint newTopLeft = [self newTopLeft];
CGPoint newTopRight = [self newTopRight];
CGPoint newBottomLeft = [self newBottomLeft];
CGPoint newBottomRight = [self newBottomRight];
CGFloat minX = fminf(newTopLeft.x, fminf(newTopRight.x, fminf(newBottomLeft.x, newBottomRight.x)));
CGFloat minY = fminf(newTopLeft.y, fminf(newTopRight.y, fminf(newBottomLeft.y, newBottomRight.y)));
return CGPointMake(minX, minY);
}
Afterwards I calculated the offset in the Y axis and applied it to the center of the transformed view:
// Adjust Y after rotating to compensate offset
CGPoint center = imageView.center;
CGPoint newOrigin = [imageView frameOriginAfterTransform]; // Frame origin calculated after transform
CGPoint newCenter = CGPointZero;
newCenter.x = center.x;
newCenter.y = center.y + (y - newOrigin.y);
imageView.center = newCenter;
For some reason the offset is only affecting the Y axis, although at first I thought it was going to affect both X and Y.
Hope this helps!

Resources