autorelease pool - iphone

I was hoping someone could help me with a memory issue on the iPhone.
I have a scrollview that is part of a nav controller.
Whenever I first push the nav controller and scroll through the scrollview (adding images as I go), memory is allocated in huge chunks (say 1mb each).
If I rotate the display a couple of times, the memory is freed, and everything is fine: The scrollview works correctly and the memory returns to where it should be (around 1mb, looking at the Net alloc in Instruments).
How can I keep the memory from going wild, or free it up during use of the scrollview, just as the device rotation does?
Here is the code snippet that is called to load the scrollview page:
- (void)loadPage:(int)page isCurrent:(BOOL)isCurrent {
if (page < 0) return;
if (page >= totalRows) return;
picViewController *controller = [[picViewController alloc] init];
if ((NSNull *)[viewControllers objectAtIndex:page] == [NSNull null]) {
NSString *fullPath = [self fullPath];
if([fileManager fileExistsAtPath:fullPath]) {
currentImage=[UIImage imageWithContentsOfFile:fullPath];
[controller setImg:currentImage];
[viewControllers replaceObjectAtIndex:page withObject:controller];
}
else {
AsyncImageView* asyncImage = [[AsyncImageView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]];
asyncImage.tag = 8;
[asyncImage loadImageFromName:imageName withURL:url];
[controller.view addSubview:asyncImage];
[asyncImage release];
[viewControllers replaceObjectAtIndex:page withObject:controller];
}
if (nil == controller.view.superview) {
CGRect frame = scrollView.frame;
frame.origin.x = frame.size.width * page;
frame.origin.y = 0;
controller.view.frame = frame;
[scrollView addSubview:controller.view];
}
[controller release];
}
}

I don't see where in your code you're actually purging off-screen pages.
Your code is somewhat like Apple's PageControl example, right?
What I do is loop through the pages each time a page is "activated" and purge pages more than 1 or 2 screens away. (1 for without bounce scrolling, 2 for with.)
- (void)updateCachedPages {
int active = pageControl.currentPage;
int count = pageControl.numberOfPages;
for ( int i = 0; i < count; i++ ) {
if ( abs( active - i ) <= 2 ) {
[self loadPage:i];
}
else {
[self purgePage:i];
}
}
}
- (void)purgePage:(int)page {
if ((page < 0) || (page >= [_myObjects count])) return;
MyControllerClass *controller = [_viewControllers objectAtIndex:page];
if ((NSNull *)controller != [NSNull null]) {
[_viewControllers replaceObjectAtIndex:page withObject:[NSNull null]];
//NSLog( #"Purged page %d", page );
}
}
Call updateCachedPages in scrollViewDidEndDecelerating, your PageControl's page change action, and viewWillAppear.

does your controller pay attention to memory low notifications? You could flush any images which aren't visible when that's received, or do any other releasing you think relevant.

OK, I wasn't freeing all the memory after all.
Once I found a class instance not being freed properly, and made sure tewha's purge code was working, everything is golden.
Thanks for the quick help. This forum is awesome BTW.

Related

viewdidLoad/ viewDidAppear issue

I m loading a view which gets data from webservices. So depending upon the response code from server I show the current view or redirect to another view (ErrorView) if responsecode is 400.But I'm not being redirected to ErrorView and find a message in console "Attempt to present <ErrorView: 0xe332f30> on ViewController while a presentation is in progress!"
After googling it out I found to place vieDidLoad code to viewDidAppear
After placing the entire code , it redirects to error view and works correctly.But in ios7 for Status bar issue I used the code
-(void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
if(responsecode==200)
{
//load current view
}
else
{
//Load anotherview(ErrorView)
}
float systemVersion=[[[UIDevice currentDevice] systemVersion] floatValue];
if(systemVersion>=7.0f)
{
CGRect tempRect;
for(UIView *sub in [[self view] subviews])
{
tempRect = [sub frame];
tempRect.origin.y += 20.0f;
[sub setFrame:tempRect];
}
}
}
First the statusbar appear on the top ,then after loading the entire viewdata , view gets adjusted and status bar dropsdown by 20px , which looks odd.Can I get it to be adjusted automatically before the viewloads completely ?
Any ideas/suggestions would be appreciable.
I think, you should seperate this code
-(void)viewDidLoad
{
float systemVersion=[[[UIDevice currentDevice] systemVersion] floatValue];
if(systemVersion>=7.0f)
{
CGRect tempRect;
for(UIView *sub in [[self view] subviews])
{
tempRect = [sub frame];
tempRect.origin.y += 20.0f;
[sub setFrame:tempRect];
}
}
}
-(void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
if(responsecode==200)
{
//load current view
}
else
{
//Load anotherview(ErrorView)
}
}

Paged UIScrollview with lazy delayed loading

I need help with the uiscrollview implementation with the following requirements:
1) pagingEnabled = true.
2) lazy pages loading.
3) pages are loading in the background. So i need at first run loading the page X, then get the notification that the page is fully loaded and only then allow the user to scroll to it.
4) ability to change the current page.
My first attempt was to override the scrollViewDidEndDeacelerating and scrollViewDidScroll, but I had troubles with stucking on half of pages (when you stop the scroll on the half of the page and then wait for new page to add to the scroll) and empty pages (when the user scrolled too fast).
My second attempt was to override the layoutSubviews method of the UIScrollView and do all calculations there. But it seems to be very sofisticated.
So, I'd love to find any examples of similar implementations.
Now I have the code like this:
I've implemented scrollViewWillBeginDragging and scrollViewDidEndDecelerating:
- (void)scrollViewWillBeginDragging:(UIScrollView *)aScrollView
{
isScrolling = YES;
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)aScrollView
{
isScrolling = NO;
// Here we load the page that was prepared when the user was scrolling
if (needDisplay > -1) {
//NSLog(#"Loading queued page %d", needDisplay);
[self loadScrollViewWithPage:needDisplay forceAddToSuperview:NO animated:YES];
needDisplay = -1;
}
// minLoadedPageIndex - index of the first loaded page.
int selectedIndex = MIN(floor((aScrollView.contentOffset.x - pageWidth / 2) / pageWidth) + 1 + minLoadedPageIndex, [photoItems count] - 1);
[self loadScrollViewWithPage:selectedIndex - 1 forceAddToSuperview:NO animated:YES];
[self loadScrollViewWithPage:selectedIndex forceAddToSuperview:NO animated:YES];
[self loadScrollViewWithPage:selectedIndex + 1 forceAddToSuperview:NO animated:YES];
}
In loadScrollViewWithPage I create the page view controller which loads the data from the server in the background. I don't add the view to the scrollview until it loads the data from the server.
- (void)loadScrollViewWithPage:(int)page forceAddToSuperview:(BOOL)value animated:(BOOL)animated
{
DetailController *controller = page >= viewControllers.count ? [NSNull null] :[viewControllers objectAtIndex:page];
if ((NSNull *)controller == [NSNull null])
{
controller = [[DetailController alloc] initWithNibName:#"DetailController" bundle:nil];
controller.delegate = self;
controller.view.hidden = NO; //this will call viewDidLoad.
if (page >= viewControllers.count) {
[viewControllers addObject:controller];
}
else {
[viewControllers replaceObjectAtIndex:page withObject:controller];
}
[controller release];
}
// add the controller's view to the scroll view
if (controller.view && controller.view.superview == nil && (controller.isLoadedOrFailed || value)) {
[self setNumberOfVisiblePages:visiblePagesCount+1];
if (page < selectedIndex) {
// We are adding the page to the left of the current page,
// so we need to adjust the content offset.
CGFloat offset = (int)scrollView.contentOffset.x % (int)scrollView.frame.size.width;
offset = scrollView.frame.size.width * (selectedIndex - minLoadedPageIndex) + offset;
[scrollView setContentOffset:CGPointMake(offset, 0.0) animated:animated];
}
CGRect frame = scrollView.frame;
frame.origin.x = frame.size.width * (page - minLoadedPageIndex);
frame.origin.y = 0;
controller.view.frame = frame;
[scrollView addSubview:controller.view];
[controller viewWillAppear:NO];
}
}
Also I have a detailControllerDidFinishDownload method which is called when data for the page view controller has been loaded.
- (void)detailControllerDidFinishDownload:(DetailController *)controller
{
... //here I calculate new minLoadedPageIndex value
// reset pages frames
if (minLoadedPageIndex < oldMinPage) {
for(int i=oldMinPage;i < [viewControllers count]; i++) {
DetailController *detailsController = [viewControllers objectAtIndex:i];
if ((NSNull *)detailsController != [NSNull null]) {
CGRect frame = scrollView.frame;
frame.origin.x = frame.size.width * (i - minLoadedPageIndex);
frame.origin.y = 0;
[detailsController.view setFrame:frame];
}
}
}
// load the page now or delay load until the scrolling will be finished
if (!isScrolling) {
[self loadScrollViewWithPage:[photoItems indexOfObject:controller.photoItem] forceAddToSuperview:NO animated:NO];
}
else {
needDisplay = [photoItems indexOfObject:controller.photoItem];
//NSLog(#"pageControlUsed is used!!! %d", [photoItems indexOfObject:controller.photoItem]);
}
}
The problem I have now is that sometimes the scroll stucks on the middle (or somewhere near to middle) of the pages and it won't go to the nearest page bounce until I slightly more it. My tests showed that this situation happens if I scroll out of content view frame (the scroll view have bounces on) and wait for the new page to load. In 1 of the 10 times the scroll stucks.
Thanks, Mikhail!
There are a lot of things you are requiring at the same time. I suggest you have a look at this example.
http://ykyuen.wordpress.com/2010/05/22/iphone-uiscrollview-with-paging-example/
It's a very good starting point.

how to handle tiling of images on the fly

I am writing an app which would tile the images 256 * 256 and write those tile files back in the directory. I am updating my URL each time if there are any updates and tile those images back and store in the iphone folder. I am worried about two main things :
1) Memory consumption -- will the memory consumption for 5 images of size 200 KB a lot ?
2) How fast I can process my app if I have to tile 5 different URL with images at the same time ?
I have written a code to tile and save in the directory for one URL and would like to do the same for 5 URLs. Is it recommended to go with this approach or if anyone has a different approach?
- (void)viewDidLoad
{
[super viewDidLoad];
NSString *URLString = #"http://www.abc.com/abc.html?event=123";
NSURL *url = [[NSURL alloc] initWithString:URLString];
NSData * dataImage = [NSData dataWithContentsOfURL:url];
NSString *directoryPath = [[NSBundle mainBundle] bundlePath];
UIImage *big = [UIImage imageWithData:dataImage];
[self saveTilesOfSize:(CGSize){256,256} forImage:big toDirectory:directoryPath usingPrefix:#"image_124_"];
TileView *tv = [[TileView alloc] initWithFrame:(CGRect){{0,0}, (CGSize){5000,5000}}];
[tv setTileTag:#"image_110_"];
[tv setTileDirectory:directoryPath];
[scrollView addSubview:tv];
[scrollView setContentSize:(CGSize){5000,5000}];
}
- (void)saveTilesOfSize:(CGSize)size
forImage:(UIImage*)image
toDirectory:(NSString*)directoryPath
usingPrefix:(NSString*)prefix
{
CGFloat cols = [image size].width / size.width;
CGFloat rows = [image size].height / size.height;
int fullColumns = floorf(cols);
int fullRows = floorf(rows);
CGFloat remainderWidth = [image size].width -
(fullColumns * size.width);
CGFloat remainderHeight = [image size].height -
(fullRows * size.height);
if (cols > fullColumns) fullColumns++;
if (rows > fullRows) fullRows++;
CGImageRef fullImage = [image CGImage];
for (int y = 0; y < fullRows; ++y) {
for (int x = 0; x < fullColumns; ++x) {
CGSize tileSize = size;
if (x + 1 == fullColumns && remainderWidth > 0) {
// Last column
tileSize.width = remainderWidth;
}
if (y + 1 == fullRows && remainderHeight > 0) {
// Last row
tileSize.height = remainderHeight;
}
CGImageRef tileImage = CGImageCreateWithImageInRect(fullImage,
(CGRect){{x*size.width, y*size.height},
tileSize});
NSData *imageData = UIImagePNGRepresentation([UIImage imageWithCGImage:tileImage]);
NSString *path = [NSString stringWithFormat:#"%#/%d.png",
directoryPath, prefix];
[imageData writeToFile:path atomically:NO];
}
}
}
I have implemented solution for the similar problem(the difference is, I was not saving them in directory, those were for display purpose only.), with different approach.
In my problem, I have 84 images of 250x250 dimension with size 8KB each( I added them on scrollView and on scrolling I load them, a bit similar to google maps, but more smooth). At first I was using the same approach as yours, but performance was problem. So, I used asynchornous loading concept. I wrote an UIImageView subclass with connectiond delegates, so the UIImageView subclass was responsible for loading it's image. And as loading is asynchronous so performance is far better.
As you asked
1) Memory consumption -- will the memory consumption for 5 images of size 200 KB a lot?
Ans : 5x200KB = 1MB ~ 1.2MB or so(so you will need that much memory for displaying, if you have that much amount of memory then you should not worry.).. in my case 84x8KB = 672 ~ 900KB(as I was using some additional things like activity indicator for each imageview).
2) How fast I can process my app if I have to tile 5 different URL with images at the same time ?
Ans : As you are loading it in viewDidLoad ... or in main thread then performance will be an issue(blocking may happen, as I am not completely sure whether you are using threads or not).
Quick suggestion:
1. write an UIImageView subclass which has connection delegate methods.
2. have some method that you can call from outside to message this imageView to start loading.(give the url)
3. do proper deallocation of resources like responseData and connection object, once the downloading is complete.
4. when you move from this view to other view do proper deallocation and removal of all these imageviews.
5. use intruments to look for the allocations by this.
CODE :
TileImageView.h
#interface TileImageView : UIImageView
{
NSURLConnection *serverConnection;
BOOL isImageRequested;
NSMutableData *responseData;
}
-(void) startImageDownloading:(NSString *)pRequestURL
-(void) deallocateResources;
-(BOOL) isImageRequested;
-(void)cancelConnectionRequest;
-(void) addActivityIndicator;
-(void) removeActivityIndicator;
#end
TileImageView.m
#implementation TileImageView
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self)
{
// Initialization code.
isImageRequested = NO;
}
return self;
}
-(BOOL) isImageRequested
{
return isImageRequested;
}
-(void) startImageDownloading:(NSString *)pRequestURL
{
if (!isImageRequested)
{
NSURL *pServerURL = [[NSURL alloc] initWithString:pRequestURL];
if (pServerURL != nil)
{
isImageRequested = YES;
[self addActivityIndicator];
[self setBackgroundColor:[UIColor lightGrayColor]];
NSURLRequest *pServerRequest = [[NSURLRequest alloc]initWithURL:pServerURL];
serverConnection = [[NSURLConnection alloc] initWithRequest:pServerRequest delegate:self];
if(serverConnection)
{
responseData = [[NSMutableData alloc] init];
}
[pServerURL release];
[pServerRequest release];
}
}
}
-(void) addActivityIndicator
{
UIActivityIndicatorView *tempActivityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
CGFloat size = self.frame.size.width*0.12;
[tempActivityIndicator setFrame:CGRectMake(0, 0, size, size)];
[tempActivityIndicator setCenter:CGPointMake(self.frame.size.width/2, self.frame.size.height/2)];
[tempActivityIndicator setTag:1000];
[tempActivityIndicator setHidesWhenStopped:YES];
[tempActivityIndicator startAnimating];
[self addSubview:tempActivityIndicator];
[tempActivityIndicator release];
}
-(void) removeActivityIndicator
{
UIActivityIndicatorView *tempActivityIndicator = (UIActivityIndicatorView *)[self viewWithTag:1000];
if (tempActivityIndicator != nil)
{
[tempActivityIndicator stopAnimating];
[tempActivityIndicator removeFromSuperview];
}
}
-(void)cancelConnectionRequest
{
if (isImageRequested && serverConnection != nil)
{
[serverConnection cancel];
[self removeActivityIndicator];
[self deallocateResources];
isImageRequested = NO;
}
}
// Name : connection: didReceiveAuthenticationChallenge:
// Description : NSURLConnectionDelegate method. Method that gets called when server sends an authentication challenge.
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust])
{
[challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];
}
[challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];
}
// Name : connection: didReceiveResponse:
// Description : NSURLConnectionDelegate method. Method that gets called when response for the launched URL is received..
-(void) connection:(NSURLConnection *) connection didReceiveResponse:(NSURLResponse *) response
{
[responseData setLength:0];
}
// Name : connection: didReceiveData:
// Description : NSURLConnectionDelegate method. Method that gets called when data for the launched URL is received..
-(void) connection:(NSURLConnection *) connection didReceiveData:(NSData *) data
{
[responseData appendData:data];
}
// Name : connection: didFailWithError:
// Description : NSURLConnectionDelegate method. Method that gets called when an error for the launched URL is received..
-(void) connection:(NSURLConnection *) connection didFailWithError:(NSError *) error
{
NSLog(#"Error occured while loading image : %#",error);
[self removeActivityIndicator];
[self deallocateResources];
UILabel *tempLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 150, 30)];
[tempLabel setBackgroundColor:[UIColor clearColor]];
[tempLabel setFont:[UIFont systemFontOfSize:11.0f]];
[tempLabel setCenter:CGPointMake(self.frame.size.width/2, self.frame.size.height/2)];
[tempLabel setText:#"Image not available."];
[self addSubview:tempLabel];
[tempLabel release];
}
// Name : connectionDidFinishLoading
// Description : NSURLConnectionDelegate method. Method that gets called when connection loading gets finished.
-(void) connectionDidFinishLoading:(NSURLConnection *) connection
{
[self removeActivityIndicator];
UIImage *tempImage = [[UIImage alloc] initWithData:responseData];
self.image = tempImage;
[tempImage release];
[self deallocateResources];
}
-(void) deallocateResources
{
if (serverConnection != nil)
{
[serverConnection release];
serverConnection = nil;
}
if (responseData != nil)
{
[responseData release];
responseData = nil;
}
}
- (void)dealloc {
[super dealloc];
}
#end
So, If you use above code then only thing you have to do is to add the object of TileImageView and just call method -(void) startImageDownloading:(NSString *)pRequestURL.
Please use instruments to track allocations.
Update :
**How do I add TileImageView on scrollView ? :**
//like this I add 84 images in a 2D shape( 12 x 7) grid ... and once Images are added I set scrollView's contentSize as per complete grid size.
TileImageView *tileImageView = [[TileImageView alloc]initWithFrame:<myFrameAsPerMyNeeds>];
[tileImageView setTag:<this is the identifier I use for recognizing the image>];
[myImageScrollView addSubView:tileImageView];
[tileImageView release];
..later in code when user scroll's and other imageviews come in visibility.I use following code...
TileImageView *loadableImageView = (TileImageView *)[myImageScrollView viewWithTag:];
[loadableImageView startImageDownloading:];
I do not need to do anything in drawRect: , as I have no need to do custome drawing.
For Image names you can use tag property from imageView, but if you need some different name that are more like string then you can put another property in imageView for image name and set it while adding the image view. for saving data you can call your method once the image is downloaded in didFinishLoading method of TileImageView, where you can use that name.
SECODN UPDATE
How I add TileImageView on ScrollView
gridCount = 0;
rows = 7;
columns = 12;
totalGrids = rows*columns;
//*above : all are NSInteger type variable declared at class level
chunkWidth = 250;
chunkHeight = 250;
contentWidth = 0.0;
contentHeight = 0.0;
//*above : all are CGFloat type variable declared at class level
for (int i=0; i<rows; i++)
{
contentWidth = 0.0;
for (int j=0 ; j<columns; j++)
{
gridCount++;
CGRect frame = CGRectMake(contentWidth, contentHeight, chunkWidth, chunkHeight);
[self addNewImageViewWithTag:gridCount frame:frame];
contentWidth += chunkWidth;
}
contentHeight += chunkHeight;
}
[imageScrollView setContentSize:CGSizeMake(contentWidth, contentHeight)];
[imageScrollView setContentOffset:CGPointMake(0, 0)];
[imageScrollView setUserInteractionEnabled:YES];
And in ScrollViewDelegate method.
- (void) scrollViewDidScroll:(UIScrollView *)scrollView
{
if (isZoomed)
{
xOffset = scrollView.contentOffset.x;
yOffset = scrollView.contentOffset.y;
//*above : both are CGFloat type variable declared at class level
visibleColumn = xOffset/chunkWidth+1;
visibleRow = yOffset/chunkHeight+1;
gridNumber = (visibleRow-1)*columns+visibleColumn;
adjGrid1 = gridNumber+1;
adjGrid2 = gridNumber+columns;
adjGrid3 = adjGrid2+1;
//*above : all are NSInteger type variable declared at class level
if (gridNumber ==1)
{
[self createAndSendScrollRequest:gridNumber];
}
if (adjGrid1 > 0 && adjGrid1 <= totalGrids)
{
[self createAndSendScrollRequest:adjGrid1];
}
if (adjGrid2 > 0 && adjGrid2 <= totalGrids)
{
[self createAndSendScrollRequest:adjGrid2];
}
if (adjGrid3 > 0 && adjGrid3 <= totalGrids)
{
[self createAndSendScrollRequest:adjGrid3];
}
}
}
And this is how createAndSendScrollRequest is implemented.
- (void) createAndSendScrollRequest:(NSInteger)chunkId
{
TileImageView *loadingImageView = (TileImageView *)[imageScrollView viewWithTag:chunkId];
if ([loadingImageView image]==nil)
{
[loadingImageView startImageDownloading:<and here I pass url my url is based on tag so In reality I dont pass anything I just use it from the imageview's tag property>];
}
}
Thanks,

iphone: error and pdf pages question

I am building a pdf viewer app. my skills are at medium LOL voodoo level now... I've got real far on my own with this app but I'm pretty stuck ether on my theory or on my code. My app has a paging scroll view that makes its self the length of the entire pdf document, then it shows the current page in another scrollview (ImageScrollView) which is its own class. ImageScrollView makes a UIView which does the CATiledLayer usual stuff and it all work fine! :)
My ImageScrollView shows the on-screen page (when you scroll to the next page ImageScrollView loads again CATiledLayer on-screen and you can see the tiles). I've been researching about how to get the pages left and right of the current page to preload (as I don't think having a pdf load as tiles on-screen is good for the user experience) but Im not to sure if I'm thinking about it correctly.
Maybe I should be making a left and right UIView that sit next to the onscreen UIView in ImageScrollView?
or maybe it has to do with recycling no-longer-visible pages as seen below (but I think I would still need views to the left/right and even still wont I need to recycle the views also??)
- (void)tilePages {
// Calculate which pages are visible
CGRect visibleBounds = pagingScrollView.bounds;//CGRect visibleBounds = CGRectMake(0.0f, 0.0f, 320.0f * [self pdfPageCount], 435.0f);
int firstNeededPageIndex = floorf(CGRectGetMinX(visibleBounds) / CGRectGetWidth(visibleBounds));
int lastNeededPageIndex = floorf((CGRectGetMaxX(visibleBounds)-1) / CGRectGetWidth(visibleBounds));
firstNeededPageIndex = MAX(firstNeededPageIndex, 0);
lastNeededPageIndex = MIN(lastNeededPageIndex, [self pdfPageCount] - 1);
// Recycle no-longer-visible pages
for (ImageScrollView *page in visiblePages) {
if (page.index < firstNeededPageIndex || page.index > lastNeededPageIndex) {
[recycledPages addObject:page];
[page removeFromSuperview];}}
[visiblePages minusSet:recycledPages];
// add missing pages
for (int index = firstNeededPageIndex; index <= lastNeededPageIndex; index++) {
if (![self isDisplayingPageForIndex:index]) {
ImageScrollView *page = [self dequeueRecycledPage];
if (page == nil) {
page = [[[ImageScrollView alloc] init] autorelease];}
[self configurePage:page forIndex:index];
[pagingScrollView addSubview:page];
[visiblePages addObject:page];}}}
I changed the code to see what happened below (not sure) also I get error: initialization makes pointer from integer without a cast
- (void)tilePages:(NSUInteger) index {
// Calculate which pages are visible
CGRect visibleBounds = pagingScrollView.bounds;
int firstNeededPageIndex = floorf(CGRectGetMinX(visibleBounds) / CGRectGetWidth(visibleBounds));
int lastNeededPageIndex = floorf((CGRectGetMaxX(visibleBounds)-1) / CGRectGetWidth(visibleBounds));
firstNeededPageIndex = MAX(firstNeededPageIndex, 0);
lastNeededPageIndex = MIN(lastNeededPageIndex, [self pdfPageCount] - 1);
// Recycle no-longer-visible pages
for (ImageScrollView *page in visiblePages) {
if (page.index < firstNeededPageIndex || page.index > lastNeededPageIndex) {
// visible N/Ppages *start*
if (index == 1) {
ImageScrollView *Npage = Npage.index +1;
Npage = [[[ImageScrollView alloc] init] autorelease];
[self configurePage:Npage forIndex:index +1];
[pagingScrollView addSubview:Npage];
[visiblePages addObject:Npage];}
if (index < 2 || index > [self pdfPageCount] -2) {
ImageScrollView *Ppage = Ppage.index -1;
ImageScrollView *Npage = Npage.index +1;
Ppage = [[[ImageScrollView alloc] init] autorelease];
[self configurePage:Ppage forIndex:index -1];
[pagingScrollView addSubview:Ppage];
[visiblePages addObject:Ppage];
Npage = [[[ImageScrollView alloc] init] autorelease];
[self configurePage:Npage forIndex:index +1];
[pagingScrollView addSubview:Npage];
[visiblePages addObject:Npage];}
if (index == [self pdfPageCount] -1) {
ImageScrollView *Ppage = Ppage.index -1;
Ppage = [[[ImageScrollView alloc] init] autorelease];
[self configurePage:Ppage forIndex:index -1];
[pagingScrollView addSubview:Ppage];
[visiblePages addObject:Ppage];}
// visible N/Ppages *end*
[recycledPages addObject:page];
[page removeFromSuperview];}}
[visiblePages minusSet:recycledPages];
// add missing pages
for (int index = firstNeededPageIndex; index <= lastNeededPageIndex; index++) {
// recycled N/Ppages *start*
if (index == firstNeededPageIndex +1) {
ImageScrollView *Npage = Npage.index +1;
[recycledPages addObject:Npage];
[Npage removeFromSuperview];}
if (index < firstNeededPageIndex +2 || index > lastNeededPageIndex -2) {
ImageScrollView *Ppage = Ppage.index -1;
ImageScrollView *Npage = Npage.index +1;
[recycledPages addObject:Ppage];
[Ppage removeFromSuperview];
[recycledPages addObject:Npage];
[Npage removeFromSuperview];}
if (index == lastNeededPageIndex -1) {
ImageScrollView *Ppage = Ppage.index -1;
[recycledPages addObject:Ppage];
[Ppage removeFromSuperview];}
// recycled N/Ppages *end*
if (![self isDisplayingPageForIndex:index]) {
ImageScrollView *page = [self dequeueRecycledPage];
if (page == nil) {
page = [[[ImageScrollView alloc] init] autorelease];}
[self configurePage:page forIndex:index];
[pagingScrollView addSubview:page];
[visiblePages addObject:page];}}}
Could I store 3 pdf pages in an NSIndex? eg: previousPageToRecycle, previousPage, currentPage, nextPage, nextPageToRecycle
I'm unsure of how to do this.
ImageScrollView *Ppage = Ppage.index -1;
Ppage.index -1 is an integer. You're assigning it to a variable of type ImageScrollView*, which is supposed to magically turn it into a pointer. This is probably a mistake.
The first bit of code is much more readable, and looks like it's vaguely on the right track.
The second bit of code is completely unreadable due to lack of sane indenting. There are also some problems:
There are far too many special cases. Simplify your logic; it makess your code easier to get right and easier to maintain.
There is a lot of duplicated code. This is related to the special-casing.
I can't for the life of me figure out why firstNeededPageIndex+1 and lastNeededPageIndex-1 are special.
I would not use an NSSet for so few pages. I don't think being able to use minusSet: is a significant advantage over just using removeObject: (and if you're worried about performance, just use removeObjectIdenticalTo:)
I'm not sure what you mean by "storing pages in an NSIndex"; do you mean CFIndex or NSInteger?
And now, an overall answer:
In general, you're on the right track, but you need to load up to five pages:
The current page, i.
Pages i-1 and i+1. The user can start scrolling at any time; you don't want the display to lag while you render some tiles.
Pages i-2 and i+2. Bouncy-scrolling means if the user scrolls from i to i+1, it will show a few pixels of i+2. You don't want to load pages during the scroll animation, or it'll appear to lag (unless you can make it load in a background thread somehow). Note that if the user scrolls from i to i+1 to i+2, it'll "bounce" at i+3. You can load more, but I consider this uncommon enough that a little lag is justified.
I implemented this using a ring-buffer-like array of size 8; page i is stored in ring[i%8] if it is loaded. Since I only need to have 5 pages at a time, 8 is plenty; admittedly there's a largeish pile of ring-buffer-management code that you need to get right (mostly it just takes a while to code).
I also just used a "current page", loading i±1 during a scroll (hopefully they're already loaded) and i±2 at viewDidAppear: and at the end of a scroll (scrollViewDidEndDragging:willDecelerate: with decelerate=NO, and scrollViewDidEndDecelerating:).
You might also want to hold on to views a little longer (preload i±2 but keep i±3 if they happen to be loaded), otherwise views will be needlessly reloaded if the user moves back and forth during a scroll (laaaaaag).
If you show a page number, you might also want to add some hysteresis. I only changed pages when the user scrolled 2/3 of the way into the next page (that means to toggle between two pages, the user has to move back and forth by 1/3 page). If you implement hysteresis, the previous paragraph isn't such a big deal.

how do i scroll through 100 photos in UIScrollView in IPhone

I'm trying to scroll through images being downloaded from a users online album (like in the facebook iphone app) since i can't load all images into memory, i'm loading 3 at a time (prev,current & next). then removing image(prev-1) & image (next +1) from the uiscroller subviews.
my logic works fine in the simulator but fails in the device with this error:
[CALayer retain]: message sent to deallocated instance
what could be the problem
below is my code sample
- (void)scrollViewDidEndDecelerating:(UIScrollView *)_scrollView
{
pageControlIsChangingPage = NO;
CGFloat pageWidth = _scrollView.frame.size.width;
int page = floor((_scrollView.contentOffset.x - pageWidth / 2) / pageWidth) + 1;
if (page>1 && page<=(pageControl.numberOfPages-3)) {
[self removeThisView:(page-2)];
[self removeThisView:(page+2)];
}
if (page>0) {
NSLog(#"<< PREVIOUS");
[self showPhoto:(page-1)];
}
[self showPhoto:page];
if (page<(pageControl.numberOfPages-1)) {
//NSLog(#"NEXT >>");
[self showPhoto:page+1];
NSLog(#"FINISHED LOADING NEXT >>");
}
}
-(void) showPhoto:(NSInteger)index
{
CGFloat cx = scrollView.frame.size.width*index;
CGFloat cy = 40;
CGRect rect=CGRectMake( 0, 0,320, 480);
rect.origin.x = cx;
rect.origin.y = cy;
AsyncImageView* asyncImage = [[AsyncImageView alloc] initWithFrame:rect];
asyncImage.tag = 999;
NSURL *url = [NSURL URLWithString:[pics objectAtIndex:index]];
[asyncImage loadImageFromURL:url place:CGRectMake(150, 190, 30, 30) member:memberid isSlide:#"Yes" picId:[picsIds objectAtIndex:index]];
[scrollView addSubview:asyncImage];
[asyncImage release];
}
- (void) removeThisView:(NSInteger)index
{
if (index<[[scrollView subviews] count] && [[scrollView subviews] objectAtIndex:index]!=nil) {
if ([[[scrollView subviews] objectAtIndex:index] isKindOfClass:[AsyncImageView class]] || [[[scrollView subviews] objectAtIndex:index] isKindOfClass:[UIImageView class]]) {
[[[scrollView subviews] objectAtIndex:index] removeFromSuperview];
}
}
}
For the record it works OK in the simulator, but not the iphone device itself.
any ideas will be appreciated.
cheers,
fred.
I guess your AsyncImageView class uses asynchronous connections to load the image (which is a good thing), so that means that when you add it as a subview of your scroll view, it might be "removedFromSuperview" before the image loads completely. Check in the implementation of your class if the delegates are properly nil'd when it's been dealloc'd, and also that any connections are cancelled and so on.
Pay attention to the fact that a class should never retain its delegate, an "assign" kind of property is perfect for these situations.
Some tips:
Instead of alloc/init'ing and release'ing so many instances while you scroll, try to keep three ivars in your class, and just change the "frame" property of the instances as you scroll. You will avoid lots of allocation and releases if the user scrolls fast, and that's a good thing on an iPhone.
Avoid direct ivar accesses like that in your code; use properties, there are many advantages to them beyond the simple "correctness" thing (KVO being one of them).