iOS - can stretch a UIImageView but not a UIButton? - ios

Behold:
//UIImageView* stretchTest = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"image.png"]];
//[self addSubview:stretchTest];
UIButton *stretchTest = [UIButton buttonWithType:UIButtonTypeCustom];
[stretchTest setFrame:CGRectMake(0, 0, 400, 100)];
[stretchTest setBackgroundImage:[UIImage imageNamed:#"image.png"] forState:UIControlStateNormal];
[self addSubview:stretchTest];
stretchTest.contentStretch = CGRectMake(0.5, 0.5, 0, 0);
stretchTest.contentMode = UIViewContentModeScaleToFill;
CGRect frame = stretchTest.frame;
frame.size.height = 300;
stretchTest.frame = frame;
Using the UIImageView (commented out above), the image stretches appropriately - rounded corners maintain the correct radius, because only the center pixel of the image gets stretched.
Using the UIButton, the image gets stretched incorrectly. The corner radii are not maintained and it gets ugly.
Both UIImageView and UIButton are subclasses of UIView. Why does the button resize differently than the imageView?

You're making assumptions about the way UIButton works. It's not implemented the same way as UIImageView. UIImageView is just a view, with no subviews, and its contents is the image. UIButton is different, and the way it works is a private implementation detail.
If you're trying to stretch an image appropriately on a button, you should use -[UIImage stretchableImageWithLeftCapWidth:topCapHeight:] to get a UIImage that knows how it should be stretched. If you want to stretch just the middle pixel, you can use something like
UIImage *image = [UIImage imageNamed:#"image.png"];
image = [image stretchableImageWithLeftCapWidth:floorf(image.size.width/2) topCapHeight:floorf(image.size.height/2)];

A UIButton has two types of images it can display -- a foreground image and a background image. The background image for a button is expected to replace the button's background texture. As such, it will stretch to fill the entire background. The button's foreground image is expected to be an icon that may or may not display alongside text; it will not stretch. It may shrink if the frame is smaller than the image, but it will not stretch.
A button's foreground and background image can be set in code like this:
// stretchy
[self setBackgroundImage:backgroundImage forState:UIControlStateNormal];
// not stretchy
[self setImage:forgroundImage forState:UIControlStateNormal];
By default, the backgroundImage of a button will use scaleToFill to stretch the image. If you need the image to stretch using cap insets though, you should set them on the image before assigning it to the backgroundImage, like this:
UIImage *image = [UIImage imageNamed:#"bg_image.png"];
/* This assumes your image will have a 1px column and 1px row of pixels
in the horizontal and vertical middle of the image that should be
stretchable. If that's not the case (such as asymetrical buttons)
you need to adjust the caps */
image = [image stretchableImageWithLeftCapWidth:floorf(image.size.width/2)
topCapHeight:floorf(image.size.height/2)];
[self setBackgroundImage:image forState:UIControlStateNormal];

Related

IOS Button border thickness lssue,why?

UIButton top border appears thicker than the following ,but sometimes correct ,why?
code:
UIImage * sanImage = [UIimage imageNamed:#"product_bt1_normal"];
[self.saveBtn setBackgroundImage:[sanImage
stretchableImageWithLeftCapWidth:sanImage.size.width/3
topCapHeight:sanImage.size.height/3] forState:UIControlStateNormal];
Are you trying to make a button? If so, perhaps use a UIButton instead? You can control the border with button.layer.borderWidth = 1.0f
If you're set on using an image, create a UIImageView, and modify the border thickness that way:
UIImageView *iv = [[UIImageView alloc] initWithImage:sanImage];
[iv.layer setBorderWidth:0.5f];
It could be because of off-pixel boundaries. Since you are using height/3.0f, your image is maybe not returning a well-behaved image.
Also, there is a new stretchable image method you should be using, resizableImageWithCapInsets:.
So try this code out:
[self.saveBtn setBackgroundImage:[sanImage resizableImageWithCapInsets:UIEdgeInsetsMake(3.0f, 3.0f, 3.0f, 3.0f)] forState:UIControlStateNormal];
You might need to mess with the values for the insets a bit, I don't know the dimensions of your button image.

Scaling down a UIButton's background image when using initWithFrame:

This is the first time I have ever designed an iOS app so I want to make sure I understand this behavior correctly.
I designed a custom bar button icon for a navigation bar in Photoshop. The final image that I saved in Photoshop was 102 x 45, and yes I realize that these dimensions are bigger than the recommended 44x44 in the iOS 7 design guidelines.
Anyways, I placed my image into the asset folder, and then programmatically set the bar button item with the following code:
UIImage* firstButtonImage = [UIImage imageNamed:#"loginbutton1"];
CGRect frame = CGRectMake(0, 0, 102, 45);
UIButton * someButton = [[UIButton alloc] initWithFrame:frame];
[someButton setBackgroundImage:firstButtonImage forState:UIControlStateNormal];
[someButton addTarget:self action:#selector(didTapLoginButton:)
forControlEvents:UIControlEventTouchUpInside];
self.rightBarButton = [[UIBarButtonItem alloc] initWithCustomView:someButton];
self.navItem.rightBarButtonItem = self.rightBarButton;
As you can see I set the frame's width and height to the exact size of the image. When I first ran the app, I didn't like the image and thought it was too big. So I changed the width and height parameters in this statement:
CGRect frame = CGRectMake(0, 0, 70, 30);
And now the image looks perfect on the iPhone screen. This is on an iPhone 4s.
So my main question is, what is actually happening when I change the frame size? Since the frame is now smaller than the actual image size, does the image just get scaled down automatically to fit inside the frame?
Yes the image get scaled because you are using backgroundImage (not Image). Both images have different behaviors.
Check the Xcode Interface Builder, you can see there, that you can set two images: Image and Background. Background is the UIImage that get scaled for the whole frame of the UIButton.
The UIButton Class Reference allows you to access the imageView of the image (not theimageView of the backgroundImage)
Because you have access to the imageView, you can change the mode of the image with:
[[someButton imageView] setContentMode:UIViewContentModeBottomLeft];
In UIView Class Reference you can check all the UIViewContentModes provided by Apple.
You can check that changing a little bit your code:
[someButton setImage:firstButtonImage forState:UIControlStateNormal];
[[someButton imageView] setContentMode:UIViewContentModeBottomRight];

UIButton shrinking image?

I am working on an iPad UI and a button on that UI needs to have this image:
http://imgur.com/tVkP8wd
(as a PNG). The button is being declared like this:
CGRect newNoteButtonRect = CGRectMake(0, 0, 69, 43);
UIButton* newNoteButton = [[UIButton alloc] initWithFrame:newNoteButtonRect];
newNoteButton.imageView.contentMode = UIViewContentModeScaleToFill;
[newNoteButton setImage:self.fNewNoteIcon forState:UIControlStateNormal];
where 'fNewNoteIcon' is a UIImage. When the UI comes up, the image is tiny and squished, and almost nothing I do can change that. Any ideas?
The icon is initialized like this:
self.fNewNoteIcon = [UIImage imageNamed:#"New_Note.png"];
In that provided code you haven't specified a UIButton type? Creating a button with a custom image
// Create image
self.fNewNoteIcon = [UIImage imageNamed:#"buttomImage.png"];
// Create rect for button. 0, 0 and size it from image
CGRect newNoteButtonRect = CGRectMake:(0, 0, _fNewNoteIcon.size.width, _fNewNoteIcon.size.height);
// Alloc and init UIButton with type
// Pass in image. Add to subview of view.
UIButton *newNoteButton = [UIButton buttonWithType: UIButtonTypeCustom];
[newNoteButton setImage: _fNewNoteIcon forState: UIControlStateNormal];
[self.view addSubview: newNoteButton];
I type all of this out in SO without compiling so check over but that should get you a UIButton, with the required image at the correct size.
So it looks like if your PNG doesn't have an Alpha channel, then you'll have size issues. I opened the PNG in Photoshop and added an Alpha channel. The image is now properly sized in the iPad!

UIButton with left+right caps and a middle pattern

How can I create a UIButton with a background image which is composed by:
A fixed left cap
A fixed right cap
A number of middle images placed one after the other to fill all the available space
like in the example below?
EDIT: I did not realize that in the center there are no N repeated images, but only a streched one. See the accepted answer.
As far as I know it cannot be done. What you can do is stretch an image, but you cannot add n middle images.
The code for adding a stretchable image in between is
//Create an image - Where UIEdgeInsets is in top left bottom right
UIImage* buttonImage = [[UIImage imageNamed:#"button.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 16, 0, 16)];
// Create a custom buttom
UIButton *myButton = [UIButton buttonWithType:UIButtonTypeCustom];
myButton.frame = CGRectMake(0, 0, 100, buttonImage.size.height);
[myButton setBackgroundImage:buttonImage forState:UIControlStateNormal];
[myButton setTitle:#"Button" forState:UIControlStateNormal];
//Add it to view - if it is a view controller self.view
[self addView:myButton];
From Apple's UIImage Class Reference:
"resizableImageWithCapInsets:
You use this method to add cap insets to an image or to change the existing cap insets of an image. [...] During scaling or resizing of the image, areas covered by a cap are not scaled or resized. Instead, the pixel area not covered by the cap in each direction is tiled, left-to-right and top-to-bottom, to resize the image." [emphasis added]
You can read more about it at http://mobiledevelopertips.com/user-interface/ios-5-uiimage-and-resizableimagewithcapinsets.html

How do I prevent a button's background image from stretching?

I'm allocating a UIButtonTypeCustom to a UIView with a background image that is smaller than the button's frame.
Reason why the image is smaller is because I'm trying to add more of a "target area" for the UIButton. However, the image is being scaled to the full size of the frame, rather than just being the image's size.
I have tried setting the UIButton and UIButton's imageView's contentMode property to UIViewContentModeScaleAspectFit, but no luck, the image still gets stretched out.
Is there a way to do what I'm trying to do programmatically?
Thanks in advance!
A lot of people make the same mistake you do in regards to button images and then jump through hoops trying to make the button behave as they expect it to. Let's clear this up once and for all:
A UIButton has two types of images it can display -- a foreground image and a background image. The background image for a button is expected to replace the button's background texture. As such, it makes sense that it stretches to fill the entire background. However, the button's foreground image is expected to be an icon that may or may not display alongside text; it will not stretch. It may shrink if the frame is smaller than the image, but it will not stretch. You can even set the alignment of the foreground image using the Control alignment properties in Interface Builder.
A button's foreground and background image can be set in code like this:
// stretchy
[self setBackgroundImage:backgroundImage forState:UIControlStateNormal];
// not stretchy
[self setImage:forgroundImage forState:UIControlStateNormal];
You don't have access to the background imageView, but there is fully working workaround:
EDIT: There is an even better workaround then what I posted originally.
You can create a UIImage from any color, and call -setBackgroundImage:forState.
See bradley's answer, here:
https://stackoverflow.com/a/20303841/1147286
Original answer:
Instead of calling -setBackgroundImage:forState:, create a new UIImageView and add it as a subview of the button.
UIImageView *bgImageView = [[UIImageView alloc] initWithImage:img];
bgImageView.contentMode = UIViewContentModeScaleAspectFit;
[bgImageView setFrame:CGRectMake(0, 0, videoButton.frame.size.width, videoButton.frame.size.height)];
bgImageView.tag = 99;
[yourButton addSubview:bgImageView];
[yourButton bringSubviewToFront:yourButton.imageView];
Create the imageview
Set the content mode and frame
I also set a recognizable tag, so that when the screen rotates I can easily find my custom imageView in the button's subviews and reset its frame
Add it as a subview to the button
Bring the frontal imageView of the button to the front so our custom imageView doesn't overlap it
When the button needs to rotate just find the imageView by its tag and reset its frame:
UIImageView *bgImageView = (UIImageView *)[button viewWithTag:99];
[bgImageView setFrame:CGRectMake(0, 0, newWidth, newHeight)];
The cleanest and easiest way it probably to use the title insets of the button.
You set your image as the button image, and then you change the left title inset to match minus the width of your image:
myButton.titleEdgeInsets = UIEdgeInsetsMake(0, -myImage.width, 0, 0)
This will move the text back where it was before the image was added to its left. You can also use this value to add some padding to you button.
Stumbled on this problem too.
Adding image programmatically, as memmons thoroughly explained, did not help:(
I had a button 100x40 and image 100x100, it would appear squeezed, not fitted, as one would infer from "Aspect Fit" option. Actually, non of those view options had an effect.
I just had to rescale it so it would fit on a button, then use setImage:
UIImage *img=[UIImage imageNamed:#"myimage.png"];
CGImageRef imgRef = [img CGImage];
CGFloat imgW = CGImageGetWidth(imgRef);
CGFloat imgH = CGImageGetHeight(imgRef);
CGFloat btnW = myBttn.frame.size.width;
CGFloat btnH = myBttn.frame.size.height;
//get lesser button dimension
CGFloat minBtn=btnW;
if (btnW>btnH) {
minBtn=btnH;
}
//calculate scale using greater image dimension
CGFloat scl=imgH/minBtn;
if (imgW>imgH) {
scl=imgW/minBtn;
}
//scale image
UIImage *scaledImage = [UIImage imageWithCGImage:[img CGImage] scale:(img.scale * scl) orientation:(img.imageOrientation)];
//clean up
UIGraphicsEndImageContext();
//set it on a button
[myBttn setImage:scaledImage forState:UIControlStateNormal];
It is simple as:
ImageBtn.imageView?.contentMode = .scaleAspectFit
ImageBtn.setImage(chosenImage, for: .normal)
Another consideration is the BaseLine constraint. If your buttons have this constraint set (depicted as a horizontal or vertical line through multiple controls on your layout), it will cause your images to stretch without stretching the underlying button control. If your button is otherwise properly constrained (leading/trailing and top/bottom spaces, and etc...) removing the BaseLine constraint should have no impact on the layout, while allowing the foreground image to scale properly to the underlying button shape.
Answerbot answers the question with what is proper and correct to do. Don't fight the OS and use things as intended is always good advice. However, sometimes you need to break the rules.
I was able to mask the enlarged background image (not prevent it) by overlaying it with a black CAlayer then overlaying again with a properly resized image CAlayer. This was all done by creating a subclass of UIButton and overwriting the setHighlighted method.
NEED CODE?
- (void)setHighlighted:(BOOL)highlighted
{
super.highlighted = highlighted;
//
//Whenever an image needs to be highlighted, create a dimmed new image that is correctly sized. Below it is a englarged stretched image.
//
if (highlighted != _previousHighlightedSate)
{
_previousHighlightedSate = highlighted;
if (highlighted)
{
//Create a black layer so image can dim
_blackLayer = [CALayer layer];
_blackLayer.bounds = self.bounds;
CGRect rect = _blackLayer.bounds;
rect.size.width = rect.size.width*2;
rect.size.height = rect.size.height*2;
_blackLayer.bounds = rect;
_blackLayer.backgroundColor = [[UIColor blackColor] CGColor];
//create image layer
_nonStretchImageLayer = [CALayer layer];
_nonStretchImageLayer.backgroundColor = [UIColor blackColor].CGColor;
_nonStretchImageLayer.bounds = CGRectMake(0 , 0, self.bounds.size.width, self.bounds.size.height);
_nonStretchImageLayer.frame = CGRectMake(0 , 0, self.bounds.size.width, self.bounds.size.height);
_nonStretchImageLayer.contentsGravity = kCAGravityResizeAspect;//default is to resize
_nonStretchImageLayer.contents = (id)self.imageView.image.CGImage;
_nonStretchImageLayer.opacity = 0.5;
//add layers to image view
[self.imageView.layer addSublayer:_blackLayer];
[self.imageView.layer addSublayer:_nonStretchImageLayer];
}
else
{
//remove from image view
[_blackLayer removeFromSuperlayer];
[_nonStretchImageLayer removeFromSuperlayer];
//nil them out.
_blackLayer = nil;
_nonStretchImageLayer = nil;
}
}
Inspiration for this work around came from here
I guess the easiest solution is to use UIImage's resizableImageWithCapInsets method. Use UIEdgeInsetsMake to configure the free spaces.
might help someone
button.subviews.first?.contentMode = .scaleAspectFit
Swift version of Zoltán Matók answer
Just a copy of my code using SnapKit to do auto layout and syntatic sugar Then library for initilizations, it should work similar for normal Apples way of programatic layout.
let backButton = UIButton(type: .custom).then { (button) in
let image = UIImage(named: "backButtonIcon")?.withRenderingMode(.alwaysOriginal)
let imageView = UIImageView(image: image)
button.addSubview(imageView)
imageView.snp.makeConstraints { (make) in
make.center.equalTo(button.snp.center)
}
button.bringSubviewToFront(imageView)
button.contentMode = .scaleAspectFit
button.backgroundColor = .clear
button.isUserInteractionEnabled = true
button.isEnabled = true
button.imageView?.contentMode = .scaleAspectFit
button.imageView?.snp.makeConstraints({ (make) in
make.height.width.equalTo(24)
})
button.snp.makeConstraints { (make) in
make.height.width.equalTo(24)
}
}
You can use uibutton.imageView.contentMode for no stretching:
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
button.frame = CGRectMake(posX, posY, widthButton, heightButton);
[button setTitle:#"" forState:UIControlStateNormal];
[button setImage:[UIImage imageNamed:#"imageNamed"] forState:UIControlStateNormal];
[button setImage:[UIImage imageNamed:#"imageNamedHighlighted"] forState:UIControlStateHighlighted];
button.imageView.contentMode = UIViewContentModeScaleAspectFit;
[button addTarget:self action:#selector(functionMenu:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview: button];
What you need to do is add your image as a UIImageView.
Than add a button with transperent background (UIColor ClearColor) on top of it with your desired width and height.

Resources