0

In my iPhone app, I am dynamically adding a UIScrollView and adding n number of UIImages and UIButtons into the scrollview. Here, the images are loaded from different urls and the button titles are coming from SQlite database. Everything is fine. But when I scroll the scrollview, now I am getting memory warning Level=1 and after some time it is Level=2 and crashes the app. I am using ARC. How can I fix this problem?

Code

- (void)setUpViewLayout{
    int newContentSize = [appDelegate.itemArray count] * 125;

    menuItemIdArray = [[NSMutableArray alloc]init];
    mainView = [[UIScrollView alloc]initWithFrame:CGRectMake(0, 100, 480, 220)];
    mainView.contentSize = CGSizeMake(newContentSize, 220);
    mainView.tag = 100;
    mainView.delegate = self;
    mainView.userInteractionEnabled = YES;
    mainView.backgroundColor = [UIColor clearColor];

    int xPosition = 20;

    for (tagVal = 0; tagVal < [appDelegate.itemArray count]; tagVal++) {
        [self createImage:xPosition];
        [self createButton];
        xPosition = xPosition + 120;
    }
    [self.view addSubview:mainView];
}

- (void)createImage:(int)xPosition{
    DataBaseClass *itemObj = [appDelegate.itemArray objectAtIndex:tagVal];

    NSString *url = [NSString stringWithFormat:@"%@",itemObj.notAvialableIcon];
    imgView = [[UIImageView alloc]initWithFrame:CGRectMake(xPosition+8, 48, 110, 123)];
    imgView.userInteractionEnabled = YES;
    imgView.tag = tagVal;

    [imgView setImageWithURL:[NSURL URLWithString:url] placeholderImage:[UIImage imageNamed:@"item01.png"]];

    [mainView addSubview:imgView];
}
- (void)createButton{
    DataBaseClass *itemObj = [appDelegate.itemArray objectAtIndex:tagVal];
    button = [UIButton buttonWithType:UIButtonTypeCustom];
    button.frame = CGRectMake(5, 90, 100, 26);
    button.tag = tagVal;
    button.userInteractionEnabled = YES;
    button.tag = tagVal;
    button.titleLabel.textColor = [UIColor blueColor];
    button.titleLabel.font = [UIFont systemFontOfSize:9.0];
    NSString *name = [NSString stringWithFormat:@"%@",itemObj.itemStatus];
    itmName = [NSString stringWithFormat:@"%@",itemObj.itemName];

    NSString *date = [self changeDateFormat:itemObj.itemReleaseDate];          
    [button setTitle:date forState:UIControlStateNormal]; 
    button.userInteractionEnabled = NO;
    button setBackgroundImage:[UIImage imageNamed:@"not_available_bttn_bck_img"] forState:UIControlStateNormal];

    [imgView addSubview:button];
}
Kara
  • 6,115
  • 16
  • 50
  • 57
Mithuzz
  • 1,091
  • 14
  • 43
  • I think, it's not about scrolling your scroll view, if you load all subviews in scrollview all together then too this will come up with the same issue, the delay will be only while you images get loaded. – rptwsthi Jun 15 '12 at 10:26
  • so is there any other way to add the subviews, as the scrollview scrolls? – Mithuzz Jun 15 '12 at 10:29
  • try table view without separators.., and catch your images point to be noted reuse your cells.. – rptwsthi Jun 15 '12 at 11:05

7 Answers7

4
  1. Don't add all subviews to the scroll view at one time. That's too expensive.

  2. When scroll view did scroll, get the visible rect of the scroll view, and add your image and button just fit in than range or more than a little of that rect.

  3. When the visible subview is not visible, remove from the super view.

Wubao Li
  • 1,728
  • 10
  • 13
  • i think it would be little bit difficult to implement. Can you navigate me to some tips or tutorials for how to implement this? – Mithuzz Jun 15 '12 at 12:26
  • @John Generally, just schedule your subview when to add and when to remove from scroll view, calculate with the scroll view content offset – Wubao Li Jun 15 '12 at 12:37
2

You need to identify which part of your code causes the leak. You can do this several ways.

One way is to use the built in analyzer in xcode. It analyzes your code and detect (some) potential memory leaks.

The instruments tool is also a good tool to find these leaks. Start it using the allocation/leak component. Go to your scrollview, and do a sample after scrolling the view. Your leak should show up. Now you can track down the leak and have instruments locate the correct place in your code directly.

The third option is to go through your code and try and figure out what is happening yourself. Understanding memory managment is a vital part of programming for ios devices.

What about posting the code your are using in your scrollview here, so we can take a look?

Øystein
  • 1,163
  • 1
  • 12
  • 23
2

Abhishek is absolutely correct that all subviews must be released after being added to a superview. That will cause a leak. Specifically, once the scroll view comes off screen and is released, its subviews will not be released as they should. They will still have a retain count of 1, from when they were alloc'ed.

However, as long as the scroll view is still on screen, there is no leak. A superview retains all its subviews (i.e. increases their retain count by 1.) If a subview was alloc'ed but not released, it's retain count is 2. If it was alloc'ed and released its retain count is 1. Either way, as long as the scroll view exists, its subviews are still, correctly, retained.

If you are receiving memory warnings while the scroll view is still up, the problem may not be the leak, just over-usage of memory. If you keep adding images to a large scroll view, you will certainly run into memory overage problems.

To fill a large scroll view with images, but avoid memory overages, you might take a look at the ScrollViewSuite demo's third example, on tiling. That should work well for you since your images and buttons are the same size, and can act as the tiles.

The idea is to make a sort of table view out of the scroll view that now recycles image tiles instead of cells. The scroll view is subclassed and a set of reusable tiles is kept as one of its instance variables. The key to the implementation is, in layoutSubviews, to remove from superview the tiles that have moved out of the visible area, then recycle tiles for newly visible content and add them as subview. In this way, only visible tiles are loaded into memory. And, it recycles tiles just like a table view recycles cells.

From the size of your scroll view, it may be that you have no other option than to tile and recycle. Nonetheless, it's a good option.

Update: Wubao Li essentially summarizes what needs to be done. The ScrollViewSuite demo shows you how.

salo.dm
  • 2,317
  • 1
  • 15
  • 16
  • You may be right, but I don't know. I'm not familiar with ARC, and I also don't know whether it has other requirements. – salo.dm Jun 15 '12 at 12:00
  • @John remove it from super view, then It should be auto released. When the view is a subview of some view, there is a strong relation between them. ARC Will not know when to auto release such an object. – Wubao Li Jun 15 '12 at 12:24
2

I'm opening a new response for this one. It's the simple solution we all missed.

From the code you posted, this is just a table view on its side. So, you don't have to build your own tiled scroll view.

Here's a bit of code to get you started. When you set up the table view, rotate it by 90 degrees, set the row height and eliminate the separator lines:

tableView.transform = CGAffineTransformMakeRotation(0.5 * M_PI);
tableView.rowHeight = 120.0;
tableView.separatorStyle = UITableViewCellSeparatorStyleNone;

You'll have to set the table view's frame so that it's in the correct position after rotation. Essentially, it's the same as your current scroll view's frame, or as that frame on its side.

Here are a couple of the table view's data source methods:

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return [appDelegate.itemArray count];
}

The table cells can be very simple custom table cells that just have a single image view and a button on that image view. You might also rotate the image view so that the image is displayed correctly, not on its side. Or, you could rotate all your images in a photo editor or image editor before loading them.

That's pretty much it. The table view, as always, will take care of recycling your cells and optimizing your memory usage.

salo.dm
  • 2,317
  • 1
  • 15
  • 16
1
//you had allocated the things but did not release it ... it was the reason of leak


- (void)setUpViewLayout{
int newContentSize = [appDelegate.itemArray count] * 125;

// menuItemIdArray = [[NSMutableArray alloc]init]; why you are allocating this array

UIScrollView *mainView = [[UIScrollView alloc]initWithFrame:CGRectMake(0, 100, 480, 220)];
mainView.contentSize = CGSizeMake(newContentSize, 220);
mainView.tag = 100;
mainView.delegate = self;
mainView.userInteractionEnabled = YES;
mainView.backgroundColor = [UIColor clearColor];

int xPosition = 20;

for (tagVal = 0; tagVal < [appDelegate.itemArray count]; tagVal++) {
    [self createImage:xPosition];
    [self createButton];
    xPosition = xPosition + 120;
}
[self.view addSubview:mainView];
[mainView relese];//release scroll view here
}

- (void)createImage:(int)xPosition{
DataBaseClass *itemObj = [appDelegate.itemArray objectAtIndex:tagVal];

NSString *url = [NSString stringWithFormat:@"%@",itemObj.notAvialableIcon];
imgView = [[UIImageView alloc]initWithFrame:CGRectMake(xPosition+8, 48, 110, 123)];
imgView.userInteractionEnabled = YES;
imgView.tag = tagVal;

   [imgView setImageWithURL:[NSURL URLWithString:url] placeholderImage:[UIImage  imageNamed:@"item01.png"]];

   [mainView addSubview:imgView];
   [imgView release]; //release imageview here
}
 - (void)createButton{
DataBaseClass *itemObj = [appDelegate.itemArray objectAtIndex:tagVal];
button = [UIButton buttonWithType:UIButtonTypeCustom];
button.frame = CGRectMake(5, 90, 100, 26);
button.tag = tagVal;
button.userInteractionEnabled = YES;
button.tag = tagVal;
button.titleLabel.textColor = [UIColor blueColor];
button.titleLabel.font = [UIFont systemFontOfSize:9.0];
NSString *name = [NSString stringWithFormat:@"%@",itemObj.itemStatus];
itmName = [NSString stringWithFormat:@"%@",itemObj.itemName];

NSString *date = [self changeDateFormat:itemObj.itemReleaseDate];          
[button setTitle:date forState:UIControlStateNormal]; 
button.userInteractionEnabled = NO;
button setBackgroundImage:[UIImage imageNamed:@"not_available_bttn_bck_img"] forState:UIControlStateNormal];

[imgView addSubview:button];
}

may it help you

Abhishek
  • 2,255
  • 1
  • 13
  • 21
  • thanks for your work. but if I release mainView in the first method, does that make any issue if i use that in some other places? And also I am declaring scrollView in .h file and using ARC. So, should I use release? – Mithuzz Jun 15 '12 at 10:40
  • set the tag of scroll view and access it with the tag where you want to use it... like UIScrollView *scrollView = (UIScrollView*)[self.view viewWithTag:tag_of_scrolView]; avoid to make things global.... – Abhishek Jun 15 '12 at 10:44
1

There are 3 suggestions for you here:

  • Try loading images in background thread
  • Check this response Does iOS 5 have garbage collection?
  • Use leak, instrument to find out where your application is leaking, and then manage that part for the best
Community
  • 1
  • 1
rptwsthi
  • 10,094
  • 10
  • 68
  • 109
  • 1
    Actually what i have found till yet is if your application exceeds boundary of 14mb, your application get crashed. So try managing memory more better if you are loading lots of images.. – rptwsthi Jun 15 '12 at 11:03
1

This is the bug of Apple. UIScrollView will LEAK even these codes:

UIScrollView *s = [[UIScrollView alloc] initWithFrame:self.view.bounds];
s.contentSize = CGSizeMake(320, 800);
[self.view addSubview:s];
[s release];
nealx
  • 11
  • 1