Using stretchable images in Xcode storyboards

39,108

Solution 1

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).

Stretchable image attributes

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.

Solution 2

With Xcode 6 (and iOS7+ target) you can use slicing editor when working with images assets. Toggle slicing mode with Editor -> Show Slicing menu or press Show Slicing button when select specific image with editor (showed below).

Show Slicing button

Then you can select image for specific display scale and drag rules or edit insets values manually.

Rules

After that you can select this image in Interface Builder. For example I use it for UIButton Background Image (IB button's representation could look bad, but it should be OK when running).

My buttons look well (running iOS 7.1 simulator and iOS 8 device).

enter image description here

This Apple doc link could be helpful.

Solution 3

It's doable in XCode's 5.0 Interface Builder with the assets catalog. Create an image asset ( if you don't already have an asset catalog you can create one as follows:

  1. File->New->File->Resources->Asset Catalog
  2. Then Editor->New Image Set
  3. Set the images for each Idiom & Scale
  4. Then hit the Show Slicing button to set the slices as you wish.

Check Apple docs here: Developer Apple: Asset Catalog Help Then all you have to do is to set the background image of the button as the requested asset.

EDIT: I've forgot to mention that it works as desired only in iOS7

Solution 4

Here's what I did: I set an outlet for the button and connected it, then did this in viewDidLoad:

[self.someButton setBackgroundImage:[[self.someButton backgroundImageForState:UIControlStateNormal] resizableImageWithCapInsets:UIEdgeInsetsMake(3, 3, 4, 3)] forState:UIControlStateNormal];

This makes it so it reuses the image you set in the storyboard, so if you change it from one color to another it will work, as long as the insets dont change.

For a view that had many of these things, I did this:

for (UIView * subview in self.view.subviews) {
        if ([subview isKindOfClass:[UIImageView class]] && subview.tag == 10) {
            UIImageView* textFieldImageBackground = (UIImageView*)subview;
            textFieldImageBackground.image = [textFieldImageBackground.image stretchableImageWithLeftCapWidth:7 topCapHeight:5];
        } else if([subview isKindOfClass:[UIButton  class]] && subview.tag == 11) {
            UIButton * button = (UIButton*)subview;
            [button setBackgroundImage:[[button backgroundImageForState:UIControlStateNormal] resizableImageWithCapInsets:UIEdgeInsetsMake(3, 3, 4, 3)] forState:UIControlStateNormal];
        }
    }

Note that I set the tags for all the ones I wanted stretched.

I'm in the same boat as you though, I'd love being able to set these very UI centric things on the storyboard itself.

Share:
39,108
Joel
Author by

Joel

Updated on March 30, 2020

Comments

  • Joel
    Joel about 4 years

    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?