Stretchable and Pattern UIImage at the same time - iphone

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.

Related

Using stretchable images in Xcode storyboards

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

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.

Background image that's scrollable and in synch with the UITable scrolling

I've added an image to a UITableview using the following code. What I can't figure out is how to have that background image scroll with the table (not an image in each cell, but a large image behind the UITable). I need the background image to be scrollable and in synch with the UITable scrolling.
I've searched and all the examples I've seen simply add a static image.
here's the code:
[self.view setBackgroundColor:
[UIColor colorWithPatternImage:
[UIImage imageWithContentsOfFile:
[[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:
#"background.png"]]]];
thanks for any help.
I had this same problem and didn't find any solutions. I rolled my own which I explain in more detail than can be easily given here. It's explained at UITableView that scrolls the background with dynamic content
The basic idea is the following:
create a second table view behind your table view that has dynamic cell heights (or heights that aren't a multiple of a background pattern image's height)
turn its .userInteractionEnabled property to NO (so it doesn't receive touch events)
have it listen to the 'front' table view's contentOffset property via KVO so that the background table view sets its contentOffset to the same value.
have this background table view be its own delegate (you have to make a subclass so to implement KVO listening handlers anyway), and it populates empty cells' contentView.backgroundColor = [UIColor colorWithPatternImage: ...]
make sure your 'front' table view cells have transparent backgrounds. i.e. backgroundView set to an imageView without an image and opaque set to NO, and backgroundColor set to clearColor.
worked for me quite fine, and didn't seem in any way slower. The tables were still quite snappy on an iPod 4G
Because few days ago I saw that -
http://www.appuicomponents.com/component/sbtv
I am guessing the answer is not trivial. But maybe you would like to try that ?
Any way, if you are using a pattern image, isn't that possible to add the pattern to the cells them selfs ?

How to resize (shrink) a UIView without pixellation?

In my app, I'm creating a full-screen view and then shrinking it down (to about 1/10th of its original size) so that I can later animate it to full-size. The problem is that the view looks terrible at that size, and is highly pixellated.
Here's the full-size view:
And here's the shrunken view in-place:
I'm shrinking the view using setFrame: - is there some way to tell iOS to use high-quality interpolation? Or am I already getting the highest-quality interpolation and this is the best it can do?
I'd recommend turning the view into an image before you shrink it (I assume currently you have your day view, made up of lots of subviews, which you then just shrink by adjusting the frame).
If you look you'll see this is how Apple achieve it: rather than literally shrinking the view, they will capture the contents of the view as an image, replace the view with that image, and then shrink the image. You'd do this for a number of reasons: efficiency (you can get rid of the shrunken views and replace them with a single UIImageView), speed, and also quality.
To save your UIView as an image you should do something like:
UIGraphicsBeginImageContext(myView.bounds.size);
[myView.layer renderInContext:UIGraphicsGetCurrentContext()];
viewImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
...then replace your view with the image, and shrink it.

CATiledLayer blanking tiles before drawing contents

All,
I'm having trouble getting behavior that I want from CATiledLayer. Is there a way that I can trigger the tiles to redraw without having the side-effect that their areas are cleared to white first? I've already subclassed CATiledLayer to set fadeDuration to return 0.
To be more specific, here are the details of what I'm seeing and what I'm trying to achieve:
I have a UIScrollView with a big content size...~12000x800. Its content view is a UIView backed by a CATiledLayer.
The UIView is rendered with a lot of custom-drawn lines
Everything works fine, but the contents of the UIView sometimes change. When that happens, I'd like to redraw the tiles as seamlessly as possible. When I use setNeedsDisplay on the view, the tiles redraw but they are first cleared to white and there's a fraction-of-a-second delay before the new content is drawn. I've already subclassed CATiledLayer so that fadeDuration is set to 0.
The behavior that I want seems like it should be possible...when you zoom in on the scrollview and the content gets redrawn at a higher resolution, there's no blanking before the redraw; the new content is drawn right on top of the old one. That's what I'm looking for.
Thanks; I appreciate your ideas.
Update:
Just to follow up - I realized that the tiles weren't being cleared to white before the redraw, they're being taken out entirely; the white that I was seeing is the color of the view that's beneath my CATiledLayer-backed view.
As a quick hack/fix, I put a UIImageView beneath the UIScrollView, and before triggering a redraw of the CATiledLayer-backed view I render its visible section into the UIImageView and let it show. This smooths out the redraw significantly.
If anyone has a better solution, like keeping the redraw-targeted tiles from going away before being redrawn in the first place, I'd still love to hear it.
I've found that if you set levelsOfDetailBias and levelsOfDetail both to the same value (2 in my case), then it only redraws the tiles that are touched by my setNeedsDisplayInRect: call, as you'd hope.
However if the levelsOfDetail is different to LODB, then any calls to setNeedsDisplayInRect: redraw all the tiles.
You could add another layer (possibly a CATiledLayer) behind the existing tiled layer. (Sort of a double-buffered solution.) You would call setNeedsDisplay: on the second layer from a timer that fires after a few seconds to ensure that that layer doesn't redraw at the same time as the front layer.
Another potential option is to use the same delegate to draw content to a bitmap context and swap the bitmap into the backing store once the content is refreshed. This should produce a flicker-free result. That being said, I can't tell you how this might be done, and one nice thing about CATiledLayers is they automatically generate tiles when you zoom and pregenerate tiles when you pan once zoomed in.
I would like to see how you implement your application. I have been looking for weeks to find an example that uses a combination of UIScrollView and a CATiledLayer-back view with a lot of custom drawn lines. Apple has some great sample code - but it all involves images rather than line art, so no help for me.
Having read through these answers without a solution, I discovered that tiling a page was the dominant background task.
Preparing my lo-res placeholder image on a high priority queue solved this issue - the images now appear while the tiling is occurring. Caching the placeholder images further improves their appearance - they appear before the tiling begins.
With newer devices, the tiling it so fast, these tricks might not matter. A sample PDF consisting of large scanned images (e.g. a scanned book) tiles the slowest in my experience, and makes for good test data.
I had the same problem with iPad.
The solution was more simple than I thought and far more simple than using UIImageView to render display before redrawing... :
Just don't set any background color for the Layer!
I had CATiledLayer set in a similar way:
layer = [[CATiledLayer alloc] init];
layer.masksToBounds = YES;
layer.contentsGravity = kCAGravityLeft;
//layer.backgroundColor = [[UIColor whiteColor] CGColor];
layer.tileSize = CGSizeMake(1004.0, 1004.0);
layer.levelsOfDetail = 16;
layer.levelsOfDetailBias = 8;
Note that I have commented out the line setting layer's background color to white.
After that the white blank before redraw problem disappeared!
Let me know if anyone has tried that.