Advertise Mobile SDKs Books Events Forum News Social Networking Support Us
Follow @iphonedevsdk on Twitter

Interface 2, Advanced iOS
Mockup & Code Gen
($9.99)

Make your own iPhone apps
and run them live!
(free)

Pic Frame Dynamo: Photo Editing
($0.99)

Abiliator
($1.99)

Want your application or service advertised on iPhone Dev SDK?

Go Back   iPhone Dev SDK Forum > iPhone SDK Development Forums > iPhone SDK Development

Reply
 
LinkBack Thread Tools Display Modes
Old 04-18-2011, 02:47 PM   #1 (permalink)
Registered Member
 
Join Date: Jan 2011
Posts: 4
CambodianBreastMilk is on a distinguished road
Default UIScrollView with multiple sub UIScrollViews - how to keep memory usage down?

Hey guys, I've been browsing/reading here for a while but this is my first post so go easy on me I spent some time searching here and other places for a possible solution but am still stuck.

I have an app that is essentially an full screen image viewer that pulls images from the web. The app is based on Apple's PhotoScroller app except I have replaced the UIImageViews in ImageScrollView with UIWebViews.

The original PhotoScroller example by Apple recycles the UIScrollView subviews to keep memory usage low. For some reason when I switched to UIWebViews I lost this memory control and now memory usage climbs with every page swipe.

I've read of a few suggested methods for handling memory with UIWebViews and I've tried to implement them, but they have not help. I've also tried to release the unused UIViews when not in view however this crashes the app (without an error). I also tried looking for leaks and allocations with Instruments and did not find anything helpful, however I'll admit I wasn't sure what to look for.

Any help or guidance would be much appreciated!

Attached relevant bits of code:

AppViewController.h
Code:
@class WebScrollView;

@interface MyViewController : UIViewController <UIScrollViewDelegate> {
  IBOutlet UIScrollView *pagingScrollView;
  NSArray *imageList;
  NSMutableSet *recycledPages;
  NSMutableSet *visiblePages;
}

@property (nonatomic, retain) NSArray *imageList;
AppViewController.m
Code:
- (void)viewDidLoad {
  pagingScrollView.pagingEnabled = YES;
  pagingScrollView.showsVerticalScrollIndicator = NO;
  pagingScrollView.showsHorizontalScrollIndicator = NO;
  pagingScrollView.delegate = self;
  [pagingScrollView setContentOffset:CGPointMake(0, 0) animated:NO];
  
  // imageList is a plist feed of images
  imageList = [NSArray arrayWithContentsOfURL:[NSURL URLWithString:@"http://example.com/list.plist"]];

  [self tilePages];
}

//
// Tile paging & view recycling
//

- (void)tilePages {

  // Calculate which pages are visible
  CGRect visibleBounds = pagingScrollView.bounds;
  int currentPage = floorf(CGRectGetMinX(visibleBounds) / CGRectGetWidth(visibleBounds));
  currentPage = MAX(currentPage, 0);
  int lastNeededPageIndex  = floorf((CGRectGetMaxX(visibleBounds)-1) / CGRectGetWidth(visibleBounds));
  lastNeededPageIndex = MIN(lastNeededPageIndex, [imageList count] - 1);
  
  // add missing pages
  for (int index = currentPage; index <= lastNeededPageIndex; index++) {
    if (![self isDisplayingPageForIndex:index]) {
      [self loadPage:index];
    }
  }    

}

- (void)loadPage:(NSNumber*)index {
  WebScrollView *page = [self dequeueRecycledPage];
  if (page == nil) {
    page = [[WebScrollView alloc] init];
  }
  [self configurePage:page forIndex:[index intValue]];
  [pagingScrollView addSubview:page];
  [visiblePages addObject:page];
}  

- (WebScrollView *)dequeueRecycledPage {
    WebScrollView *page = [recycledPages anyObject];
    if (page) {
        [[page retain] autorelease];
        [recycledPages removeObject:page];
    }
    return page;
}

- (BOOL)isDisplayingPageForIndex:(NSUInteger)index {
  BOOL foundPage = NO;
  for (WebScrollView *page in visiblePages) {
        if (page.index == index) {
            foundPage = YES;
            break;
        }
    }
    return foundPage;
}

- (void)configurePage:(WebScrollView *)page forIndex:(NSUInteger)index {
  page.index = index;
  page.frame = [self frameForPageAtIndex:index];
  NSString *imageURL = [self.imageList objectAtIndex:index];
  [page displayPage:imageURL];
}

//
// Scroll view delegates
//

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    [self tilePages];
}

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
  // Calculate which pages are visible
  CGRect visibleBounds = pagingScrollView.bounds;
  int currentPage = floorf(CGRectGetMinX(visibleBounds) / CGRectGetWidth(visibleBounds));
  currentPage = MAX(currentPage, 0);
  int lastNeededPageIndex  = floorf((CGRectGetMaxX(visibleBounds)-1) / CGRectGetWidth(visibleBounds));
  lastNeededPageIndex = MIN(lastNeededPageIndex, [imageList count] - 1);

  // Recycle pages more than kNumberOfCachedPages spaces away
  for (WebScrollView *page in visiblePages) {
    signed int compareFirst = currentPage - page.index;
    if ( compareFirst > kNumberOfCachedPages) { 
      [recycledPages addObject:page];
      [page removeFromSuperview];
//    [page release]; // If I attempt to release the page UIView here, the app will crash
    }
    signed int compareLast = page.index - lastNeededPageIndex;
    if ( compareLast > kNumberOfCachedPages) {
      [recycledPages addObject:page];
      [page removeFromSuperview];
//    [page release]; // If I attempt to release the page UIView here, the app will crash
    }
  }
  [visiblePages minusSet:recycledPages];
}
WebScrollView.h
Code:
@interface WebScrollView : UIScrollView <UIScrollViewDelegate, UIWebViewDelegate> {
  UIView *imageView;
  NSUInteger index;
  NSURL	*url;
	NSURLRequest *requestObj;
	UIWebView	*webView;
}

@property (assign) NSUInteger index;
- (void)displayPage:(NSString *)image;
WebScrollView.m
Code:
@implementation WebScrollView
@synthesize index;

- (id)initWithFrame:(CGRect)frame {
  if ((self = [super initWithFrame:frame])) {
    self.showsVerticalScrollIndicator = NO;
    self.showsHorizontalScrollIndicator = NO;
    self.bouncesZoom = YES;
    self.decelerationRate = UIScrollViewDecelerationRateFast;
    self.delegate = self;        
  }
  return self;
}

- (void)dealloc {
  [imageView release];
  imageView = nil;
  [requestObj release];
  requestObj = nil;
  [url release];
  url = nil;
  webView.delegate = nil;
  webView = nil;
  [webView release];
  [super dealloc];
}

- (void)displayPage:(NSString *)image {
  // clear the previous imageView
  [imageView removeFromSuperview];
  [imageView release];
  imageView = nil;

  url = [NSURL URLWithString:image];
  requestObj = [NSURLRequest requestWithURL:url];

  CGRect webFrame = [[UIScreen mainScreen] applicationFrame];
  webFrame.origin.y = 0.0f;
  webView = [[[UIWebView alloc] initWithFrame:webFrame] autorelease];
  [webView loadRequest:requestObj];
  webView.scalesPageToFit = YES;

  [self addSubview:webView];
}

//
// UIWebview delegates
//

- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error {
  UIAlertView *alert = [[[UIAlertView alloc] initWithTitle:@"Network access unavailable" message:nil delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil] autorelease];
  [alert show];
}

- (void)webViewDidStartLoad:(UIWebView *)awebView {
    [awebView.delegate retain];
}

- (void)webViewDidFinishLoad:(UIWebView *)awebView {   
    [awebView.delegate release];
}

- (void)awebView:(UIWebView *)awebView didFailLoadWithError:(NSError *)error {
    [awebView.delegate release];
}

-(void)viewWillDisappear:(BOOL)animated {
    if (webView.loading) {
        [webView stopLoading];
    }
}

Last edited by CambodianBreastMilk; 04-27-2011 at 08:50 AM.
CambodianBreastMilk is offline   Reply With Quote
Old 04-18-2011, 03:47 PM   #2 (permalink)
Senior Member
iPhone Dev SDK Supporter
 
Join Date: Aug 2008
Location: Memphis, TN, USA
Age: 24
Posts: 3,983
smithdale87 is on a distinguished road
Send a message via AIM to smithdale87
Default

It's pretty clear from your code and your use of alloc's,releases, retains, and autoreleases, that you still are not very familiar with how memory management works in iOS. You might want to do some googling on that subject.
smithdale87 is offline   Reply With Quote
Old 04-26-2011, 08:23 PM   #3 (permalink)
Registered Member
 
Join Date: Jan 2011
Posts: 4
CambodianBreastMilk is on a distinguished road
Default

Quote:
Originally Posted by smithdale87 View Post
It's pretty clear from your code and your use of alloc's,releases, retains, and autoreleases, that you still are not very familiar with how memory management works in iOS. You might want to do some googling on that subject.
So I took your advice and went and read everything I could find on memory management. I think I have a better handle of how allocations/retains/releases work in iOS and I've made several changes to the app. Unfortunately, I still have uncontrollable memory usage. Maybe a leak? I'm not sure, Instruments finds nothing.

I've changed the app by switching from UIWebViews back to UIImageViews and am using HJCache to have the images load nicely.

It doesn't seem like the ImageView object is being dealloc'd even though it's in the autorelease pool and is getting removeFromSuperview'd. If I initialize ImageView with an alloc and then release it while or instead of removeFromSuperview'ing it, the app will scroll once then crash. I understand it will crash because something is trying to access the deallocated object, but am not sure how to troubleshoot from there.

Does anyone have any suggestions or pointers? I've spent many frustrating hours trying to figure this out so any insight would be much appreciated!

Here's my changes:

AppViewController.h
Code:
@class ImageView;

@interface FloridaWeeklyViewController : UIViewController <UIScrollViewDelegate, UITableViewDelegate, UITableViewDataSource, UIActionSheetDelegate> 
{
  IBOutlet UIScrollView *pagingScrollView;
  NSArray *imageList;
  NSMutableSet *recycledPages;
  NSMutableSet *visiblePages;
}

@property (nonatomic, retain) IBOutlet UIScrollView *pagingScrollView;
@property (nonatomic, retain) NSArray *imageList;
@property (nonatomic, retain) NSMutableSet *recycledPages;
@property (nonatomic, retain) NSMutableSet *visiblePages;

- (void)swipePages;
- (BOOL)isDisplayingPageForIndex:(NSUInteger)index;
- (void)loadPage:(int)index;

@end
AppViewController.m
Code:
@implementation MyViewController

@synthesize pagingScrollView;
@synthesize imageList;
@synthesize recycledPages;
@synthesize visiblePages;

#pragma mark -
#pragma mark View loading and unloading

- (void)viewDidLoad
{
  // Get the image file list and set the "Visit website" button URL
  [self setImageList:[NSArray arrayWithContentsOfURL:[NSURL URLWithString:@"http://some-url.com/list.plist"]]];
  
  // Configure the scrollview
  CGRect pagingScrollViewFrame = [self frameForPagingScrollView];
  pagingScrollView.frame = pagingScrollViewFrame;
  pagingScrollView.contentSize = CGSizeMake(pagingScrollViewFrame.size.width * [imageList count], pagingScrollViewFrame.size.height);
  pagingScrollView.pagingEnabled = YES;
  pagingScrollView.showsVerticalScrollIndicator = NO;
  pagingScrollView.showsHorizontalScrollIndicator = NO;
  pagingScrollView.delegate = self;
  
  // Load the first page
  [self swipePages];
}

- (void)dealloc 
{
  [pagingScrollView release];
  [imageList release];
  [recycledPages release];
  [visiblePages release];
  [super dealloc];
}

#pragma mark -
#pragma mark Page configuration

- (void)swipePages 
{
  // Calculate which pages are visible
  CGRect visibleBounds = pagingScrollView.bounds;
  int currentPage = floorf(CGRectGetMinX(visibleBounds) / CGRectGetWidth(visibleBounds));
  currentPage = MAX(currentPage, 0);
  int lastNeededPageIndex  = floorf((CGRectGetMaxX(visibleBounds)-1) / CGRectGetWidth(visibleBounds));
  lastNeededPageIndex = MIN(lastNeededPageIndex, [imageList count] - 1);

  // Add the needed page or pages
  for (int index = currentPage; index <= lastNeededPageIndex; index++) {
    if (![self isDisplayingPageForIndex:index]) {
       [self loadPage:index];
    }
  }    
}

- (BOOL)isDisplayingPageForIndex:(NSUInteger)index 
{
  BOOL foundPage = NO;
  for (ImageView *page in visiblePages) {
    if (page.index == index) {
      foundPage = YES;
      break;
    }
  }
  return foundPage;
}

- (void)loadPage:(int)index 
{
  ImageView *page = [[[ImageView alloc] init] autorelease];

  // Configure page
  page.index = index;
  [page displayImage:[imageList objectAtIndex:index]]; 
  
  // Add page to scroll view
  [pagingScrollView addSubview:page];
  
  // Add page to list of visible pages
  [visiblePages addObject:page];
}  


// ScrollView delegate methods

- (void)scrollViewDidScroll:(UIScrollView *)scrollView 
{
  [self swipePages];
}

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {

  // Calculate which pages are visible
  CGRect visibleBounds = pagingScrollView.bounds;
  int currentPage = floorf(CGRectGetMinX(visibleBounds) / CGRectGetWidth(visibleBounds));
  currentPage = MAX(currentPage, 0);
  int lastNeededPageIndex  = floorf((CGRectGetMaxX(visibleBounds)-1) / CGRectGetWidth(visibleBounds));
  lastNeededPageIndex = MIN(lastNeededPageIndex, [imageList count] - 1);
  
  // Remove pages that are more than 1 pages out of view
  for (ImageView *page in visiblePages) {
    signed int compareFirst = currentPage - page.index;
    signed int compareLast = page.index - lastNeededPageIndex;
    if ( compareFirst > 1 || compareLast > 1) {
      [recycledPages addObject:page];
      [page removeFromSuperview];
    }
  }
  [visiblePages minusSet:recycledPages];
  
}
ImageView.h (replaced WebScrollView)
Code:
@interface ImageView : UIScrollView <UIScrollViewDelegate> {
  NSUInteger index;
  UIActivityIndicatorView *activityIndicator;
  HJObjManager *objMan;
  HJManagedImageV *mi;
}

@property (assign) NSUInteger index;
@property (nonatomic, retain) HJObjManager *objMan;
@property (nonatomic, retain) HJManagedImageV *mi;

- (void)displayImage:(NSString *)image;
- (void)loadImage:(NSString *)image;
- (void)removeSpinner:(UIActivityIndicatorView*)indicator;

@end
ImageView.m (replaced WebScrollView)
Code:
@implementation ImageView
@synthesize index;
@synthesize objMan;
@synthesize mi;
  
- (id)initWithFrame:(CGRect)frame {
  if ((self = [super initWithFrame:frame])) {
    // UIScrollView stuff
  }
  return self;
}

- (void)dealloc {
  [objMan release];
  [mi release];
  [super dealloc];
}

#pragma mark -
#pragma mark Image handling & rendering

- (void)displayImage:(NSString *)image {
    
  // reset our zoomScale to 1.0 before doing any further calculations
  self.zoomScale = 1.0;
  
  // Setup activity indicator
  activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
  activityIndicator.center = CGPointMake(768/2, 1000/2);
  activityIndicator.hidesWhenStopped = YES;
  [activityIndicator startAnimating];
  
  [self addSubview:activityIndicator];
  [self performSelectorOnMainThread:@selector(loadImage:) withObject:image waitUntilDone:NO];
  [self performSelector:@selector(removeSpinner:) withObject:activityIndicator  afterDelay:2.0];
  
}

- (void)loadImage:(NSString *)image {
  
  objMan = [[HJObjManager alloc] initWithLoadingBufferSize:2 memCacheSize:2];
  mi = [[HJManagedImageV alloc] initWithFrame:CGRectMake(0,0,748,1000)];
  mi.url = [NSURL URLWithString:image];
  [self addSubview:mi];
  [objMan manage:mi];
  
}

- (void)removeSpinner:(UIActivityIndicatorView*)indicator {
    [indicator stopAnimating];
    [indicator removeFromSuperview];
}

// UIScrollView delegates for zooming/pinching

@end // ImageScrollView
CambodianBreastMilk is offline   Reply With Quote
Old 04-28-2011, 08:32 AM   #4 (permalink)
Registered Member
 
Join Date: Jan 2011
Posts: 4
CambodianBreastMilk is on a distinguished road
Default

Bump.
CambodianBreastMilk is offline   Reply With Quote
Old 05-03-2011, 08:50 PM   #5 (permalink)
Registered Member
 
Join Date: Jan 2011
Posts: 4
CambodianBreastMilk is on a distinguished road
Default

I was able to resolve this memory issue by removing all of the page's subviews. I guess they're not removed/released with the parent view?

Code:
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {

  // Calculate which pages are visible
  CGRect visibleBounds = pagingScrollView.bounds;
  int currentPage = floorf(CGRectGetMinX(visibleBounds) / CGRectGetWidth(visibleBounds));
  currentPage = MAX(currentPage, 0);
  int lastNeededPageIndex  = floorf((CGRectGetMaxX(visibleBounds)-1) / CGRectGetWidth(visibleBounds));
  lastNeededPageIndex = MIN(lastNeededPageIndex, [imageList count] - 1);
  
  // Remove pages that are more than 1 pages out of view
  for (ImageView *page in visiblePages) {
    signed int compareFirst = currentPage - page.index;
    signed int compareLast = page.index - lastNeededPageIndex;
    if ( compareFirst > 1 || compareLast > 1) {
      [recycledPages addObject:page];
      for (UIView *subview in page.subviews) {
	[subview removeFromSuperview];
      }
      [page removeFromSuperview];
    }
  }
  [visiblePages minusSet:recycledPages];
  
}
CambodianBreastMilk is offline   Reply With Quote
Reply

Bookmarks

Tags
crash, memory, photoscroller, uiscrollview, uiwebview

Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off
Trackbacks are On
Pingbacks are On
Refbacks are On



» Advertisements
» Online Users: 350
14 members and 336 guests
dansparrow, dre, ilmman, LezB44, lorrettaui53, Nobbsy, Objective Zero, oztemel, pbart, samdanielblr, shagor012, sledzeppelin, thephotographer, Trickphotostudios
Most users ever online was 1,387, 04-10-2012 at 04:21 AM.
» Stats
Members: 175,663
Threads: 94,119
Posts: 402,896
Top Poster: BrianSlick (7,990)
Welcome to our newest member, LezB44
Powered by vBadvanced CMPS v3.1.0

All times are GMT -5. The time now is 01:47 AM.
Powered by vBulletin® Version 3.8.0
Copyright ©2000 - 2012, Jelsoft Enterprises Ltd.
Search Engine Friendly URLs by vBSEO 3.3.0