I'm using storyboards to layout my view controllers and I would like to use stretchable images for my buttons (so that I don't need to generate several images with different sizes).
Is this possible to do directly in storyboards without writing any code? I'm really liking the possibility to use storyboards for all graphic stuff and keep the code clean from UI stuff, but it seems like I can't get away with it here.
If it is not possible, what would you suggest otherwise?
Update for iOS 7+
iOS 7+ now supports stretchable images natively via the asset catalog. Using the asset catalog you can now specify how images are sliced and how they scale (stretch or tile). These asset catalog attributes for the image will be reflected immediately in storyboard. Great new improvement. For more info, see Apple's docs on the Asset Catalog
For deploying to iOS versions before 7:
It's a little known fact, but you can absolutely set cap insets of an image using only Interface Builder/Storyboard and the stretching properties in the attributes inspector. Thanks to Victor for the original answer.
Looking at the stretching properties in the attributes inspector of a UIImage
, the X and Y values are the positions for the stretch starting point, relative to the entire width and height of the image. A value of 0.5 would mean a point in the middle of the image.
The width and height are sizes for the stretchable area relative to the image size. So, setting the width to a value of 1 / imageWidth would set the stretchable area to be 1px wide.
Most stretchable images will stretch from the middle, so using these values for X,Y, Width, & Height will usually work:
X = 0.5
Y = 0.5
Width = 1/imageWidth
Height = 1/imageHeight
Note: Unless you have a very small image you are stretching, this means that width and height properties will be very small (e.g. 0.008) and 0.0 can be used instead. So, practically speaking, 0.5, 0.5, 0.0, 0.0 will almost always work for X,Y, Width & Height.
In the small number of cases that 0.0 does not work for Width and Height this does mean you need to use a calculator to set these values in IB. However, I think that is generally preferable than having to set it programmatically as you will be able to see the resulting stretched image in IB (WYSIWYG).
Update: Some people have pointed out that although stretching images works in Storyboard using the above suggestions, stretching images on buttons is still broken, even as of iOS7. Not to worry, this is easily addressed by creating a UIButton
category that takes care of setting the cap insets for control states:
@implementation UIButton (Stretchable)
/* Automatically set cap insets for the background image. This assumes that
the image is a standard slice size with a 1 px stretchable interior */
- (void)setBackgroundImageStretchableForState:(UIControlState)controlState
{
UIImage *image = [self backgroundImageForState:controlState];
if (image)
{
CGFloat capWidth = floorf(image.size.width / 2);
CGFloat capHeight = floorf(image.size.height / 2);
UIImage *capImage = [image resizableImageWithCapInsets:
UIEdgeInsetsMake(capHeight, capWidth, capHeight, capWidth)];
[self setBackgroundImage:capImage forState:controlState];
}
}
Using this category, you can set your stretchable image for your button via Storyboard and then easily ensure that it stretches properly by calling -setBackgroundImageStretchableForState:
in your -viewDidLoad
. Iterating through your view hierarchy makes it trivial to do this even for a large number of buttons in your view:
NSPredicate *predicate =
[NSPredicate predicateWithFormat:@"self isKindOfClass:%@",[UIButton class]];
NSArray *buttons = [self.view.subviews filteredArrayUsingPredicate:predicate];
for (UIButton *button in buttons)
[button setBackgroundImageStretchableForState:UIControlStateNormal];
While this isn't quite as good as having a UIButton
subclass which does this automatically for you (subclassing UIButton
isn't practical since it's a class cluster), it does give you nearly the same functionality with just a bit of boilerplate code in your viewDidLoad -- you can set all your button images in Storyboard and still get them to properly stretch.