What is the best way to manually reproduce
contentMode = UIViewContentModeScaleAspectFit;
without using it?
I need to scale a UIImageView (inside a scroll view) to fit the aspect ratio. I need to know the new size of the image to draw overlays over it.
Recently, I needed to find the frame of the image inside an ImageView, to add touchable views over that image, this is how I did it:
-(void)calculateScaleAndContainerFrame{
if(!imageView || !image) return;
CGSize imageSize = image.size;
CGSize imageViewSize = imageView.frame.size;
float imageRatio = imageSize.width / imageSize.height;
float viewRatio = imageViewSize.width / imageViewSize.height;
if(imageRatio > viewRatio){
scale = imageSize.width / imageViewSize.width;
}else{
scale = imageSize.height / imageViewSize.height;
}
CGRect frame = CGRectZero;
frame.size = CGSizeMake(roundf(imageSize.width / scale), roundf(imageSize.height / scale));
frame.origin = CGPointMake((imageViewSize.width - frame.size.width) / 2.0, (imageViewSize.height - frame.size.height) / 2.0);
[container setFrame:frame];
}
I'm pretty sure you can use it as a guide, replacing the imageViewSize with the content size of your scroll view (or the view you want to put your image in).
Note 1: In my case, I needed to center the view vertically, if you don't, just set the y to 0 on the line where I set the frame origin. Same for x if you don't want to center the image horizontally.
Note 2: This is NOT, by any means, a code you can just plug in into your project and work, you'll probably have to read it, understand it, and then apply the method to your own project. I don't have time right now to modify it to your needs.
Note 3: With that code I managed to get a view perfectly over the image inside a image view that used that content mode, so it works.
Related
I've an UIImageView with content mode Aspect Fit of size 220x155. I'm dynamically inserting different images in different resolutions, but all larger than the size of the UIImageView. As the content mode is set to Aspect Fit, the image is scaled with respect to the ratio to fit the UIImageView.
My problem is, that if for instance the image inside the UIImageView is scaled to 220x100, I would like the UIImageView to shrink from a height of 155 to 100 too to avoid space between my elements.
How can I do this?
I wrote this method to get me the frame of the image view once it loaded an image.
So , the requirements for me were the same as in your case:
1) image view with aspect fit content mode
2) get the exact frame of the image ( this way you can re-position the image view )
Hope this helps:
- (CGRect) getFrameOfImage:(AsyncImageView *) imgView
{
if(!imgView.loaded)
return CGRectZero;
CGSize imgSize = imgView.image.size;
CGSize frameSize = imgView.frame.size;
CGRect resultFrame;
if(imgSize.width < frameSize.width && imgSize.height < frameSize.height)
{
resultFrame.size = imgSize;
}
else
{
float widthRatio = imgSize.width / frameSize.width;
float heightRatio = imgSize.height / frameSize.height;
float maxRatio = MAX (widthRatio , heightRatio);
NSLog(#"widthRatio = %.2f , heightRatio = %.2f , maxRatio = %.2f" , widthRatio , heightRatio , maxRatio);
resultFrame.size = CGSizeMake(imgSize.width / maxRatio, imgSize.height / maxRatio);
}
resultFrame.origin = CGPointMake(imgView.center.x - resultFrame.size.width/2 , imgView.center.y - resultFrame.size.height/2);
return resultFrame;
}
I am using here AsyncImageView but it will work just as good with UIImageView. The important thing to remember is to call this method AFTER the image was loaded.
Cheers!
Its very simple, you just need to get image actual size, which can be done by
UIImage *image = [UIImage imageName:#""];
then you just need to set frame
Like :-
imageView.frame = CGRectMake(0.0, 0.0, image.size.width, image.size.height);
Hope this helps you.
Once the imageview's image is set to the new image (and thus scaled) you can get the height of the image inside the imageview (imageview.image.size.height) and set the imageview's height (frame) accordingly.
I have an image inside an UIImageView which is within a UIScrollView. What I want to do is rotate this image 90 degrees so that it is in landscape by default, and set the initial zoom of the image so that the entire image fits into the scrollview and then allow it to be zoomed up to 100% and back down to minimum zoom again.
This is what I have so far:
self.imageView.transform = CGAffineTransformMakeRotation(-M_PI/2);
float minimumScale = scrollView.frame.size.width / self.imageView.frame.size.width;
scrollView.minimumZoomScale = minimumScale;
scrollView.zoomScale = minimumScale;
scrollView.contentSize = CGSizeMake(self.imageView.frame.size.height,self.imageView.frame.size.width);
The problem is that if I set the transform, nothing shows up in the scrollview. However if I commented out the transform, everything works except the image is not in the landscape orientation that I want it to be!
If I apply the transform and remove the code that sets the minimumZoomScale and zoomScale properties, then the image shows up in the correct orientation, however with the incorrect zoomScale and seems like the contentSize property isn't set correctly either - since the doesn't scroll to the edge of the image in the left/right direction, however does top and bottom but much over the edge.
NB: image is being loaded from a URL
Maybe rotating the image itself fits your needs:
UIImage* rotateUIImage(const UIImage* src, float angleDegrees) {
UIView* rotatedViewBox = [[UIView alloc] initWithFrame: CGRectMake(0, 0, src.size.width, src.size.height)];
float angleRadians = angleDegrees * ((float)M_PI / 180.0f);
CGAffineTransform t = CGAffineTransformMakeRotation(angleRadians);
rotatedViewBox.transform = t;
CGSize rotatedSize = rotatedViewBox.frame.size;
[rotatedViewBox release];
UIGraphicsBeginImageContext(rotatedSize);
CGContextRef bitmap = UIGraphicsGetCurrentContext();
CGContextTranslateCTM(bitmap, rotatedSize.width/2, rotatedSize.height/2);
CGContextRotateCTM(bitmap, angleRadians);
CGContextScaleCTM(bitmap, 1.0, -1.0);
CGContextDrawImage(bitmap, CGRectMake(-src.size.width / 2, -src.size.height / 2, src.size.width, src.size.height), [src CGImage]);
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
I believe the easiest way (and thread safe too) is to do:
//assume that the image is loaded in landscape mode from disk
UIImage * LandscapeImage = [UIImage imageNamed: imgname];
UIImage * PortraitImage = [[UIImage alloc] initWithCGImage: LandscapeImage.CGImage
scale: 1.0
orientation: UIImageOrientationLeft];
Any calculations that you do based on the imageView's frame should probably be done before you apply any transformations to it. But I would actually suggest doing those calculations based on the size of the UIImage, not the UIImageView. Then set both the UIImageView's frame and the UIScrollView's contentSize based on that.
Max's suggestion is a good one, although with a larger image it could be a performance killer. Are you displaying this image from your app's resources? If so, why not just rotate the images before you even build the app?
There's a much easier solution that is also faster, just do this:
- (void) imageRotateTapped:(id)sender
{
[UIView animateWithDuration:0.33f animations:^()
{
self.imageView.transform = CGAffineTransformMakeRotation(RADIANS(self.rotateDegrees += 90.0f));
self.imageView.frame = self.imageView.superview.bounds; // change this to whatever rect you want
}];
}
When the user is done, you will need to actually create a new rotated image, but that is very easy to do.
I was using the accepted answer for a while until we noticed that non-square rotations based on images taken directly from the camera seemed stretched (they were rotated as desired, just the frame width/height wasn't adjusted).
Great explanation/post here from Trevor: http://vocaro.com/trevor/blog/2009/10/12/resize-a-uiimage-the-right-way/
In the end, it was a very simple import of Trevor's code which uses categories to add a resizedImage:interpoationQuality method to UIImage. So yeah, user beware, if it still works for you, great. But if it doesn't, I'd take a look at the library instead.
I have a UIButton in my iPhone app. I set its size to 100x100.
I have an image that is 400x200 that I wish to display in the button.
The button STILL needs to stay at 100x100... and I want the image to downsize to fit... but
keep the correct aspect ratio.
I thought that's what "Aspect Fit" was used for.
Do I include my image with setImage, or setBackgroundImage?
Do I set AspectFit for the button? or the image? or the background image?
(I need the smaller images to INCREASE in size. While larger images should DESCREASE in size.
Always keeping the button at 100x100.)
I've tried countless combinations of all of the above... and I just can't seem to get
everything to work at the same time:
Don't change the button's size of 100x100.
Don't destroy the image's aspect ratio.
Always increase small images to fit the button.
Always decrease large images to fit the button.
Never clip any of the image edges.
Never require the "put UIButtons over all your UIimages" hack.
Don't require everyone to upgrade to v4.2.1 just to use new framework methods.
I see so many apps, with so many fully-working image-buttons... that I can't believe I can't figure out this very simple, very common thing.
Ugh.
UIButton is broken. That's the short answer. The UIImageViews in its image and backgroundImage properties don't respect UIViewContentMode settings. They're read-only properties, and while the UIImage contents of those UIImageViews can be set through setImage: and setBackgroundImage: methods, the content mode can't be.
The solution is either to provide properly-sized images in your bundle to begin with, or to put a UIImageView down, configure it the way you want it, and then put a clear "custom" UIButton over top of it. That's the hack all those fancy professional apps you've seen have used, I promise. We're all having to do it.
UIImage *img = [UIImage imageNamed:#"yourImageName"];
button.imageView.contentMode = UIViewContentModeScaleAspectFit;
[button setImage:img forState:UIControlStateNormal];
To do this correctly, I would actually programmatically resize and manipulate the image to get the desired aspect ratio. This avoids the need for any view hierarchy hacks, and also reduces any performance hit to a single operation, instead of every redraw.
This (untested) code should help illustrate what I mean:
CGSize imageSize = image.size;
CGFloat currentAspect = imageSize.width / imageSize.height;
// for purposes of illustration
CGFloat targetWidth = 100;
CGFloat targetHeight = 100;
CGFloat targetAspect = targetWidth / targetHeight;
CGFloat newWidth, newHeight;
if (currentAspect > targetAspect) {
// width will end up at 100, height needs to be smaller
newWidth = targetWidth;
newHeight = targetWidth / currentAspect;
} else {
// height will end up at 100, width needs to be smaller
newHeight = targetHeight;
newWidth = targetHeight * currentAspect;
}
size_t bytesPerPixel = 4;
// although the image will be resized to { newWidth, newHeight }, it needs
// to be padded with empty space to provide the aspect fit behavior
//
// use calloc() to clear the data as it's allocated
void *imageData = calloc(targetWidth * targetHeight, bytesPerPixel);
if (!imageData) {
// error out
return;
}
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
if (!colorSpace) {
// error out
return;
}
CGContextRef context = CGBitmapContextCreate(
imageData,
targetWidth,
targetHeight,
8, // bits per component
targetWidth * bytesPerPixel, // bytes per row
colorSpace,
kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst
);
CGColorSpaceRelease(colorSpace);
// now we have a context to draw the original image into
// in doing so, we want to center it, so prepare the geometry
CGRect drawRect = CGRectMake(
floor((targetWidth - newWidth) / 2),
floor((targetHeight - newHeight) / 2),
round(newWidth),
round(newHeight)
);
CGContextDrawImage(context, drawRect, image.CGImage);
// now that the bitmap context contains the aspect fit image with transparency
// letterboxing, we want to pull out a new image from it
CGImageRef newImage = CGBitmapContextCreateImage(context);
// destroy the temporary context
CGContextRelease(context);
free(imageData);
// and, finally, create a new UIImage
UIImage *newUIImage = [UIImage imageWithCGImage:newImage];
CGImageRelease(newImage);
Let me know if any part of that is unclear.
I think what Dan is trying to say (but without ever saying it) is to do this:
Use a "temp image" to do the resizing for you.
The temp-image needs to be set to ASPECT FIT and HIDDEN.
Make sure your button is set to your desired size, and NOT set to ASPECT FIT.
// Make a frame the same size as your button
CGRect aFrame = CGRectMake(0, 0, myButton.frame.size.width, myButton.frame.size.height);
// Set your temp-image to the size of your button
imgTemp.frame = aFrame;
// Put your image into the temp-image
imgTemp.image = anImage;
// Copy that resized temp-image to your button
[myButton setBackgroundImage:tempImage forState:UIControlStateNormal];
Since none of my attempts have worked....
Maybe I should be asking this instead. When using a UIButton:
When DO I use setImage instead of setBackgroundImage? (Why are there both?)
When DO I use "Aspect Fit" instead of "Center"? (Why do both seem to stretch my images when I expect them to "keep aspect ratio" and "don't resize anything", respective.)
And the big question: Why is such a common thing... such a huge mess?
It would all be solved if I could find a work-around method like: Just use UIImage instead and detect TAPS. (But that seems to be even a LARGER nightmare of code.)
Apple, if you've tried to make my job easier... you have instead made it 400 times more confusing.
Place a imageview over the button, set your image for the imageview and not for button.
All the best.
I would resize the image to 100x100 maintaining the aspect ratio of the content contained in the image. Then set the backgroundImage property of the UIButton to the image.
I faced same issue few days back and resolved it. Please try with this
[_profilePicBtn setImage:profilePic forState:UIControlStateNormal];
_profilePicBtn.imageView.contentMode = UIViewContentModeScaleAspectFit;
I have an image that I want to load into an image view and set the minimumZoomScale, as well as the zoomScale to an aspectFill like scale that I calculate as follows:
// configure the map image scroll view
iImageSize = CGSizeMake(iImageView.bounds.size.width, iImageView.bounds.size.height);
iScrollView.minimumZoomScale = iScrollView.bounds.size.height / iImageSize.height;
iScrollView.maximumZoomScale = 2;
iScrollView.zoomScale = iScrollView.minimumZoomScale;
iScrollView.contentOffset = CGPointMake(0, 0);
iScrollView.clipsToBounds = YES;
The iScrollView size is 450 x 320px and the iImageSize is 1600 x 1960px.
Doing the minimumZoomScale math by hand: 450 / 1960 = 0.22959184.
The system determines 0.234693885 instead (???).
But to get the window fit into the 450px space, both figures don't work (!!!).
I manually tried and found 0.207 is the right number (that would translate to a image height of 2174 xp or alternatively a UIScrollView height of 406px).
For information: the UIScrollview screen is 450px, namely 480px minus status bar height (10), minus UITabBar height (20)
Any clues on this misbehaviour?
Not sure if you still have this problem, but for me, the scale is exactly the opposite. minimumZoomScale = 1 for me and I have to calculate the maximumZoomScale.
Here's the code:
[self.imageView setImage:image];
// Makes the content size the same size as the imageView size.
// Since the image size and the scroll view size should be the same, the scroll view shouldn't scroll, only bounce.
self.scrollView.contentSize = self.imageView.frame.size;
// despite what tutorials say, the scale actually goes from one (image sized to fit screen) to max (image at actual resolution)
CGRect scrollViewFrame = self.scrollView.frame;
CGFloat minScale = 1;
// max is calculated by finding the max ratio factor of the image size to the scroll view size (which will change based on the device)
CGFloat scaleWidth = image.size.width / scrollViewFrame.size.width;
CGFloat scaleHeight = image.size.height / scrollViewFrame.size.height;
self.scrollView.maximumZoomScale = MAX(scaleWidth, scaleHeight);
self.scrollView.minimumZoomScale = minScale;
// ensure we are zoomed out fully
self.scrollView.zoomScale = minScale;
For simple zoom in/out i generally use below code. minimumZoomScale= 1 sets initial zoom to current image size. I have given maximumZoomScale = 10 which can be calculated from actual ratio of image size and container frame size.
[scrollView addSubview: iImageView];
scrollView.contentSize = iImageView.frame.size;
scrollView.minimumZoomScale = 1.0;
scrollView.maximumZoomScale = 10;
[iImageView setFrame:CGRectMake(0, 0, 450, 320)];
[scrollView scrollRectToVisible:iImageView.frame animated:YES];
I am loading in images with varying sizes and putting them in UIScrollViews, all the images are larger than the UIScrollView.
The user can scroll and zoom as they please, but initially I would like for the image
to be centered and scaled so the largest side of the image aligns with the edge of the scrollView, i.e. if the picture is in landscape I would like to size and scale it so that the left and right side goes all the way to the edge of the UIScrollVIew and vice versa
I found a formula in a utility function in the Programming guide but it does not quite fit my needs.
My approach is to use:
CGrect initialPos = ?
[self.scrollView zoomToRect:initialPos animated:YES];
I know the size of my scrollView and the size of my image, what I need to figure out is the scale and CGRect to apply to the scrollView to center and size my image.
Hope someone can help out:) Thanks
Edit: previous version was sizing the image to the view rather than the other way around. I think this should correct that:
double imgRatio = imageSize.width / imageSize.height;
double viewRatio = viewSize.width / viewSize.height;
if ( imgRatio >= viewRatio )
{
initialPos.size.width = imageSize.width;
initialPos.size.height = imageSize.width / viewRatio;
initialPos.origin.x = 0;
initialPos.origin.y = (imageSize.height - initialPos.size.height) / 2;
}
else
{
initialPos.size.height = imageSize.height;
initialPos.size.width = imageSize.height * viewRatio;
initialPos.origin.y = 0;
initialPos.origin.x = (imageSize.width - initialPos.size.width) / 2;
}