Problems scaling a UIImage to fit a UIButton - objective-c

I have a set of buttons and different sized images. I want to scale each image in order that it fits the button in the correct aspect ratio. Once I've scaled the image, I set the button's image property to the scaled version.
UIImage *scaledImage = [image scaledForButton:pickerButton];
[pickerButton setImage:scaledImage forState:UIControlStateNormal];
my scaledForButton method is defined in a class extension for UIImage. It looks like this:
- (UIImage *)scaledForButton:(UIButton *)button
{
// Check which dimension (width or height) to pay respect to and
// calculate the scale factor
CGFloat imageRatio = self.size.width / self.size.height;
CGFloat buttonRatio = button.frame.size.width / button.frame.size.height;
CGFloat scaleFactor = (imageRatio > buttonRatio ? self.size.width/button.frame.size.width : self.size.height/button.frame.size.height);
// Create image using scale factor
UIImage *scaledimage = [UIImage imageWithCGImage:[self CGImage]
scale:scaleFactor
orientation:UIImageOrientationUp];
return scaledimage;
}
When I run this on an iPad2 it works fine and the images are scaled correctly. However if I run it on a retina display (both in the simulator and on a device) the image does not scale correctly and is squished into the button.
Any ideas why this would happen on retina only? I've been scratching my head for a couple of days but can't figure it out. They're both running the same iOS and I've checked the scale and ratio outputs, which are always the same, regardless of device. Many thanks.

Found the answer here: UIButton doesn't listen to content mode setting?
If you're setting the .contentMode, it seems you have to set the imageView property of the UIButton, not just the UIButton, then it worked properly.
The problem on iPad 3 was as Herman suggested - the CGImage was still a lot larger than the UIButton, so even though it was scaled down, it still had to be resized to fit the button.

Related

Make image smaller inside UIImageView

I have an UIImageView that shows and image of a creditCard, My problem is that I want to make boarders around it, so the credit card wont touch the edges of the UIimageView, I dont want to change the UIImageView position on the screen so what can I do ?
image 1
image 2
( unlike the example of the blue card it dosnt have to leave spaces only from the sides, it could shrink it from all sides )
You basically have (3) three options that can fix this.
Adjust the UIViewContentMode contentMode of the UIImageView so that it appropriately displays the image (this will only work however if the image you are using scales to fit without touching the border.
Use a UIButton instead and simply adjust the image insets until your satisfied (just disable the button or set user interaction to no here to "mimic" an image view)
Create a wrapper method that creates a new image to your desired size. Something like this should work just fine
CGSize size = CGSizeMake(< some width > , < some height >);
Then just create a new image capture with the size you've chosen
CGFloat scale = MAX(size.width/image.size.width, size.height/image.size.height);
CGFloat width = image.size.width * scale;
CGFloat height = image.size.height * scale;
CGRect imageRect = CGRectMake((size.width - width)/2.0f,
(size.height - height)/2.0f,
width,
height);
UIGraphicsBeginImageContextWithOptions(size, NO, 0);
[image drawInRect:imageRect];
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
From here you should then be able to just apply a border width and color to get your desired result!
I guess a fourth (4th) option could be manually shrinking the image (with some photo editor) so that it renders (fits) into your image view with the appropriate padding around the image. I think the others are easier and best practice though.

Get image from AVCaptureVideoPreviewLayer

I'm essentially cloning Cropping a captured image exactly to how it looks in AVCaptureVideoPreviewLayer since asking the original poster if they found a solution isn't an "answer" and I am unable to comment yet because I don't have enough reputation...
The app I'm building will always be in portrait mode because rotation isn't important in this case.
I have an AVCaptureSession with the AVCaptureVideoPreviewLayer connected to a UIView of size 320x240 that is positioned against the top layout guide.
I have capturing the input working but the image that I'm receiving is skewed and shows a lot more than the portion I'm displaying. How can I capture just the area that is shown in my AVCaptureVideoPreviewLayer?
Have a look at AVCaptureVideoPreviewLayer s
-(CGRect)metadataOutputRectOfInterestForRect:(CGRect)layerRect
This method lets you easily convert the visible CGRect of your layer to the actual camera output.
One caveat: The physical camera is not mounted "top side up", but rather rotated 90 degrees to the right. (So if you hold your iPhone - Home Button right, the camera is actually top side up).
Keeping this in mind, you have to convert the CGRect the above method gives you, to crop the image to exactly what is on screen.
Example:
CGRect visibleLayerFrame = THE ACTUAL VISIBLE AREA IN THE LAYER FRAME
CGRect metaRect = [self.previewView.layer metadataOutputRectOfInterestForRect:visibleLayerFrame];
CGSize originalSize = [originalImage size];
if (UIInterfaceOrientationIsPortrait(_snapInterfaceOrientation)) {
// For portrait images, swap the size of the image because
// here, the output image is actually rotated relative to what you see on screen.
CGFloat temp = originalSize.width;
originalSize.width = originalSize.height;
originalSize.height = temp;
}
// metaRect is fractional, that's why we multiply here
CGRect cropRect;
cropRect.origin.x = metaRect.origin.x * originalSize.width;
cropRect.origin.y = metaRect.origin.y * originalSize.height;
cropRect.size.width = metaRect.size.width * originalSize.width;
cropRect.size.height = metaRect.size.height * originalSize.height;
cropRect = CGRectIntegral(cropRect);
This may be a bit confusing, but what made me really understand this is this:
Hold your device "Home Button right" -> You'll see the x - axis actually lies along the "height" of your iPhone, while the y - axis lies along the "width" of your iPhone. That's why for portrait images, you have to swap the size ;)

iOS 7 UITextView: Size of nstextattachment getting 2x after reopening the application

I am building a note editor using the Text Kit in ios7. Earlier I had trouble in rendering of custom size NSTextAttachment's as it was slowing down the rendering to a great extent.I solved the issue by scaling the images and then adding them to textview.You can find my answer in
iOS 7.0 UITextView gettings terribly slow after adding images to it
After scaling the images the textview rendering runs fine without any lag.The attributed text of textview is stored in core data.During a running session of application the textview displays the images correctly.Even after saving the attributed text in the core data and retrieving it again to display on textview,the images look fine.But after killing the app and again running the application.The images get enlarged to 2x size.while scaling the images I used the following function and used [[UIScreen bounds] scale] to maintain the image quality.
- (UIImage *)imageWithImage:(UIImage *)image scaledToSize:(CGSize)newSize {
UIGraphicsBeginImageContextWithOptions(newSize, NO, [UIScreen mainScreen].scale);
[image drawInRect:CGRectMake(0, 0, newSize.width, newSize.height)];
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
If I scale the images to 1.0 the images doesn't expand but the image quality is very bad.
What I Think where the problem lies?
The problem lies in the layout manager.
What I have Tried
I have tried subclassing the NSLayoutManager and overriding the
- (void)drawGlyphsForGlyphRange:(NSRange)glyphsToShow atPoint:(CGPoint)origin
What I see is the attachment size is doubling when running a new session of the application.If I try to check the size of attachment and resize it.The lag starts coming again.
I am stucked with this problem from a quite time.Any suggestions would be much appreciated.
Could the reason due to retina display? If it is retina, you might need to reduce the size by 50% before storing. How about trying this:-
//Original Size that you want to store
CGSize imageSize = CGSizeMake(320.0f, 320.0f);
//Make the image 50% of the size for retina
if ([[UIScreen mainScreen] respondsToSelector:#selector(displayLinkWithTarget:selector:)] &&([UIScreen mainScreen].scale == 2.0)) {
// Retina display
imageSize = CGSizeMake(160.0f, 160.0f);
}
UIImage * storeImage = [self imageWithImage:self.image scaledToSize:imageSize]
//TODO: Store this image locally or whatever you want to do.
#interface MMTextAttachment : NSTextAttachment
{
}
#end
#implementation MMTextAttachment
//I want my emoticon has the same size with line's height
- (CGRect)attachmentBoundsForTextContainer:(NSTextContainer *)textContainer proposedLineFragment:(CGRect)lineFrag glyphPosition:(CGPoint)position characterIndex:(NSUInteger)charIndex NS_AVAILABLE_IOS(7_0)
{
return CGRectMake( 0 , 0 , lineFrag.size.height , lineFrag.size.height );
}
#end
I think you can try this.

UIImageView, CGImage, and Retina Art

I've got a few CALayers in my interface, and I'm drawing images directly to the layers as opposed to imageViews.
Here's a snippet:
UIImage *anImage = [UIImage imageNamed:#"anyImage"];
CGImageRef anImageRef = [anImage CGImage];
CALayer *aLayer = [CALayer layer];
CGFloat anImageWidth = CGImageGetWidth(anImageRef);
CGFloat anImageHeight = CGImageGetHeight(anImageRef);
CGRect layerFrame = CGRectMake(0,0,anImageWidth, anImageHeight);
[aLayer setLayerContents:(__bridge id)anImageRef];
[parentLayer addSublayer:aLayer];
So my problem is that I'm getting inconsistent results with the size of the image. On the retina Device, the image that appears is double the size anticipated (e.g., it matches the pixel size of the #2x image). On the simulator in retina mode, the image drawn to the layer is the anticipated size (where points match the pixels of the non retina image).
Rather than statically set the size, or halve the size (which corrects the issue on the device but breaks compatibility with non-retina displays), what is a good solution or workaround to this scenario? Why is it happening?
The UIImage contains a scale property. It will be 2.0 for retina display images. See the docs for more info.
CGImageGetWidth() and CGImageGetHeight() return the number of pixels whereas you need the image size in points. Use -[UIImage size] instead.

How do I pan the image inside a UIImageView?

I have a UIImageView that is displaying an image that is wider and taller than the UIImageView is. I would like to pan the image within the view using an animation (so that the pan is nice and smooth).
It seems to me that I should be able to just adjust the bounds.origin of the UIImageView, and the image should move (because the image should paint inside the view with that as its origin, right?) but that doesn't seem to work. The bounds.origin changes, but the image draws in the same location.
What almost works is to change the contentsRect of the view's layer. But this begins as a unit square, even though the viewable area of the image is not the whole image. So I'm not sure how I would detect that the far edge of the image is being pulled into the viewable area (which I need to avoid, since it displays by stretching the edge out to infinity, which looks, well, sub-par).
My view currently has its contentsGravity set to kCAGravityTopLeft via Interface Builder, if that makes a difference (Is it causing the image to move?). No other options seemed to be any better, though.
UPDATE: to be clear, I want to move the image inside the view, while keeping the view in the same spot.
I'd highly recommend enclosing your UIImageView in a UIScrollView. Have the UIImageView display the full image, and set the contentSize on the UIScrollView to be the same as your UIImageView's size. Your window into the image will be the size of the UIScrollView, and by using scrollRectToVisible:animated: you can pan to particular areas on the image in an animated fashion.
If you don't want scroll bars to appear, you can set the showsHorizontalScrollIndicator and showsVerticalScrollIndicatorproperties to NO.
UIScrollView also provides pinch-zooming functionality, which may or may not be useful to you.
Brad Larson pointed me down the right road with his suggestion to put the UIImageView inside a UIScrollView.
In the end I put the UIImageView inside of a UIScrollView, and set the scrollView's contentSize and the imageView's bounds to be the same size as the image in the UIImage:
UIImage* image = imageView.image;
imageView.bounds = CGRectMake(0, 0, image.size.width, image.size.height);
scrollView.contentSize = image.size;
Then, I can animate the scrollView's contentOffset to achieve a nice panning effect:
[UIView beginAnimations:#"pan" context:nil];
[UIView setAnimationDuration:animationDuration];
scrollView.contentOffset = newRect.origin;
[UIView commitAnimations];
In my particular case, I'm panning to a random space in the image. In order to find a proper rect to pan to and a proper duration to get a nice constant speed, I use the following:
UIImage* image = imageView.image;
float xNewOrigin = [TCBRandom randomIntLessThan:image.size.width - scrollView.bounds.size.width];
float yNewOrigin = [TCBRandom randomIntLessThan:image.size.height - scrollView.bounds.size.height];
CGRect oldRect = scrollView.bounds;
CGRect newRect = CGRectMake(
xNewOrigin,
yNewOrigin,
scrollView.bounds.size.width,
scrollView.bounds.size.height);
float xDistance = fabs(xNewOrigin - oldRect.origin.x);
float yDistance = fabs(yNewOrigin - oldRect.origin.y);
float hDistance = sqrtf(powf(xDistance, 2) + powf(yDistance, 2));
float hDistanceInPixels = hDistance;
float animationDuration = hDistanceInPixels / speedInPixelsPerSecond;
I'm using a speedInPixelsPerSecond of 10.0f, but other applications might want to use a different value.