Clear CATiledLayers Cache When Changing Images - iphone

I have a UIScrollView with a single subview, a UIView backed by a CATiledLayer. All is working well with one exception: when I change images from one to another the CATiledLayer caches the previous images zoom levels. Scrolling around then displays the old image for a split second before the updated image loads.
Is there any way to totally clear out the CATiledLayer's cache so it doesn't show old images? The CATiledLayer obviously knows that the backing image changed because it asks it's delegate for new tiles...

I think you are making this a little more complicated than it needs to be. I believe all you have to do is set the contents of your CATiledLayer to nil.
myCATiledlayer.contents = nil.

-(void)invalidate
{
CATiledLayer *tiledLayer = (CATiledLayer *)[self layer];
tiledLayer.tileSize = CGSizeMake(93,93);//Set a different tile size
tiledLayer.tileSize = CGSizeMake(92,92);//Restore original tile size
}
This works for me.

Related

Load MKMapView in background and create UIImage from it (iPhone & iPad)

I have a situation whereby I need a way of loading an MKMapView with several overlays. This map view shouldn't be shown onscreen, the only reason I require it to load is in order to create an image from the map for use elsewhere.
I have had a look around online but I haven't had any luck with finding a solution to the problem. Please can someone help me out?
Here is what I've tried so far (with no success)
// Load the map view
Logs_Map_ViewController *mapViewController = [[Map_ViewController alloc] initWithNibName:#"Map_ViewController" bundle:nil];
mapViewController.GPSCoordinatesToPlot = [someDictionary objectForKey:kGPSCoords];
// Take a picture of the map
UIImage *mapImage = [mapViewController convertMapToImage];
NSData *imageData = [NSData dataWithData:UIImagePNGRepresentation(mapImage)];
NSString *base64String = [imageData base64EncodedString];
NOTE: It's really important that the app waits for the map to fully load (so that the image is created properly).
This may or may not be helpful but this is what I do in my situation.
I have a tableview for an venue/business with 7 table cells. One of the table cells contains a map with pindrop. When the user views an establishment for the very first time, i run a quick conditional to see if the mapview has been cached (or rather, saved as image). if the image does not exist, i load a fresh mapview into the cell and add a single pindrop annotation. After both the mapview has loaded and the annotation has dropped, i save the view layer to image, then remove the mapview (and release it since we needed to keep the delegate alive) from the cell and replace with the fresh new image. I suggest spawing a background thread to save the image/insert into cell. This way the table view doesn't choke up.
And then finally, the next time the user returns to that same venue/business, the mapview image exists and i load the image.
I know it's important for the map to fully load, otherwise your image saves with gray where the map did not load. if you're dropping a pin drop, you need to make sure your image saving method fires after both the map finished loading (mapViewDidFinishLoadingMap:) and your annotation has dropped (mapView:didAddAnnotationViews:). Only after both have finished should you fire method to save image. i suggest using a delayed selector. I delay it by 1 second.
This same concept can surely be applied to other situations. Bottom line is, as strings42 states, you're going to have to add a visible mapview to your view in order to capture an image.
You should be able to implement mapViewDidFinishLoadingMap in the MKMapViewDelegate protocol and store your image there, I would think. That should solve the problem of needing to wait until the map is loaded to store the image.
If you're seeing other issues as well, please provide more detail on those, as your question is not clear on the specific issue(s) that you're asking about.
The method below will always be called as the delegate of mapView,
mapViewDidFinishRenderingMap:fullyRendered:
so, you can get map image from mapView here
- (UIImage *)renderToImage:(MKMapView *)mapView
{
UIGraphicsBeginImageContext(mapView.bounds.size);
[mapView.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}

Added a shadow to a UIImageView on a UITableView kills performance... why?

I have a UITableView that has three UIImageView views per cell, with three cell displaying on the view at one time (for a total of nine UIImageView views). Think of it as a bookshelf. Sometimes I can have as many as 500 books.
I've added shadow to the UIImageView with code that is this:
UIImageView *itemImageView = [[UIImageView alloc] initWithFrame:CGRectMake(25, 7, 65, 75)];
itemImageView.contentMode = UIViewContentModeScaleAspectFit;
itemImageView.tag = 6;
itemImageView.layer.shadowColor = [UIColor blackColor].CGColor;
itemImageView.layer.shadowOffset = CGSizeMake(3, -1);
itemImageView.layer.shadowOpacity = 0.7;
itemImageView.layer.shadowRadius = 3.0;
itemImageView.clipsToBounds = NO;
[cell.contentView addSubview:itemImageView];
When I add the shadow code, as seen above, scrolling performance is just totally killed and becomes choppy. Each image has a different Rect so the shadow has to be created for each item as it scrolls. Anyone have any tips on how to add shadows to my images on a UITableView without having this issue?
You may see a performance improvement if you add
itemImageView.layer.shadowPath =
[UIBezierPath bezierPathWithRect:itemImageView.layer.bounds].CGPath;
But in general, layer operations like this will kill performance in a table view. I experienced exactly the same issue, and we just eliminated the shadow effect. It wasn't worth it.
You should review the WWDC videos on CoreAnimation best practices. You can request that the rasterized copy of the shadow be cached in memory. Cocoa is definitely fast enough to render these shadows on the fly without falling back to a pre-rendered image.
Example:
itemImageView.layer.shouldRasterize = YES;
Also I up-voted the answer regarding UIBezierPath. This is also mentioned in the best practices, but setting the shadow path of a CALayer is a huge performance boost. You can also create some interesting special effects by manipulating the shadow path.
Shadows are expensive and will kill your performance.
A better approach is to render the shadowed image in the background, cache/save it and display it on the view when its ready.
Edit: You way wish to look at Core Graphics / CGImage routines. Specifically CGContextSetShadowWithColor will draw you a shadow.
http://developer.apple.com/library/mac/#documentation/GraphicsImaging/Reference/CGContext/Reference/reference.html
I have the same problem with adding shadow to labels inside a tableviewcell. I tried to layout the elements inside cellForRowAtIndexPath when the cell will be created:
if(cell == nil){
//layout cell
These optimized the scrolling a little bit, but its quite choppy.
For optimizing your pictures quality you should add also the rasterizationScale if you activated "shouldRasterize":
aLabel.layer.shouldRasterize = YES;
aLabel.layer.rasterizationScale = [[UIScreen mainScreen] scale];
Maybe somebody has some ideas how to optimize the code to get the normal iOS scrolling. thx
Do you activate reusing? If not, the cells will redone each time the view changes (e.g., during scrolling). That would eat a lot of performance for sure.
Check out my same question here: App running slowly because of UIImageViews
I would use this code:
imageView.layer.masksToBounds = NO;
UIBezierPath *path = [UIBezierPath imageView.bounds];
imageView.layer.shadowPath = path.CGPath;

ScrollView with large images scrolls too slowly

When I am scrolling images frequently in a UIScrollView then after some images, the next image takes time to load... it's not taking too much time but looks odd.
Suppose I have 27 images in a scrollView. When I start to scroll these images, for 1 or 2 images it scrolls smoothly but when I scroll again to see the 3rd image it takes time to load. Then when I start the images scrolling again from the 3rd image, it behaves like before.
I can't load all 27 images at a time or my app crashes.
When I slowly scroll the scrollview then I don't have this problem.
My code is below:
//Taking image view for 27 images;
int x=0;
for(int i = 1; i<=27; i++) {
imageView = [[UIImageView alloc] init];
imageView .frame = CGRectMake(x,0,768,1024);
imageView.tag=i;
imageView.image=nil;
imageView.userInteractionEnabled=YES;
[contentView addSubview:imageView];
x+=768;
}
//setContentOffset of the scrollView -->ContentView
[contentView setContentOffset: CGPointMake((imageNumber-1)*768, 0) animated: YES];
//desire image which i want to see from the start of the scrollview
pageNumber=imageNumber;
int pageBefore=pageNumber-1;
int pageAfter=pageNumber+1;
//Views for image
for( UIImageView * views in [contentView subviews]){
if(views.tag==pageNumber){
if(views.image==nil){
NSLog(#"entering");
views.image=[UIImage imageNamed:[ NSString stringWithFormat:#"%d.jpg",pageNumber]];
[views.image release];
}
}
if(views.tag==pageBefore){
if(views.image==nil){
views.image=[UIImage imageNamed:[ NSString stringWithFormat:#"%d.jpg",pageBefore]];
[views.image release];
}
}
if(views.tag==pageAfter){
if(views.image==nil){
views.image=[UIImage imageNamed:[ NSString stringWithFormat:#"%d.jpg",pageAfter]];
[views.image release];
}
}
My alarm bells rang when I saw this;
imageView .frame = CGRectMake(x,0,768,1024);
Apart from the space before .frame, are you saying that your images are 768x1024? That's HUGE and I suspect your problems are memory ones rather than code ones.
Be aware that in particular, using UIImage imageNamed: is likely to cause grief with such large images as that method caches the images in memory. You may wish to consider using alternative methods that load the image from a file each time.
You should try use the EGOImageView, it has caching build in which might help with your performance issues. You can implement a placeholder image to show the user that an image is being prepared for viewing. The image will load in another thread before being displayed, giving you smoother scrolling performance. The EGOImageView is part of the EGOImageLoading library.
https://github.com/tastefulworks/EGOImageLoading
As an alternative you could create your own lazy loading mechanism to increase scrolling performance. E.g. once a user stops scrolling for a second, start loading the image, otherwise display placeholder image if not yet the correct image is cached.
Edit: when thinking more about this issue, I realize caching won't help much (since you already load image from disk), but the asynchronous loading of images should help with the scroll performance, so make use of NSThread or NSOperation to load the image in a background thread, then notify the main thread that the image is loaded and ready for display.

setting image to button -> take up lots of memory ? what should i do then

basically im running my apps with instruments and found out that by just setting a background image to the UIButton, it takes up 6mb of data(which i do not want in case low-memory warnings). i read around and found out that since the button has been assigned the image, it retains it(and the memory).
How should i code it then?My current codes are as below. Btw im new to iPhone development so please tell me what to do.
btw this button would just bring me to another view. is there anyway to release the memory that was allocated to this image?
.m file
-(void)viewDidLoad{
UIColor *background = [[UIColor alloc] initWithPatternImage:[UIImage imageNamed:#"MainScreen.png"]];
selectionScreen.backgroundColor = background;
[background release]
}
You mentioned in comment above that your .png is under 300k. That's perhaps a touch big, but you're actually not looking at the right thing. A png gets expanded to a native CGImage object. I usually figure a 32-bit image with alpha takes up width * height * 4 bytes of memory. That's pretty much guaranteed to be bigger than the PNG it gets expanded from, and in your case could be quite big indeed. Enough so that the docs recommend not instantiating UIImages bigger than 1024 x 1024.
Now, one solution could be that -initWithPatternImage can take a small piece of your background, and will tile it when it's drawn. So your first shot at solving this would be to provide that method as small an image as possible, and let it tile to bigger sizes.
Second thing, the retention. You're correctly releasing your UIColor object after setting it on the background. You WANT that object you set it on to retain it! In a world of infinite memory, you'd want that button to retain its background color until the viewcontroller it's on gets dealloc'ed. If it's still huge and you really have to get rid of it before backing out of the view controller (say when you push to a new UINavigationController view or something), you could try setting background to nil (or a system default color maybe) in -viewDidDisappear and re-building your background in -viewWillAppear.
Wheb viewWillDisappear, you can set backgroundColor as another color, and release the background color you made.
-(void)viewWillDisappear:(BOOL)animated
{
// release original backgroundColor
// default backgroundColor is nil by UIView class reference.
selectionScreen.backgroundColor = nil;
}
Hope this can help you.
Did you try using something like:
button = [UIButton buttonWithType:UIButtonTypeCustom];
[button setFrame:/frameOfChoice/];
[button setBackgroundImage:[UIImage imageNamed:#"MainScreen.png"] forControlState:UIControlStateNormal];
I'm not sure how this effects the memory usage tho.

How to determine image coordinates for a large cropped image within a UIImageView?

With regards to the iPhone SDK:
I have a 512 x 512 pixel .png image, and I want to display it within a 200 x 200 pixel UIImageView WITH cropping (at native resolution, without zooming, fitting, or rotation). Is there a way to determine the image's coordinates that are visible inside the UIImageView?
For example, if the image in the exact center of the UIImageView, then I want to know what the coordinates of the image are at UIImageView's (0,0) and (200,200) points, which will be (156,156) and (356,356), respectively for the image being displayed.
I plan on implementing "pan" and "zoom" functions at some point, but I want to know how to address this issue first. The UIImageView class reference did not seem helpful... I was hoping to avoid using a UIView, but if that is necessary I will go that route.
Thanks!
You're right about what you've seen from the UIImageView reference - there is no easy way to do that using UIImageView alone. You would need to take into account things like the contentMode of the UIImageView or limit yourself to only calculating the position for a single UIViewContentMode.
However, when you eventually get to implementing pan and zoom, you'll be using a UIScrollView with a UIImageView as a subview of that UIScrollView. At that point, it would be most sane to size your UIImageView initially to the size of whatever image you're display (could be dynamic). You can then determine where the image is by determining where the image view is, which is something you can accomplish much more simply and with just a bit of math.
Thanks for your help. Working out the pan/zoom features became trivial after implementing the UIScrollView. Implementing the UIScrollView let me determine the coordinates I needed with the myScrollView.bounds.origin.x and myScrollView.bounds.origin.y values... zooming still requires calculation - as you stated above, but I am on the right track now.
here is the code I used to make the UIScrollView (based on multiple sources available on the net and this site - Apple's ScrollViewSuite was particularly helpful. Here is the code I used in the (void)viewDidLoad section of my program, hopefully others can find it useful:
UIImageView *tempImageView = [[UIImageView alloc] initWithImage:[imageArray objectAtIndex:0]]; //imageArray is an array/stack of images...
[self setMyImage:tempImageView]; //myImage is of type UIImageView
[myImage setTag:100]; //set this to an 'arbitrary' value so that when I update the scrollview I can remove the current image...
[tempImageView release];
myScrollView.contentSize = CGSizeMake(myImage.frame.size.width, myImage.frame.size.height);
myScrollView.maximumZoomScale = 10.0;
myScrollView.minimumZoomScale = 0.15;
myScrollView.clipsToBounds = YES;
myScrollView.delegate = self;
[myScrollView addSubview:myImage];