I want to build a UIButton out of 3 images (A,B,C). A and C are stretchable components (depending on the button text) the middle part is fixed.
What is the best way to attach these images to one another? How can I be flexible in the implementation without specifying all origins and widths and so on... Is it necessary to have a surrounding rectangle?
You need to stitch your images together in an image editor to create a single image. Then you'll load this image, and create a resizable version using the UIImage resizableImageWithCapInsets:resizingMode: method. You'll specify UIEdgeInsets that mirror the original widths of your two "end cap" images.
For the button itself you need to make this resizable image the background image of the button. It won't work as a foreground image.
UIImage* bgImage = [[UIImage imageNamed:#"my_bg_image"] resizableImageWithCapInsets: UIEdgeInsetsMake(0, 10, 0, 10)];
UIButton* b = [UIButton buttonWithType: UIButtonTypeCustom];
[b setBackgroundImage: bgImage forState: UIControlStateNormal];
I've had to implement something like this recently.
My strategy was to build what I wanted using UIView objects, and then write the created view to a UIImage.
I'd recommend:-create a container view-create UILabels for the button text, add to container view-for labels A and C, sizeToFit them after they have their text, and then use that information to place the stretchable buttons in the view-once the subviews are positioned, you'll probably want to reset the frame of your container view to make sure it is still correct.
once your view looks the way you want it, write it to an image using:
UIGraphicsBeginImageContextWithOptions(container.frame.size, NO, 0); //or YES if you have no opacity in your images
[container.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *img = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
-you can then set this as the image for your UIButton
The best way to do this is to put the 3 images into one file, and use resizableImageWithCapInsets:. See the doco here.
Here's a good tutorial on how to do this.
// Set the button background image
UIImage * buttonImage = [UIImage imageNamed:#"button_resizable.png"];
UIImage * resizableButtonImage = [buttonImage resizableImageWithCapInsets:UIEdgeInsetsMake(0, 12, 0, 12)];
[button setBackgroundImage:resizableButtonImage forState:UIControlStateNormal];
Related
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.
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];
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
I have a custom class inheriting from UITableViewCell class that shows either an image (left to the title) or a generic dark-colored square if the image is not available). The following code shows a dark square on a light-colored cell background:
imageView = [[UIImageView alloc] initWithFrame:CGRectMake(11, 6, 40, 40)];
[imageView setBackgroundColor:kBackgroundGreyColour];
[cell.contentView addSubview:imageView];
However, instead of creating a custom subview in each table cell I would rather like to use the existing imageView property of the generic UITableViewCell class and modify it somehow to show the square as the code above does. This is what I am trying at this moment:
UIImageView* iv = [[UIImageView alloc] initWithFrame:CGRectMake(11, 6, 40, 40)];
[iv setBackgroundColor:[UIColor redColor]];
self.imageView.hidden = NO;
self.imageView.opaque = iv.opaque;
self.imageView.alpha = iv.alpha;
self.imageView.image = iv.image;
[self bringSubviewToFront:self.imageView];
[self.imageView setBackgroundColor:[UIColor redColor]];
I added all those lines to set as many of the existing UIImageView properties to the same values as the created UIImageView instance in the first code snippet, and yet the second code snippet doesn't show any dark square. It just doesn't show anything at all and the cell looks like there is just the light background and no image view visible. But I see that the imageView property is not nil so executing all those lines of code in the second snippet should show something?
However, as soon as I assign a new image to the imageView property (e.g. self.imageView.image = [[UIImage alloc] init...], the square shows the assigned image without problems.
Edit: Just a note that in the second case I am setting the frame of the imageView in layoutSubview function, e.g.:
-(void)layoutSubviews
{
[super layoutSubviews];
self.imageView.frame = CGRectMake(11, 6, 40, 40);
}
So my questions are:
1. Which properties of the existing imageView property I would need to set and to what values so that the code will show a square filled with a specific color (like the first snippet of code does)?
Is there a way of creating the UIImage programatically so that it shows only a background color without any image associated with it (and which I could use to set the imageView.image property to show that color).
Is it possible to replace the existing imageView property in a UITableViewCell class with a custom view without adding a custom subview (like the first code snippet did), so that I can show a placeholder UIView with a background color when the image is not available?
The reason why your code doesn't work, is as you guessed; Because when you set the background colour of an imageview, it doesn't create anything on the image property.
And, you've figured out that you can't directly set the imageview property of the cell either.
I'd say your best bet, is the former option; To create a UIImage programmatically.
Although, I'd highly suggest simply creating one in your favourite image editing software then including it in the bundle. It makes for easy replacement later, for when you may get a better image, and next to no code and effort required to replace.
But if you still wish to do it all programmatically, it's not as simple as you'd hope.
CGRect rect = CGRectMake(11, 6, 40, 40);
UIGraphicsBeginImageContext(rect.size);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, [kBackgroundGreyColour CGColor]);
CGContextFillRect(context, rect);
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
self.imageview.image = image;
Should do the trick.
This defines the image size, creates a graphics context (think of it as a canvas), picks your grey colour to use, paints the canvas with it, then scans it into your computer into the small little size you wanted.
The little green imp does it all behind the screen (Sorry, too much Terry Pratchett).
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];