Memory leak when scrolling UIScrollView - iphone

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];
}

Don't add all subviews to the scroll view at one time. That's too expensive.
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.
When the visible subview is not visible, remove from the super view.

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.

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?

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.

//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

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

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];

Related

How to assign didSelectRowAtIndexPath in Tableview cells subview item

I have 2 problem and i am totally confuse. please help me
1) I face one problem that i want to assign click listener to image view which is inside of my subview and that subview is with in cell of UITableView.
In my tableview cell there are two image-view i want to give them click listener and also identify them like which image-view is clicked on which row etc etc.
if i write scrollview.userinteractionEnable=YES; than my didSelectRowAtIndexPath Not responds.
i know its because of subview.
But if i change scrollview.userinteractionEnable=NO; than my didselectrowatIndexpath code executes. but scrollview does not scroll to horizontal..
What do i do ? I want horizontal scroll plus Image-view click Listener on both ImageView.
** Solve click problem by crating scrollview sub class**
This is my Customcell class
cellScrollViewClass *scrollView = [[cellScrollViewClass alloc] initWithFrame:CGRectMake(0.0,0.0,430,187)];
scrollView.contentSize = CGSizeMake(scrollView.frame.size.width * 3,scrollView.frame.size.height);
scrollView.scrollEnabled=YES;
scrollView.userInteractionEnabled=YES;
for (int i = 0; i < 3; i++) {
CGRect frame;
frame.origin.x = scrollView.frame.size.width * i;
frame.origin.y = 0;
frame.size = scrollView.frame.size;
if(i==2) // to check Last item must be train Engine
{
UIView *EngineView = [[UIView alloc]initWithFrame:frame];
UIImageView *engineImageView = [[UIImageView alloc]initWithFrame:CGRectMake(-112.5, 6, 430, 180)];
UIImage *Image = [UIImage imageNamed:#"backengine.png"];
engineImageView.contentMode = UIViewContentModeScaleAspectFit;
engineImageView.image = Image;
[EngineView addSubview:engineImageView];
[scrollView addSubview:EngineView]; // add 3rd imageview that is Train Engine to end of scrollview
}
else{ // Not equal to 2 than it must be wagon of train.
UIView *wagon = [[UIView alloc]initWithFrame:frame];
UIColor *background = [[UIColor alloc] initWithPatternImage:[UIImage imageNamed:#"wagon.png"]];
wagon.backgroundColor = background;
UIView *videoviewContainer = [[UIView alloc]initWithFrame:CGRectMake(20, 40, 220 , 200)];
UIImageView *videoImageView = [[UIImageView alloc]initWithFrame:CGRectMake(0,0, 220, 100)];
UIImage *bgImage = [UIImage imageNamed:#"video.png"];
videoImageView.image = bgImage;
videoviewContainer.contentMode = UIViewContentModeLeft; // set Video Image view to left hand side of wagon UIView
videoImageView.tag=2;
[videoviewContainer addSubview:videoImageView];
UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(singleTapGestureCaptured:)];
[videoImageView addGestureRecognizer:singleTap];
[videoImageView setMultipleTouchEnabled:YES];
[videoImageView setUserInteractionEnabled:YES];
UIView *textUiView = [[UIView alloc]initWithFrame:CGRectMake(238,28, 150 , 187)];
textUiView.contentMode = UIViewContentModeRight;
UILabel * label = [[UILabel alloc]initWithFrame:CGRectMake(28,10, 150 , 87)];
label.text=#"This is testing text for IOS app to check line are aligned or not and to check video image and nested views for UIviews";
label.backgroundColor = [UIColor clearColor];
label.textColor=[UIColor redColor];
label.numberOfLines = 4;
[textUiView addSubview:label];
[wagon addSubview:textUiView];
[wagon addSubview:videoviewContainer];
[scrollView addSubview:wagon];
[self.contentView addSubview:scrollView];
}
}
}
return self;
}
- (void)singleTapGestureCaptured:(UITapGestureRecognizer *)gesture
{
NSLog(#"Touch event on view %d",gesture.view.tag);
}
And I created Scrollview subclass like this..
#implementation cellScrollViewClass
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
NSLog(#"You are in scrollview");
}
return self;
}
IS this one is right method of Implementation?
Any Help is Appreciated . Thank you in advance.
Use UITapGestureRecognizer. Create and attach a tap gesture recogniser to each image view when you create it. Also set the tag of the image view when you create it so you can identify which one it is on the cell.
When you get the callback from the recogniser you can get the tag by:
recogniser.view.tag
And you can get the table view cell by looping on the superview until you find a table view cell class:
UIView *superView = recogniser.view.superview;
while (superView != nil && ![superView isKindOfClass:[UITableViewCell class]]) {
superView = superView.superview;
}
Then you can get the index path from the table view using
[tableView indexPathForCell:superView];
To get the on click listener for UIImageView have a category of it & use touches begin method and delegate callback to 'tableViewController'.
Use tags to identify which image is clicked & which row.
To avoid overlap, if the cell us already allocated just remove the previous images to do this have a else condition.
you need to implement the UIScrollView delegate callbacks and check what kind of scroll the user is doing (horizontal scroll or vertical scroll) and then changing the tableview or the scrollview offset manually, thats pretty much the only way i see this...
as for your image you need to set a UIButton for everyImage and link them to a selector, then you can use the tag of the button to know what image it was.
you can add a custom button over the imageview with same frame of image view and the you can add the selector on the button.
For handling gestures on UIImageView make sure that the UIImageView that you are using has the userInteractionEnable = YES by default is set to NO

iOS - UIScrollView doesn't update its content and some UIViews disappear after reload

I have UITableViewController with UIScrollView inside UIView as its header.
UIScrollView is used to display slideshow of images:
UITableView
-UITableViewHeaderView
-UIView
-UIScrollView
-UIImageView
...
-UIPageControl
Every image has a subview with its title and description:
UIImageView
-UIView
-UILabel
-UILabel
When I need to update my tableView a delegate method is called which reloads data in a tableView and calls addImages method:
- (void)eventsUpdated
{
if ([self respondsToSelector:#selector(setRefreshControl:)])
[self.refreshControl endRefreshing];
[self.tableView reloadData];
[self addImages];
}
Here is how I add my images:
- (void)addImages
[self.scrollView.subviews makeObjectsPerformSelector:#selector(removeFromSuperview)];
for (int i = 0; i < images.count; i++) {
CGRect frame;
frame.origin.x = self.scrollView.frame.size.width * i;
frame.origin.y = 0;
frame.size = self.scrollView.frame.size;
UIImageView *subview = [self createImageViewForEvent:[images objectAtIndex:i] inFrame:frame];
subview.frame = frame;
[self.scrollView addSubview:subview];
}
self.scrollView.contentSize = CGSizeMake(scrollView.frame.size.width*images.count, scrollView.frame.size.height);
pageControll.currentPage = 0;
pageControll.numberOfPages = images.count;
}
- (UIImageView *)createImageViewForEvent:(Event *)event inFrame:(CGRect)frame
{
UIImage *image;
NSString *imageName = [event.imageName lastPathComponent];
NSString *bundleFilePath = [[NSBundle mainBundle] pathForResource:[imageName stringByDeletingPathExtension] ofType:[imageName pathExtension]];
image = [UIImage imageWithContentsOfFile:bundleFilePath];
UIImageView *output = [[UIImageView alloc] initWithImage:image];
output.frame = frame;
UIView *descriptionView = [[UIView alloc] initWithFrame:CGRectMake(0, frame.size.height*0.7, frame.size.width, frame.size.height*0.3)];
descriptionView.backgroundColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0.5];
descriptionView.alpha = 1.0;
UILabel *header = [[UILabel alloc] initWithFrame:CGRectMake(10, 5, frame.size.width-20, 12)];
header.textColor = [UIColor colorWithRed:210/256.0 green:217/256.0 blue:231/256.0 alpha:1.0];
header.font = [UIFont fontWithName:#"Helvetica" size:17];
header.backgroundColor = [UIColor clearColor];
header.lineBreakMode = UILineBreakModeTailTruncation;
header.numberOfLines = 1;
UILabel *description = [[UILabel alloc] initWithFrame:CGRectMake(10, 22, frame.size.width-20, 28)];
description.textColor = [UIColor colorWithRed:255/256.0 green:255/256.0 blue:255/256.0 alpha:1.0];
description.font = [UIFont fontWithName:#"Helvetica" size:12];
description.backgroundColor = [UIColor clearColor];
description.lineBreakMode = UILineBreakModeTailTruncation;
description.numberOfLines = 2;
header.text = event.title;
[descriptionView addSubview:header];
description.text = event.shortDesription;
[descriptionView addSubview:description];
[output addSubview:descriptionView];
return output;
}
After first launch everything works fine, but if I try to reload my tableView and call addImages again, all UILabels disappear, only UIImageView and UIView are visible.
UPD:
What I have noticed, is that my UILabels appear after some time, approximately after 30 seconds. I have never experienced something similar before
UPD 2:
After I reload my tableView and scrollview, scrollView's content is not updated right away, only after I start scrolling
I reproduced a sample project from the code you posted, and didn't find any refreshing issues on the labels nor on any other component. This leads me to think the problem is not in the code you posted, but rather somewhere else.
Usually when you see such a delay, it is because some code that should not be running in parallel, is running in parallel.
I would suggest you check how and when you perform your call to eventsUpdated. You must make sure that call is performed on the main thread, and only once your array of events has finished updating. To check that, you can add add the following line in eventsUpdated:
NSLog(#"Current: %#, main: %#", [NSThread currentThread], [NSThread mainThread]);
Please post the log! :-)
I would suggest you also add to your question the code from the method that calls eventsUpdated, as it could also be of help.
At first, never do something like
[scrollView.subviews makeObjectsPerformSelector:#selector(removeFromSuperview)];
Scroll view has some internal views (scroll indicators, for example), so you're removing them which may dealloc them and cause strange crashes any time later.
As for your problem - try calling bringSubviewToFront: of your UIImageView to get your labels infront of everything else

Changing bounds of imageView of UITableViewCell

I'm trying to place various size images inside imageView of UITableViewCell. I get the image data asynch'ly, create the image, set the content mode of imageView and finally set bounds of imageView. But the code seems insensitive to any changes I made. I want the images to be centered in a 75x75 area. I wrote the below code for this purpose
UIImage* image = [UIImage imageWithData:data];
[holder.imageView setContentMode:UIViewContentModeCenter || UIViewContentModeRedraw];
[holder.imageView setImage:image];
[holder.imageView setBounds:CGRectMake(0,0,75,75)];
[holder.imageView setFrame:CGRectMake(0,0,75,75)];
[holder setNeedsLayout];
Where holder is the UITableViewCell. The result I get is always the same. All images have 75px height and different widths. Can someone help me solve this problem?
I have realized that setting contentMode and bounds properties does not have any effect in that code. I have added an NSLog after the last line and got the results as below:
NSLog(#"imageview:%# bounds and contentMode:%# %#",[holder imageView],[holder.imageView bounds],[holder.imageView contentMode]);
imageview:<UIImageView: 0x39ab8a0;
frame = (0 0; 75 75); opaque = NO;
userInteractionEnabled = NO; layer =
<CALayer: 0x39a92b0>> bounds and
contentMode:(null) (null)
Still no solution
Done, I finally found the solution, it cost me 3 hours though =)
The solution is to change properties like bound,frame,contentMode in -(void)layoutSubviews method of the custom UITableViewCell class. The "trick" is to write layout code in this method, otherwise the code does not have any effect.
Below code did the work for me. It makes rows of the table vertically aligned.
- (void)layoutSubviews {
[super layoutSubviews];
self.imageView.bounds = CGRectMake(0,0,75,75);
self.imageView.frame = CGRectMake(0,0,75,75);
self.imageView.contentMode = UIViewContentModeScaleAspectFit;
CGRect tmpFrame = self.textLabel.frame;
tmpFrame.origin.x = 77;
self.textLabel.frame = tmpFrame;
tmpFrame = self.detailTextLabel.frame;
tmpFrame.origin.x = 77;
self.detailTextLabel.frame = tmpFrame;
}
So the problem with UITableViewCell's is that you have no control over the size of the built-in objects (namely imageView, contentView, accessoryView, backgroundView). When the table changes, your customizations get trampled over.
You can, as Behlul pointed out, force the sizes to be correct by using layoutSubviews, but the problem with that is that layoutSubviews is called every time the table scrolls. That is a lot of unnecessary re-layout calls.
An alternate, method is to add all of your content to the contentView. Similarly if you are customizing the background, you can create a transparent backgroundView and add your custom background view (eg myBackgroundView) as a subview of backgroundView.
This way you can place and size your items how you want them.
The down side is the stock messages are no longer received from the accessory or image views. You just have to create you own.
Hope that helps!
// This code is not tested
// MyCustomTableViewCell
- (id) init{
self = [super initWithStyle: UITableViewCellStyleDefault reuseIdentifier:#"MyReuseIdentifier"];
if(self){
//image view
my_image_view = [[[UIImageView alloc] initWithImage:[UIImage imageNamed:#"default_image.png"]] retain];
[my_image_view setFrame:CGRectMake(10,10,30,30)];
[self.contentView addSubview:my_image_view];
//labels
my_text_label = [[[UILabel alloc] initWithFrame:CGRectMake(50,10,100,15)] retain];
[self.contentView addSubview:my_text_label];
//set font, etc
//detail label
my_detail_label = [[[UILabel alloc] initWithFrame:CGRectMake(50,25,100,15)] retain];
[self.contentView addSubview:my_detail_label];
//set font, etc
//accessory view
//Whatever you want to do here
//attach "accessoryButtonTapped" selector to button action
//background view
UIView* background_view = [[[UIView alloc] initWithFrame:CGRectMake(0, 0, 200, 50)] autorelease];
[background_view setBackgroundColor:[UIColor greenColor]];
background_view.layer.cornerRadius = 17;
background_view.layer.borderWidth = 3;
background_view.layer.borderColor = [UIColor whiteColor].CGColor;
[self setBackgroundView:[[[UIView alloc] init] autorelease]];
[self.backgroundView addSubview:background_view];
}
return self;
}
- (void) setLabelText: (NSString*) label_text{
[my_text_label setText:label_text];
}
- (void) setDetailText: (NSString*) detail_text{
[my_detail_label setText: detail_text];
}
- (void) accessoryButtonTapped{
//call table view delegate's accessoryButtonTappedForRowWithIndexPath method
}
"UIViewContentModeCenter || UIViewContentModeRedraw" is equivalent to 1. It's also not a bitfield. You want UIViewContentModeCenter.
UITableViewCell.imageView is managed by the cell. If you want custom layout, try adding a view to contentView (I'm guessing what you mean by "centered in a 75x75 area"):
UIImageView * iv = [[[UIImageView alloc] initWithImage:image] autorelease];
iv.frame = (CGRect){{0,0},{75,75}};
iv.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin| UIViewAutoresizingFlexibleRightMargin;
iv.contentMode = UIViewContentModeScaleAspectFit;
[holder.contentView addSubview:iv];
try changing the "contentMode" property of imageView to 'UIViewContentModeScaleAspectFit' or 'UIViewContentModeScaleAspectFill'
Create subclass of UITableViewCell:
#interface UITableViewCellSubClass : UITableViewCell
#end
#implementation UITableViewCellSubClass
- (void)layoutSubviews {
[super layoutSubviews];
self.imageView.frame = CGRectMake(0,4,32,32);
self.textLabel.frame = CGRectMake(42,4,300,32);
}
#end

Why are tableview cells hardly visible when setting tableview background image?

I can't get the searchResultsTableView cells to be fully visible when loading with a background image. The cells look quite weak and don't stand out from the background imageview, even when selected. Any suggestions?
- (void)searchDisplayController:(UISearchDisplayController *)controller willShowSearchResultsTableView:(UITableView *)tableView {
for (UIView *view in controller.searchResultsTableView.subviews) {
//if ([view isKindOfClass:[UIImageView class]]) {
[view removeFromSuperview];
//}
}
UIImage *patternImage = [UIImage imageNamed:#"background_newer.png"];
UIImageView * backgroundImageView = [[UIImageView alloc] initWithImage:patternImage];
//backgroundImageView.opaque = NO;
backgroundImageView.alpha = 0.9;
controller.searchResultsTableView.separatorStyle = UITableViewCellSeparatorStyleNone;
controller.searchResultsTableView.backgroundColor = [UIColor clearColor];
[controller.searchResultsTableView addSubview:backgroundImageView];
[controller.searchResultsTableView sendSubviewToBack:backgroundImageView];
controller.searchResultsTableView.rowHeight = 25;
[patternImage release];
[backgroundImageView release];
}
I am not doing anything else than allocating a new UITableViewCell for use (in searchResultsTableView) inside this delegate method:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { .... }
Thanks for any corrections!
(I am on iPhone simulator 3.1.2)
#Jessedc
Thanks for your advice. Haven't tried out your code yet.
I solved this headache (before I saw your reply) by setting hidden=YES on the main tableview behind the searchResultsTableView whenever the searchResultsTableView loads, and hidden=NO when the search is over. The main tableview is set with backgroundColor=[UIColor clearColor] on top of an imageview (loaded with the same image as in my original searchResultsTableView code up there).
This new layout is as previously desired :-).
Are trying to display a static background image with the table text scrolling over the top?
I think you may have your code a little mixed up (possibly).
Customising a table is usually done in a viewWillAppear method. so this code should go there:
UIImage *patternImage = [UIImage imageNamed:#"background_newer.png"];
UIImageView * backgroundImageView = [[UIImageView alloc] initWithImage:patternImage];
//backgroundImageView.opaque = NO;
backgroundImageView.alpha = 0.9;
controller.searchResultsTableView.separatorStyle = UITableViewCellSeparatorStyleNone;
controller.searchResultsTableView.backgroundColor = [UIColor clearColor];
[controller.searchResultsTableView addSubview:backgroundImageView];
[controller.searchResultsTableView sendSubviewToBack:backgroundImageView];
controller.searchResultsTableView.rowHeight = 25;
[patternImage release];
[backgroundImageView release];
Next, in your delegate method searchDisplayController:willShowSearchResultsTableView you are not referring to the tableview object passed in, but you are calling it through the top level (unnecessary perhaps?)
Have a go at moving your table setup code into the viewWillAppear.
Let me know if this helps, or if you can provide more information/code.
It seems to me like you're setting the background of your tableview the hard way. I don't know if it's 100% the problem, but you should set your UITableView background like this:
controller.searchResultsTableView.backgroundColor = [[[UIColor alloc] initWithPatternImage:[UIImage imageNamed:#"background_newer.png"]]autorelease];

Does adding many many subviews to a viewcontrollers default view make my app slow?

I have an app where I create many uiviews and add them to the self.view of the UIViewController. My app is running really slowly. I am releasing all of my objects and have no memory leaks (I ran the performance tool). Can anyone tell me what could be making my app so slow? (code is below)
[EDIT] The array has around 30 items. [/EndEdit]
Thanks so much!
Here is the code for the loadView method of my UIViewController:
- (void)loadView {
UIView *contentView = [[UIView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]];
contentView.backgroundColor = [UIColor whiteColor];
self.view = contentView;
[contentView release];
int length = 0;
for(NSString *item in arrayTips)
{
length++;
[item release];
}
int index = 0;
for(NSString *item in arrayTitles)
{
SingleFlipView *backView = [[SingleFlipView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]];
backView.userInteractionEnabled = YES;
backView.backgroundColor = [UIColor whiteColor];
[backView setViewIndex:index];
[backView setLastViewIndex:length];
CGRect labelFrame = CGRectMake(10.0f, 0.0f, 300.0f, 30.0f);
UILabel *backLabel = [[UILabel alloc] initWithFrame:labelFrame];
backLabel.textAlignment = UITextAlignmentCenter;
backLabel.userInteractionEnabled = YES;
backLabel.text = item;
backLabel.font = [UIFont fontWithName:#"Georgia" size:24.0f];
backLabel.textColor = [UIColor blackColor];
backLabel.backgroundColor = [UIColor whiteColor];
CGRect textFrame = CGRectMake(10.0f, 30.0f, 300.0f, 110.0f);
UITextView *tbxView = [[UITextView alloc] initWithFrame:textFrame];
tbxView.textAlignment = UITextAlignmentCenter;
tbxView.userInteractionEnabled = YES;
tbxView.editable = FALSE;
tbxView.text = [arrayTips objectAtIndex:index];
tbxView.font = [UIFont fontWithName:#"Arial" size:14.0f];
tbxView.textColor = [UIColor blackColor];
tbxView.backgroundColor = [UIColor whiteColor];
//CGRect labelFrame = CGRectMake(10.0f, 0.0f, 84.0f, 30.0f);
UIImage *nextTip = [[UIImage imageNamed:#"NextTip.png"] retain];
UIImageView *nextTipView = [ [ UIImageView alloc ] initWithImage:nextTip];
nextTipView.frame = CGRectMake(230.0f, -10.0f, 84.0f, 30.0f);
nextTipView.userInteractionEnabled = YES;
UIImageView *view = [[ UIImageView alloc ] init];
view.userInteractionEnabled = YES;
if(self.sexString == #"Men")
{
UIImage *imgTip = [[UIImage imageNamed:#"feet_small.jpg"] retain];
view.image = imgTip;
view.frame = CGRectMake(0.0f, 110.0f, 416.0f, 228.0f); //59*161
[imgTip release];
}
[backView addSubview:view];
[backView addSubview:tbxView];
[backView addSubview:backLabel];
//[backView addSubview:nextTipView];
[self.view addSubview:backView];
[backView release];
[backLabel release];
[nextTip release];
[nextTipView release];
[tbxView release];
[view release];
index++;
[item release];
}
}
It's going to depend upon how many items are in arrayTitles. If you're just adding one or two of these, you shouldn't see a HUGE slowdown; more, and you will. You should probably take a look at the way UITableView handles its cells; only create these as they're actually needed/used, or, better yet, only create one of these, and set its contents on-the-fly.
A few other notes:
== is not a valid string comparison operator in Objective-C; use [string1 isEqualTo: string2]
It appears you're trying to place a lot of these on screen at the same time, which doesn't seem like it would make a lot of sense.
it looks like you've got a spurious [item release] at the end there (you're never retaining item, so there's no need to release it.
the whole first loop ( for(NSString *item in arrayTips)... frightens and confuses me; items in NSArrays are already retained by the array. You shouldn't have to explicitly retain/release them in this way.
Having deep view hierarchies can lead to slow downs that you can often fix through flattening them some with custom views, but if you are using simple views you can have dozens on the screen with no perceptible performance impact, so in general I recommend ignoring how many views you have when you are developing, and then reducing the view count if it proves to be a performance problem.
Having said that, you appear to be setting up something with an unboundedily large number of views which is not good. Without knowing how many entries there are in array titles I can't tell you what is going on exactly, but I suspect that while the actual visual heiarchy with each backView you are creating is fine, making a backView for each item in the array and using indices to have the front most one hide all the other ones behind it is causing you to have way too many views.
So, how to test it:
Add a break to the bottom of your for loop. Make the loop drop out after a single iteration and see if performance improves. If it does, then the huge view hierarchies are your issue. YOu may have to hack up the routine that changes the indexes to make sure it never swaps to an invalid index to test.
If that is the case you have a few options. You could implement a custom view and flatten every backview into a single view, but depending on how many you have that mat not be sufficient, and it is more work than simply building the back views the way you currently are, but on demand instead of at load time:
1) Refactor the code in your for loop into a separate method that makes a backView for a specific title and attaches it to the view at that time.
2) Where ever you are currently altering the indexes to make the backview visible, instead call the new method to actually build and attach the backview at that time
Don't forget to make as many of your views opaque as you can. Transparent views are a major source of performance issues.