I have a UIImageView object that is just a plain black rectangle.
This is what i use to select a button in the view.
Problem is, I have 49 of these buttons in my view, and all of them can be selected at the same time.
What I use for adding a subview to a button is:
UIImageView* selectedSquareView = [[UIImageView alloc] initWithFrame:CGRectMake(0,0,40,40)];
[selectedSquareView setImage:[UIImage imageNamed:#"SelectedSquare.png"]];
[button addSubview: selectedSquareView];
I would like the selectedSquareView to be reused multiple times as subviews for the other buttons, but only keep one allocation of it. I would prefer not having 49 UIImageViews created at the same time just for this purpose. Is this possible?
If not, should I store them in a NSMutableArray for easy removal later?
Regards
-Raymond
You will need 49 UIImageViews, you only need 1 UIImage. The UIImageViews contain position, size, is_higlighted, etc information for each button.
That being said even if you had a lot of UIImage's, UIImage is supposed to be pretty intelligent about these things as Apple describes in their documentation:
In low-memory situations, image data may be purged from a UIImage object to free up memory on the system. This purging behavior affects only the image data stored internally by the UIImage object and not the object itself. When you attempt to draw an image whose data has been purged, the image object automatically reloads the data from its original file. This extra load step, however, may incur a small performance penalty.
You should avoid creating UIImage objects that are greater than 1024 x 1024 in size. Besides the large amount of memory such an image would consume, you may run into problems when using the image as a texture in OpenGL ES or when drawing the image to a view or layer. This size restriction does not apply if you are performing code-based manipulations, such as resizing an image larger than 1024 x 1024 pixels by drawing it to a bitmap-backed graphics context. In fact, you may need to resize an image in this manner (or break it into several smaller images) in order to draw it to one of your views.
Alternatively if you really feel like you need to do delete the UIImageView's when not in use you can do as you suggest store them in an array and release them on viewDidDisappear and then recreate them all on viewWillAppear.
Each UIView appears only once, so you will definitely need to create 49 copies of it.
Your current code is probably fine, since UIImage will probably cache the image, but you might like to create the image only once and then set it each time, something like:
UIImageView* selectedSquareView = [[UIImageView alloc] initWithFrame:CGRectMake(0,0,40,40)];
static UIImage* kSelectedSquareImage = [UIImage imageNamed:#"SelectedSquare.png"] retain];
[selectedSquareView setImage:kSelectedSquareImage];
If not, should I store them in a
NSMutableArray for easy removal later?
It depends if there are any other views in the container view - if not, then there is no need to store them in an NSMutableArray as you can just use container.subviews to get an array of views. Otherwise, sure, you could store them in an NSMutableArray and remove them that way (just make sure you remove them from the array or release the array as well, otehrwise they will remain in memory simply because they are stored in the array).
Related
In my application I'm creating a large image dynamically and then loading it up for display in my image explorer class. Because I can't add new images to the bundle at run time, it seems I have to use imageWithContentsOfFile - however, this gives me major speed issues further down the line.
The way my image explorer works is that it takes in an image, splits it up into tiles, caches those tiles and then only loads those tiles into memory for display that need to be shown on the screen. Using a bunch of NSLogs, I've managed to find out where all the slowdown is. It's not in the imageWithContentsOfFile function itself, it's when I try to call this line:
CGContextDrawImage(context_ref,
CGRectMake(0, 0, imgWidth, imgHeight), tileImage);
This is when I'm writing the tile to the cache file. tileImage is a CGImageRef that is returned from CGImageCreateWithImageInRect, which is how I get subsets of my larger image to save separately.
The odd thing is that splitting up a large image this way takes about 45 seconds (!), but when I split up an image from the bundle using imageNamed rather than imageWithContentsOfFile, it takes only about 2 seconds.
Anyone have any ideas? Thanks in advance :)
I think U should split up your image.
Because, CGContextDrawImage will take fully loaded "tileImage".
If your "tileImage" size is 8 MB, your app must load 8MB data to memory.
It takes long time for loading. and It may create memory issue and so on.
If you want to use a single big image and you can wait for loading,
there is solution that U can use another thread.
It can avoid to UI lock during loading a big image.
An 8MB JPG image will use over 8MB memory, UIImage should use noncompressed format for fast drawing.
imageNamed uses caching, and may reduce the amount of scaling.
UIImage is immutable. imageNamed may note this and return a reference to a cached image, rather than loading and creating a new image... wherever you load your image.
if you create an images, you can setup your own (in memory) caching scheme and pass references in many cases. then purge the cache when you receive a memory warning.
if you need to scale the image and the size is static, determine the size to draw, and create a UIImage using imageWithCGImage:scale:orientation: -- or you can approach the problem in a similar way using CoreGraphics apis directly too.
beyond that, hold onto/reuse what you need, and use a profiler to balance your allocations and to measure timings.
I want to make an animated background for iphone app. Something simple 5-6 frames changing in the loop. On the front there will be another animation running. How can this be done?
The easiest thing to do is probably to use the animationImages property of a UIImageView. Once you have the animationImages property correctly set up, just call startAnimating on your view. So your code would look something like:
imageView.animationImages = myNSArrayofUIImagesObjects;
imageView.animationDuration = 1; // by default this is equal to the number of images multiplied by 1/30th of a second
[imageView startAnimating];
An important thing to note is that you can't easily control how long each image is shown. But what you can do is use the same image in your NSArray of images multiple times. So, for example, you could have an NSArray of length 500, where the first 100 entries map to your first image, the second 100 entries map to your second image, etc. Make sure to minimize the amount of memory you're loading onto the heap by reusing the same UIImage object for each of your five or six images.
In the iPhone sample code "PhotoScroller" from WWDC 2010, they show how to do a pretty good mimmic of the Photos app with scrolling, zooming, and paging of images. They also tile the images to show how to display high resolution images and maintain good performance.
Tiling is implemented in the sample code by grabbing pre scaled and cut images for different resolutions and placing them in the grid which makes up the entire image.
My question is: is there a way to tile images without having to manually go through all your photos and create "tiles"? How is it the Photos app is able to display large images on the fly?
Edit
Here is the code from Deepa's answer below:
- (UIImage *)tileForScale:(float)scale row:(int)row col:(int)col size:(CGSize)tileSize image:(UIImage *)inImage
{
CGRect subRect = CGRectMake(col*tileSize.width, row * tileSize.height, tileSize.width, tileSize.height);
CGImageRef tiledImage = CGImageCreateWithImageInRect([inImage CGImage], subRect);
UIImage *tileImage = [UIImage imageWithCGImage:tiledImage];
return tileImage;
}
Here goes the piece of code for tiled image generation:
In PhotoScroller source code replace tileForScale: row:col: with the following:
inImage - Image that you want to create tiles
- (UIImage *)tileForScale: (float)scale row: (int)row column: (int)col size: (CGSize)tileSize image: (UIImage*)inImage
{
CGRect subRect = CGRectMake(col*tileSize.width, row * tileSize.height, tileSize.width, tileSize.height);
CGImageRef tiledImage = CGImageCreateWithImageInRect([inImage CGImage], subRect);
UIImage *tileImage = [UIImage imageWithCGImage: tiledImage];
return tileImage;
}
Regards,
Deepa
I've found this which may be of help: http://www.mikelin.ca/blog/2010/06/iphone-splitting-image-into-tiles-for-faster-loading-with-imagemagick/
You just run it in the Terminal as a shell script on your Mac.
Sorry Jonah, but I think that you cannot do what you want to.
I have been implementing a comic app using the same example as a reference and had the same doubt. Finally, I realized that, even if you could load the image and cut it into tiles the first time that you use it, you shouldn't. There are two reasons for that:
You do the tiling to save time and be more responsive. Loading and tiling takes time for a large image.
Previous reason is particularly important the first time the user runs the app.
If these two reasons make no sense to you, and you still want to do it, I would use Quartz to create the tiles. CGImage function CGImageCreateWithImageInRect would be my starting point.
Deepa's answer above will load the entire image into memory as a UIImage (the input variable in his function), defeating the purpose of tiling.
Many image formats support region-based decoding. Instead of loading the whole image into memory, decompressing the whole thing, and discarding all but the region of interest (ROI), you can load and decode only the ROI, on-demand. For the most part, this eliminates the need to pre-generate and save image tiles. I've never worked with ImageMagick but I'd be amazed if it couldn't do it. (I have done it using the Java Advanced Imaging (JAI) API, which isn't going to help you on the iPhone...)
I've played with the PhotoScroller example and the way it works with pre-generated tiles is only to demonstrate the idea behind CATiledLayer, and make a working-self contained project. It's straightforward to replace the image tile loading strategy - just rewrite the TilingView tileForScale:row:col: method to return a UIImage tile from some other source, be it Quartz or ImageMagick or whatever.
I have a question regarding a png, animated image sequence.
I am loading images from 1 - 35 into a layer and I am initializing the layer with the 1st image using - initWithImage. I have several buttons that want to be able to play different ranges of the image sequence array.
Is there a way to play only the images from 1 - 10?
Is there a way to control the range of images played... 11 - 35 or 4 - 20 or whatever?
Of course I tried creating separate layers with separate array's of images and having the layers on top of each other. The problem their is the initWithImage. If I play the second layer I can see the initialized image underneath.
Here is my image sequence code:
- (void) loadAnim05 {
cAnim = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"cof_01.png"]];
cAnim.center = CGPointMake(78,211);
NSMutableArray *array = [NSMutableArray array];
for (int i = 1; i <= 35; i++)
[array addObject:[UIImage imageNamed:[NSString stringWithFormat:#"cof_%02d.png",i]]];
cAnim.animationImages = array;
cAnim.animationDuration = 1.0;
cAnim.animationRepeatCount = 1;
[self.view addSubview:cAnim];
[cAnim release];
}
Thank you very much for any help.
Why don't you maintain an array of images elsewhere and use -subarrayWithRange: method of the NSArray object to pick the images that you want to animate and assign it to the animationImages property of your imageView.
But I don't think UIImageView provides you with mechanism to play a subarray of animationImages.
I am in the process of writing a little animation framework to do exactly this. You can find the code at http://github.com/st3fan/iphone-animation
Key features are:
Optimized for PNG images with not too many shapes on a plain background
Includes a tool to convert a PNG sequence (like exported from Flash) to a simple one-file container format
Keeps frames compressed in memory. Decompresses realtime.
Frames are compressed with a simple run-length encoding
Typical full screen animation takes ~ 10% CPU
I wrote this specifically for games in the http://tickletapapps.com collection. It works very well for the kind of animations used in those games. Contact me via Github if you need help. The code is work in progress but working pretty well.
My data visualization app incurs a large memory consumption spike during redraw (setNeedsDisplay which triggers drawRect). I am currently redrawing the entire view that houses the data plot. This view is much larger then the device display.
Is there any way to tell CoreGraphics to allocate just enough memory to draw each element (each element is a small rectangular block much smaller then the device display) and release the memory when done, rather then my current naive approach?
Thanks in advance.
-Doug
UPDATE 8 Dec 8:28am EST
Here is the relevant code with explanatory wordage. I am running Instruments with ObjectAlloc, Memory Monitor, and Leaks instruments running. The only memory leak I have is due has to do with the NSOperationQueue not releasing mems. This is minor an not relevant.
Architecturally the app consists of a tableView with a list of interesting locations along the human genome to inspect. When a table row is selected I enqueue a data gathering operation that returns data called alignmentData. This data is then plotted as horizontal rectangular slabs.
Initially, when the tableView launches my memory footprint is 5 MB.
- (void)viewWillAppear:(BOOL)animated {
// Initial dimensions for the alignment view are set here. These
// dimensions were roughed out in IB.
frame = self.alignmentView.frame;
frame.origin.x = 0.0;
frame.origin.y = 0.0;
frame.size.width = self.scrollView.contentSize.width;
frame.size.height = 2.0 * (self.containerView.frame.size.height);
}
Note: After viewWillAppear: is called the memory footprint has not budged. Even though the alignmentView is be sized well beyond the dimensions of the display.
This is the method called from the data gathering operation.
- (void)didFinishRetrievingAlignmentData:(NSDictionary *)results {
// Data retrieved from the data server via the data gathering operation
NSMutableData *alignmentData = [[results objectForKey:#"alignmentData"] retain];
NSMutableArray *alignments = [[NSMutableArray alloc] init];
while (offset < [alignmentData length]) {
// ...
// Ingest alignmentData in alignments array
// ...
} // while (offset < [alignmentData length])
[alignmentData release];
// Take the array of alignment objects and position them in screen space
// so that they pack densely creating horizontal rows of alignment objects
// in the process.
self.alignmentView.packedAlignmentRows =
[Alignment packAlignments:alignments basepairStart:self.startBasepairValue basepairEnd:self.endBasepairValue];
[alignments release];
[self.alignmentView setNeedsDisplay];
}
After this line of code:
self.alignmentView.packedAlignmentRows = ...
The memory footprint is 13.8 MB
After this line of code:
[self.alignmentView setNeedsDisplay];
The memory footprint spikes to 21.5 MB, stays there for a few seconds then returns to the pre-existing level of 13.8 MB
The solution I am looking for would allow me to essentially, create a horizontal render buffer window that that is the height of a single row of alignment objects. I would allocate its memory render into it, then discard it. I would do this over and over again for each row of alignment data.
In theory, I could render an infinite amount of data with this approach which of course would be most excellent ;-).
-Doug
Here is the - not so obvious answer to my memory problem. I'll give myself this one because I learned it on the Apple dev forum form Rincewind - a very helpful Apple engineer BTW.
It turns out that by slicing a large view into N smaller pieces and rendering into each in turn I will incur a memory spike that is roughly 1/N the size of the large view.
So, for each smaller view: alloc/init, feed a portion of my data, setNeedsDisplay. Rinse/repeat for all N small views.
Simple, eh?
Prior to learning this I had mistakenly thought that setNeedsDisplay:myRect did this for the large view. Apparently not.
Thanks for all the suggestions gang.
Cheers,
Doug
#dugla
"This view is much larger then the device display."
So you're scrolling through the data representation by moving the view around? You might want to consider making your view the same size as the display and using CGTranslate to adjust the drawing offset within your drawRect function. It sounds like you're drawing tons of stuff, and CoreGraphics can't tell what's visible and what is not.
You'll get much better drawing performance if you make the view smaller and insert checks to avoid drawing things that are outside the view's bounds.
This is very possible, you will need to specify which sections of the screen need to be drawn, you need to call setNeedsDisplayInRect as described here and pass in a CGRect which is the area you wish to be redrawn.
This is much, much faster than re-drawing the entire screen, I had issues with this in an iPhone drawing application I created a year and a half ago.
In addition to Ben's suggestion:
If you're scrolling around your data, consider adding a few smaller views to the scrollview. This way you don't need to redraw most of the time, but only when some area of your scrollview isn't covered any more. Basically, if one of your subviews scrolls completely out of sight you'd move it to the opposite side of the visible area and redraw it accordingly.
In my app I'm only scrolling horizontally, and am using two subviews. Let's say view1 is on the left and view2 on the right. When view2 scrolls out of sight, I move it to the left of view1 and redraw it accordingly. If the user scrolls further in the same direction view1 will scroll out of sight as well and I'll move it to the left of view2 and so on.
If you need to scroll horizontally and vertically you'd need 4 views.
I know you are probably aware of this, but have you looked at the Core Plot framework for your data visualization? We recently added touch-scrolling of graphs and we've tried to be conservative when it comes to memory within the framework. Without knowing more about your specific case, this might be something you could try.