Using stretchable images in Xcode storyboards - iphone

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.

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).
Then you can select image for specific display scale and drag rules or edit insets values manually.
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).
This Apple doc link could be helpful.

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:
File->New->File->Resources->Asset Catalog
Then Editor->New Image Set
Set the images for each Idiom & Scale
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

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.

Use the Xcode Slicing feature to specify the dimensions of a resizable center area of the image and to optionally specify end caps, which are areas of the image that should not be filled by the resizable area.
See About Asset Catalogs

Related

iOS background and images seem to not scale properly for retina display

When using the following code:
self.view.backgroundColor = [[UIColor alloc] initWithPatternImage:[UIImage imageNamed:#"texture1.png"]];
The texture background of my view gets set just fine. But I can see that the images are not scaled properly for a retina display, they seem more pixelated, rather than rich in texture and color as they should be. I have had this issue with other images too, when trying to make an image the size of the iphone 5 screen fade away, only a part of it would be seen, and the rest would be cut off, even though the resolution is fine. Any idea what I am missing here?
EDIT: Does it have to do with the default dpi of the images that I am using?
EDIT #2: Here is a screenshot:
On a side note, does anyone know if good background texture sources besides http://subtlepatterns.com/?
EDIT #3: Here is a good example of me attempting to use the ios-linen pattern
firstly remove .png from image name if you are use #2x image
self.view.backgroundColor = [[UIColor alloc] initWithPatternImage:[UIImage imageNamed:#"texture1"]];
Please check the image height and width for retain display ..
By Apple On devices with high-resolution screens, the imageNamed:, imageWithContentsOfFile:, and initWithContentsOfFile: methods automatically looks for a version of the requested image with the #2x modifier in its name. If it finds one, it loads that image instead. If you do not provide a high-resolution version of a given image, the image object still loads a standard-resolution image (if one exists) and scales it during drawing.
Instead of using initWithPatternImage you should add a UIImageView to the UIView . This is better because :-
The UIImageView will contain the image according to its specific width and height.
Also, the initWithPatternImage method consumes MORE MEMORY than the UIImageView.
I would prefer to use a UIImageView rather than the
initWithPatternImage:[UIImage imageNamed:#"texture1.png"]
If the image is still not clear you can add the #2x image to the UIImageView and you will see the clarity is more.

Stretching a portion of a UIView using contentStretch

I need some help understanding the use of the contentStretch property on a UIView. I'd appreciate 1) some guidance on how to achieve the effect I'm going for and 2) a reference to any comprehensive documentation that exists on how to use this property. While I thought I understood the Apple documentation, I now see that I don't. Here is what I'm trying to do.
I have a custom UIView that renders a view that looks like figure a. below. The drawRect method of this UIView paints the inner box on the left (with the rounded corners and white background) with a different color and affect than is used for the rest of the view. Also, the 4 corners of the UIView are not stretchable (this would result in distortion) so I need to stretch only the center of the UIView background.
I want to grow this view to a shape similar to that pictured in figure b. below. In doing so I want only the portion of the view outside the white box (or to the right of the white box) to be stretched/repeated as I increase the UIView's height.
In all of my attempts I have only been able to produce a strecth that affects the entire width of the view and results in a view similar to figure c.
Is it possible for me to achieve the effect that I want (figure b.) using contentStretch? Is there some other technique I should be using short of re-drawing the entire view, or is re-drawing the only (or best) way to go about this?
Thanks,
To make this happen with contentStretch, you would have to set the contentStretch rectangle so that its top edge is below the bottom edge of the white box, and so that its left edge is to the right of the right edge of the white box. You will probably find that your background pattern gets stretched too much and looks ugly.
Instead, give your view a subview that draws just the white box. Set the subview's springs and struts so that the box stays at the upper left and doesn't stretch.
I would make the white box stretchable on top, and a background view filled with a pattern using colorWithPatternImage:
patternView.backgroundColor = [UIColor colorWithPatternImage:#"myBackgroundPattern.png"];
and the black stroke could be done with:
#include <QuartzCore/QuartzCore.h>
...
patternView.layer.borderWidth = 2.f;
patternView.layer.borderColor = [[UIColor blackColor] CGColor];
What you are trying to achieve can be done using the resizableImageWithCapInsets method of a UIImage. This way, you can set cap widths on each side of the image and specify which part of the image needs to be repeated (not stretched).
An UIEdgeInset is a struct consisting of four components as follows (CGFloat topCapWith, CGFloat leftCapWith, CGFloat bottomCapWith, CGFloat rightCapWith)
The code below would do the trick
UIImage * backgroundImage = [[UIImage imageNamed:#"Stretch.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(80, 4, 4, 10)];
backgroundImageView.image = backgroundImage;
Since content stretch is deprecated, best approach is to use auto layout. In the inner rectangle give a defining height and width constant and bind its top and leading constraints to the parent view.
In order to get a bottom growth in outer rectangle, bind its bottom constraint or alternatively you can also chnage it height.

Stretchable and Pattern UIImage at the same time

I would like to code a shelf and I don't want to think about resizing and changing background images on a rotation and depending on the screensize (iPad/iPhone). Is it possible to create an image, that would stretch horizontally but repeat vertically?
By now, I've only found the [UIColor colorWithPatternImage:] and [UIImage strechableImageWithLeftCapWidth:topCapHeight:] or the new [UIImage resizableImageWithCapInsets:]. But I didn't manage to let them work together, obviously.
I hope the illustration helps understanding my issue:
Do you have any idea how to accomplish the above, so that it will work with a single image for different sizes and orientations? Thanks!
I also needed to set UIView background from image and change its size at the same time. What I did was:
Created image within frame of my view size
Set view background using [UIColor colorWithPatternImage:(UIImage*)]
And here is the code :
UIGraphicsBeginImageContext(self.followView.frame.size);
[[UIImage imageNamed:#"background_content.png"] drawInRect:self.followView.bounds];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIColor *bgColor = [UIColor colorWithPatternImage:image];
[followView setBackgroundColor:bgColor];
[UIColor colorWithPatternImage:] can take up an extreme amount of memory when working with a large number of rows. I ended up creating a single image view that was one row larger than the entire view using [UIColor colorWithPatternImage:], then offset that view up or down a row as necessary when the view was about to scroll offscreen. Since the view is only moved at row boundaries, the performance was much better than I expected.
However, a better option would be to create a pool of individual UImageViews using [UIColor colorWithPatternImage:] and tile them, similar to what UITableView does will table view cells.
Apple has sample code which illustrates this technique in the ScrollView Suite sample project and the Designing Apps with Scroll Views video from WWDC 2010.
Why not use a stretchable image of one shelf and then set it as the background for every cell on the tableview?
EDIT:
My knowledge of AQGridView is limited but sounds like you could get access to each individual 'grid tile'.
Try having three images per row:
- The image for the left most grid tile to show the left corner of your shelf
- The image for the center part of the shelf that is applied to all tiles except for the first and the last tile
- The image for the right most grid tile to show the right corner of your shelf.
Also, you should probably mention you're using AQGridView in your question so that other users won't think you're using a UITableView.

UIImageView not showing the background View if the image has transparent regions

I have a UIView has some labels and buttons on it.
Next I also have a image which has a square area that is transparent, now
if I create a UIImageView and add this image which has transparent regions I am not able to see the background view (which has buttons and labels) through this transparent image.
If I play with the alpha value that doesn't work as intended which is to see the transparent regions exactly as it would have appeared on the UIView which has the labels and buttons.
UIImage* image = [UIImage imageNamed:#"TI1.jpg"];
UIImageView* imageView = [[UIImageView alloc] initWithImage:image];
[self.view addSubview:imageView];
Also I would be interested to know if there is other way to achieve what I am trying to achieve.
Basically I want to highlight a certain area of the view which has buttons/labels and make the rest of the area greyed out. My idea was to have this UIImageView with transparent regions in image to achieve that.
Thanks
Ankur
Try setting imageView.opaque = NO;
UIImageView inherits from UIView. According to that class's docs:
This property provides a hint to the drawing system as to how it
should treat the view. If set to YES, the drawing system treats the
view as fully opaque, which allows the drawing system to optimize some
drawing operations and improve performance. If set to NO, the drawing
system composites the view normally with other content. The default
value of this property is YES.
Also, not sure that JPG even supports transparency, so try exporting the image as a PNG to ensure you get the results you're looking for.

Change the position of the left UIImageView in a UITableViewCell in OS 3.0

I have a UITableView with standard UITableViewCells that contain an image on the left. I'm setting the image of the cell as follows:
UIImage *leftIcon = [UIImage imageNamed:#"myImage.png"];
cell.imageView.image = leftIcon;
This works fine. However, there's a difference in appearance depending on which iPhone OS the App runs. Until 2.2.1 the image is positioned about 11 pixels from the left border. In 3.0, the image is positioned directly on the left border without any space.
My goal is that the image is positioned about 11px from the left of the cell on 3.0. I tried the following without any success:
UIImage *leftIcon = [UIImage imageNamed:#"myImage.png"];
//trying to change x coordinate of the frame of the UIImageView
CGRect tmpRect = cell.imageView.frame;
tmpRect.origin.x = 10;
cell.imageView.frame = tmpRect;
cell.imageView.image = leftIcon;
I could think of two possible solutions:
1.) Implement my custom UITableViewCell with a custom UIImageView on the left.
2.) Add an 11px transparent border to myImage.png with Photoshop :)
Any recommendations?
It's a bit of a hack, but you can indent your cells from your UITableViewDelegate.
- (NSInteger)tableView:(UITableView *)tableView indentationLevelForRowAtIndexPath:(NSIndexPath *)indexPath {
return 1;
}
This will have the effect of pushing over the UITableViewCell's UIImageView by about 10 points.
In my opinion, the easiest thing you can do to fix problems like this in the iPhone world is to create custom classes. Also, consider creating some functions for said classes like
-(void)redrawAtDefaultCoordinates;
-(void)redrawAtCoordinates(NSInteger x, NSInteger y);
for each custom class so that you can simply make adjustments to where they are displaying.
As to your actual problem, I would say try to get at the rectangle that the cell uses and then figure out the relative geometry that your going to need. The iPhone does a pretty good job with the guesses it makes about where you want things placed, but you always want at least something to go by if it guesses wrong.