04-18-2011, 02:47 PM
#1 (permalink )
Registered Member
Join Date: Jan 2011
Posts: 4
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 .
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
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.
04-26-2011, 08:23 PM
#3 (permalink )
Registered Member
Join Date: Jan 2011
Posts: 4
Quote:
Originally Posted by
smithdale87
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
04-28-2011, 08:32 AM
#4 (permalink )
Registered Member
Join Date: Jan 2011
Posts: 4
Bump.
05-03-2011, 08:50 PM
#5 (permalink )
Registered Member
Join Date: Jan 2011
Posts: 4
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];
}
Thread Tools
Display Modes
Linear Mode
Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts
HTML code is Off
» 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