Memory Usage of UIButton with Image - ios

My app presents a scrolling list of buttons with image icons. I've found that even though the .png icon files are only 2 kb, their uncompressed versions are about 150 kb and the combined total of all the buttons is using too much device memory.
Originally each button was created within an instance of an object, so the code basically worked like this:
for (int i=0; i<buttonsCount; i++) {
UIImage *image = [UIImage imageNamed:#"Icon"];
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
[button setImage:image forState:UIControlStateNormal];
}
When I realized how much memory the images were using, I moved the image creation outside of the object and let all the objects use the same image. So now the code basically works like this:
UIImage *image = [UIImage imageNamed:#"Icon"];
for (int i=0; i<buttonsCount; i++) {
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
[button setImage:image forState:UIControlStateNormal];
}
However, the memory usage is the same. Apparently UIButton is making its own copy of the image data. Is there any way to "share" the image data among many buttons?
When I run the app in Instruments using the Allocations tool, "VM: CG raster data" is the item that gets too large.
The only alternate solutions I can think of would be loading and unloading the buttons as needed while scrolling, but I'm afraid that would hurt the scrolling performance; or simply reducing the pixel size of the images.

You could use an approach similar to UITableView (or maybe use a UITableView directly). It only creates views (cells) for the items that are on screen, and then recycles these cells as they move offscreen. This means those views are not recreated all the time, they are just updated with the correct data just before they move on screen.

Related

How can I store images in cache memory using objective-c

I am having problem when multiple images are being displayed in scrollview (photo Gallery). I have fetched images from document directory. when I display more then 100 images application is getting crashed. so I want to store images in cache memory.
I want to display first 15 images and then other from cache memory. when I scroll the scrollview first 15 from cache will be displayed and then next will be displayed.
The requirement is to display images in scrollview not in collectionView or other component.
files is array of image paths fetched from documents directory.
I have fetched all images path and stored in files array.
int x=2,y=5;
for (int i = 0; i < files.count ; i++)
{
if(x<=308)
{
NSString *setPath=[NSString stringWithFormat:#"%#/%#",filePath,[files objectAtIndex:i]];
UIImage* imagePath = [UIImage imageWithContentsOfFile:setPath];
imgView=[[UIImageView alloc]initWithFrame:CGRectMake(x, y, 77, 75)];
imgView.image=imagePath;
x+=80;
}
else
{
x=2;
y+=77;
NSString *setPath=[NSString stringWithFormat:#"%#/%#",filePath,[files objectAtIndex:i]];
UIImage* imagePath = [UIImage imageWithContentsOfFile:setPath];
imgView=[[UIImageView alloc]initWithFrame:CGRectMake(x, y, 77, 75)];
imgView.image=imagePath;
x+=80;
}
[gallaryScrollView addSubview:imgView];
[self.view addSubview:gallaryScrollView];
gallaryScrollView.contentSize=CGSizeMake(320, y+120);
}
Caching isn't going to solve your memory problem. (If anything, using additional memory cache only places more memory strain on the app.) You use cache for performance reasons, not for reducing memory usage.
The issue is that you're attempting to load image views into scroll view up front. I'd suggest doing lazy loading of the image views and their associated images. And rather than doing it in batches of 15 (or whatever), I'd suggest doing it dynamically in your UIScrollViewDelegate methods:
setting delegate for your scroll view;
implement scrollViewDidScroll which will:
remove any image views from the scroll view that are not visible (or near, at the very least)
add any image views that have scrolled into view and set its image property
If you want to marry this with NSCache for caching for performance reasons, you can do that if you want, but I'd suggest focusing on the main manual scroll view delegate implementation first.

Scaling down a UIButton's background image when using initWithFrame:

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];

Building a UIButton out of 3 images

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];

Creating a UIButton in shape of the image given

how can i create a UIButton in shape of the image given.
i am able to create something close to it,but the image seems to be not fitting in the button.
my requirement is the image given below.i.e;semicircle
the code i used to create the button is given below.
what changes should i make on the following image to get this button.
ps-add subview done in another class..
btnStartGame =[UIButton buttonWithType:UIButtonTypeCustom];
[btnStartGame setFrame:CGRectMake(60, 200, 200, 200)];
btnStartGame.titleLabel.font=[UIFont fontWithName:#"Helvetica-Bold" size:30];
[btnStartGame setImage:
[UIImage imageNamed:#"Draw button.png"] forState:UIControlStateNormal];
btnStartGame.titleLabel.textColor=[UIColor redColor];
btnStartGame.clipsToBounds = YES;
btnStartGame.layer.cornerRadius = 50;//half of the width
btnStartGame.layer.borderColor=[UIColor redColor].CGColor;
btnStartGame.layer.borderWidth=2.0f;
btnStartGame.tag=20;
btnStartGame.highlighted=NO;
There is a really great tutorial here with downloadable source code:
http://iphonedevelopment.blogspot.co.uk/2010/03/irregularly-shaped-uibuttons.html
In essence you need to create a category on UIImage, which checks whether the point you have touched is transparent image or not. This means you can use it to check hit tests on irregular shapes.
You then subclass UIButton and over-ride the hitTest:withEvent:
Hope this helps
Although the solution by Jeff Lamarche linked by the other answer works fine, it uses a lot of memory, and/or does a lot of work:
If you create NSData once in the initializer, you end up holding a relatively large block of memory for the lifetime of each button
If you make it transient, the way it is done in the answer, then you end up converting the entire image each time you hit test your button - processing thousands of pixels and throwing away the result after fetching a single byte!
It turns out that you can do this much more efficiently, both in terms of memory use and CPU consumption, by following the approach outlined in this answer.
Subclass UIButton, and override its pointInside:withEvent: method like this:
-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
if (![super pointInside:point withEvent:event]) {
return NO;
}
unsigned char pixel[1] = { 0 };
CGContextRef context = CGBitmapContextCreate(pixel, 1, 1, 8, 1, NULL, (CGBitmapInfo)kCGImageAlphaOnly);
UIGraphicsPushContext(context);
UIImage *image = [self backgroundImageForState:UIControlStateNormal] ;
[image drawAtPoint:CGPointMake(-point.x, -point.y)];
UIGraphicsPopContext();
CGContextRelease(context);
return pixel[0] != 0;
}
The code above takes alpha from the background image of the button. If your button uses another image, change the line that initializes image variable above.

iOS - Create a Photo Viewer like Photo app

i'm trying to create a photo viewer like the Apple Photos app in iOS.
The layout is ok, but it receives memory warning and then crashes. Why? This happens even i load 7/8 images from the app documents folder. Have i to manage the memory with specific system? I use ARC with iOS 5.
EDIT :
The code :
for (int i=0; i<[dataSource count]; i++) {
UIButton *button=[UIButton buttonWithType:UIButtonTypeCustom];
[button setImage:[dataSource objectAtIndex:i] forState:UIControlStateNormal];
[[button titleLabel] setText:[NSString stringWithFormat:#"%i",i+1]];
[button addTarget:self action:#selector(buttonPressed:) forControlEvents:UIControlEventTouchUpInside];
[[button layer] setBorderWidth:1];
[[button layer] setBorderColor:[UIColor darkGrayColor].CGColor];
if (i==0) {
[button setFrame:CGRectMake(x, y, width, height)];
} else {
if (i%5==0) {
nRow++;
x=18;
[button setFrame:CGRectMake(x, (y*nRow), width, height)];
} else {
[button setFrame:CGRectMake(x+space+width, (y*nRow), width, height)];
x=button.frame.origin.x;
}
}
[[self view] addSubview:button];
}
The main part of this code is the first 6 lines, after is all x and y.
dataSource is an NSArray declared as property (nonatomic, strong). It contains UIImage objects.
You should be lazily loading your images in conjunction with reusing your buttons to account for the possibility of a large number of images.
To implement:
Keep the paths to the image files in your data array instead of the UIImage objects. Get the image from the path using imageWithContentsOfFile: when you need it.
Load the first z buttons into the scroll view where z is the number that appear on the screen at a time plus one row's worth.
Set the UIViewController that we are currently in as the scrollview's delegate, and respond to changes in offset by repositioning buttons and setting appropriate images and targets.
Also, if 7/8 images is crashing your app, it sounds like you're dealing with some pretty large image files. Try to provide thumbnail-sized versions of the content within the documents directory(whatever the size of your buttons are EXACTLY), or if images are dynamic, see this post for a how-to.
If you are probably using ImageNamed, this article helped me alot:
http://www.alexcurylo.com/blog/2009/01/13/imagenamed-is-evil/
Mainly
DO NOT USE [UIImage imageNamed] for any significant amount of images. It is EVIL. It WILL bring down your application and/or Springboard, even when your application is putting along using just barely a nibble of memory on its own.
and
it is better to implement your own cache
and here's the proposed cached image example:
- (UIImage*)thumbnailImage:(NSString*)fileName
{
UIImage *thumbnail = [thumbnailCache objectForKey:fileName];
if (nil == thumbnail)
{
NSString *thumbnailFile = [NSString stringWithFormat:#"%#/thumbnails/%#.jpg", [[NSBundle mainBundle] resourcePath], fileName];
thumbnail = [UIImage imageWithContentsOfFile:thumbnailFile];
[thumbnailCache setObject:thumbnail forKey:fileName];
}
return thumbnail;
}

Resources