I'm adding UIView in some Cells of my UITableView. I use dequeueReusableCellWithIdentifier and it seems that when I scroll up and down and up and down, the views are never removed from memory and it keeps adding new views in front of the already loaded views. It increases the memory forever and that's not the way it should be.
How to remove these UIViews's from memory when not displayed ?
else if (indexPath.row == 0 && !isPremium) {
// Google AdMob
adCell = [tableView dequeueReusableCellWithIdentifier:adCellId];
if (adCell == nil) {
adCell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:adCellId];
}
AdMobView = [[GADBannerView alloc]initWithAdSize:kGADAdSizeSmartBannerPortrait];
AdMobView.adUnitID = #"ca-app-pub-XXXXXXXXXXXXXXX";
AdMobView.rootViewController = self;
AdMobView.autoresizingMask = UIViewAutoresizingFlexibleWidth;
// load the ads
GADRequest *request = [GADRequest request];
request.testDevices = [NSArray arrayWithObjects:GAD_SIMULATOR_ID, #"XXXXXXXXX", nil]; // COMMENT THIS LINE WHEN IN PROD
// add gps
if ([latitude length] > 2 && [longitude length] > 2) {
[request setLocationWithLatitude:[latitude floatValue] longitude:[longitude floatValue] accuracy:50]; // accuracy [m]
}
adCell.backgroundColor = [UIColor colorWithRed:247.0/255.0 green:149.0/255.0 blue: 24.0/255.0 alpha:1];
if (!AdMobView.superview) {
[AdMobView loadRequest:request];
}
AdMobView.tag = 3000;
[adCell.contentView addSubview:AdMobView];
[adCell setSelectionStyle:UITableViewCellSelectionStyleNone];
return adCell;
}
If the cells are being reused but the views are still piling up, that would be because in your cellForRowAtIndexPath: you are adding the views even to recycled cells that already have that view. Thus the views keep increasing in number, piling up on top of each other in each cell, as you scroll up and down.
Thus (now that you've added some code) you are saying:
[adCell.contentView addSubview:AdMobView];
...for every cell, unconditionally. But if a cell is reused, it already has an AdMobView! You need to check for this so you add this subview only conditionally (and so too for any other subviews you add).
Related
Ok so i have a UITableView that fetches some data from a web server and displays the data.
Depending on a text of a label the label will need to move a bit to the left if the if statement is true.
if ([gameInfoObject.GameTime isEqual: #"FT"] | ([gameInfoObject.GameTime rangeOfString:#":"].location != NSNotFound)) { // CHeck to see if its FT or string contains ":" then hide liveB
cell.liveButton.hidden = YES; //Hide the image cause gameTimeLable will take its position
CGRect frame = cell.gameTimeLabel.frame;
frame.origin.x= 27; // move the label to the left since no image will be present
cell.gameTimeLabel.frame= frame;
}
I have a timer that runs:
[self.tableview reloadData];
The problems is when the application starts the labels are not correctly positioned.
But after 5 seconds when the timer activate reloadData it changes to its accurate position.
How can i make it so the labels will change the first time?
Thnx..
----------------------------------------------------------------------------Edit 1
- (void) retrieveData{
GetData *getDataObject = [[GetData alloc] init]; // fixa detta sen
[getDataObject getAllGamesWithCompletionHandler:^(id responseObject, NSError *error) {
if (!responseObject) {
NSLog(#"failed: %#", error);
return;
}
gamesInfoArray = (NSMutableArray *)responseObject;
[self.tableView reloadData]; //first time it gets called here
}];
}
I'm seeing a very odd behavior with UICollectionViews.
Here is the scenario.
I have a UIViewController that has been pushed on to a UINavigationController stack.
The UIViewController view has nav bar and UICollectionView in grid layout. 3 cells wide by unlimited tall.
Just below extent of screen, I also have a UIToolbar hidden. The UIToolbar is on top of UICollectionView in layer hierarchy.
I then allow the user to put view in to "edit mode" and I animate UIToolbar on to the screen and covers bottom portion of UICollectionView. If user leaves "edit mode" I move UIToolbar back off screen.
While in "edit mode" I allow the user to multi select cells with check boxes that appear and uitoolbar has delete button.
Delete does the following:
- (void)deletePhotos:(id)sender {
if ([[self.selectedCells allKeys] count] > 0) {
[[DataManager instance] deletePhotosAtIndexes:[self.selectedCells allKeys]];
[self.selectedCells removeAllObjects];
[self.collectionview reloadData];
[self.collectionview performBatchUpdates:nil completion:nil];
}
}
// Data Manager method in singleton class:
- (void)deletePhotosAtIndexes:(NSArray *)indexes {
NSMutableIndexSet *indexesToDelete = [NSMutableIndexSet indexSet];
for (int i = 0; i < [indexes count]; i++) {
[indexesToDelete addIndex:[[indexes objectAtIndex:i] integerValue]];
NSString *filePath = [self.photosPath stringByAppendingPathComponent:[self.currentPhotos objectAtIndex:[[indexes objectAtIndex:i] integerValue]]];
NSString *thumbnailPath = [self.thumbPath stringByAppendingPathComponent:[self.currentPhotos objectAtIndex:[[indexes objectAtIndex:i] integerValue]]];
if ([[NSFileManager defaultManager] fileExistsAtPath: filePath]) {
[[NSFileManager defaultManager] removeItemAtPath: filePath error:NULL];
[[NSFileManager defaultManager] removeItemAtPath: thumbnailPath error:NULL];
}
}
[self.currentPhotos removeObjectsAtIndexes:indexesToDelete];
}
The data manager contains photo objects and are used in cell creation like so.
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
ImageCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"imageCell" forIndexPath:indexPath];
NSString *pngfile = [[[DataManager instance] thumbPath] stringByAppendingPathComponent:[[[DataManager instance] currentPhotos] objectAtIndex:indexPath.row]];
if ([[NSFileManager defaultManager] fileExistsAtPath:pngfile]) {
NSData *imageData = [NSData dataWithContentsOfFile:pngfile];
UIImage *img = [UIImage imageWithData:imageData];
[cell.imageView setImage:img];
}
if ([self.selectedCells objectForKey:[NSString stringWithFormat:#"%d", indexPath.row]] != nil) {
cell.checkbox.hidden = NO;
} else {
cell.checkbox.hidden = YES;
}
return cell;
}
So here is where I'm finding issues:
When deleting enough cells so that number of visible rows changes, UIToolbar is disappearing. In case of a full row of 3, if I only delete 1 or 2 items, the UIToolbar doesn't disappear. I am not doing any animation on the UIToolbar in delete method and only when hitting a Done button that ends edit mode. I've confirmed that this method isn't being called.
I've also confirmed that the UIToolbar isn't actually moving. If I add "self.collectionview removeFromSuperView" on hitting delete in cases where UIToolbar would normally disappear, the UIToolbar is exactly where expected on the screen. This gives me the impression the UICollectionView is somehow changing layer hierarchy in draw of parent view.
I've attempted trying to bringSubviewToFront for UIToolbar and sendSubviewToBack for collectionview and has no affect.
Re-iniating open toolbar causes uitoolbar to animate back in. Oddly, however, it seems to animate from below screen! This makes no sense unless the UICollectionView is somehow pushing the UIToolbar off the screen due after the point where I would be calling the removeFromSuperview call so I can't re-create.
One "solution" I have is to force the UIToolbar to come back in to position but without animation after a 0.01 second delay
[self performSelector:#selector(showToolbarNoAnimation) withObject:nil afterDelay:0.01];
This works.
Here is the question:
Any idea why UICollectionView causing this behavior to push UIToolbar offscreen after a full row is deleted? The hack works but doesn't explain the issue.
Thanks,
James
When you use auto layout, and your views are loaded from a storyboard (or xib), you can't set the frames of your views. Doing so may seem to work initially, but at some point auto layout will reset the view's frame based on the constraints, and you won't understand what happened, and then you'll post a question to stack overflow.
If you need to change the layout of a view, you need to update the view's constraints instead.
There is a constraint specifying the distance between the bottom edge of your toolbar and the bottom edge of its superview. Presumably that distance is -44 (where 44 is the height of the toolbar).
You need to connect that constraint to an outlet in your view controller. The outlet will have type NSLayoutConstraint *. Call it toolbarBottomEdgeConstraint.
When you want to animate the toolbar onto the screen, set constraint's constant to zero and call layoutIfNeeded in an animation block:
- (void)showToolbarAnimated {
[UIView animateWithDuration:0.25 animations:^{
self.toolbarBottomEdgeConstraint.constant = 0;
[self.toolbar layoutIfNeeded];
}];
}
To hide the toolbar, set the constraint's constant back to its original value:
- (void)hideToolbarAnimated {
[UIView animateWithDuration:0.25 animations:^{
self.toolbarBottomEdgeConstraint.constant = -toolbar.bounds.size.height;
[self.toolbar layoutIfNeeded];
}];
}
My problem is that not all cell are correct initialized with the correct UIView as an rendered image.( see code below) On a iphone 4 it always the same cel on a iphone with retina display is also an other cell. In this set-up setNeedsDisplay won't function.
If i use the same structure in an IBoutlet it's working.
!
I need to use in some cells the image-files .png and at some other cells the defined drawing method, which uses the drawrect or better i should use the setNeedsDisplay method.....
What is going wrong!!!
my code
tableview... cell
static NSString *CellIdentifier = #"TypeCell";
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
// Configure the cell...
CellTypes *cellType = [self.fetchedResultsController objectAtIndexPath:indexPath];
cell.textLabel.text = cellType.title;
cell.detailTextLabel.text = cellType.detail;
if ( [cellType.type rangeOfString:#"TL"].location != NSNotFound) {
cell.imageView.image = [UIImage imageNamed:[cellType.type stringByAppendingString:#"Thumb"]];
cell.indentationWidth = 10;
}
else {
static CGFloat scale = 0.0; // old API , screen values
UIScreen *screen = [UIScreen mainScreen];
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 4.0) {
scale = [screen scale];
}
if (scale>0.0) {
UIGraphicsBeginImageContextWithOptions(cell.imageView.bounds.size, NO, scale);
}
else {
UIGraphicsBeginImageContext(cell.imageView.bounds.size);
}
CGRect imageRect = CGRectMake(0, 0, 84, 84 ); // cell.imageView.bounds;
FittingImageView *fittingImage = [[FittingImageView alloc] initWithFrame:imageRect];
fittingImage.thumb = YES;
fittingImage.title = offSetType.title;
[fittingImage drawRect:imageRect]; // this works but skips one imageRect
// [fittingImage setNeedsDisplay]; //this won't work.
[cell.layer renderInContext:UIGraphicsGetCurrentContext()];
cell.imageView.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
return cell;
You can never call drawRect: yourself. It is called by Cocoa, and only Cocoa can setup the context. It's unclear what you're trying to do here. I don't know what a FittingImageView is. You're creating it and then throwing it away in any case.
You then try to render the cell itself into an image, and the put that image into the cell's imageview. That doesn't make sense.
You may misunderstand how tableview cells are created, reused and drawn. You should re-read "Creating and Configuring a Table View."
The primary things to remember:
In this routine, your job is to either create a new cell, or reconfigure a reusable cell (if dequeueReusableCellWithIdentifier: returns you something).
This routine is not for drawing a cell. That will happen much later, and it is the cell's job to draw itself. You're just configuring the cell. Putting everything in it that it needs to draw later.
You typically do not want to compute expensive things in this routine. Compute those elsewhere and cache them. This routine should configure a cell, slap data into it, and return. If you try to create new images in this routine, your table view is going to stutter.
You shouldn't mess with the cell's layer in the middle of this routine. If you need something that complex, you should be creating a custom cell with its own drawRect:.
I'm having a very common problem that I don't know how to fix for myself, even after reading a bunch of similar posts, since making my cells non-reusable is not an option (the app slows down immensely).
The Problem
When I have 6 or more cells (maximum of 16), the contents of the first 5 cells begin appearing in all of the other cells when I scroll to them.
Though I am using reusable cells, why are the contents of my cells not being refreshed with new data that's stored in my NSMutableArrays?
The Code
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
WorldClockTableViewCell *worldClockCell = (WorldClockTableViewCell*)[tableView dequeueReusableCellWithIdentifier:kCellID];
if(worldClockCell == nil) {
NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:#"WorldClockTableViewCell" owner:self options:nil];
worldClockCell = [topLevelObjects objectAtIndex:0];
/*
Set Scrolling to OFF by default.
User can scroll when table is in edit mode.
*/
[worldClockCell.scrollView setScrollEnabled:NO];
// Set Digital Clock in timeLabel
[self setDigitalClockInLabel:worldClockCell inRow:indexPath.row];
// Set Analog Clock Carousel in ScrollView in every cell.
[self setAnalogClocksInScrollView:worldClockCell inRow:indexPath.row];
// Scroll to User's Preferred Clock (if this is the first launch of WorldClockTableViewController)
if([[shouldAutoScrollArray objectAtIndex:indexPath.row] boolValue] == YES) {
[self scrollToUsersPreferredClock:worldClockCell atRow:indexPath.row];
[shouldAutoScrollArray replaceObjectAtIndex:indexPath.row withObject:[NSNumber numberWithBool:NO]];
}
// Add City Name to cityLabel in every cell
[worldClockCell buildFormattedCityName:[model.timeZoneCityNames objectAtIndex:indexPath.row]];
// Make sure new clocks created while in 'edit' mode don't scroll out of the cell
worldClockCell.scrollView.frame = worldClockCell.scrollViewFrame;
}
return worldClockCell;
}
You're doing all your cell configuration only if it created a new cell, not if it dequeued one. Pull everything out of
if(worldClockCell == nil)
except
NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:#"WorldClockTableViewCell" owner:self options:nil];
worldClockCell = [topLevelObjects objectAtIndex:0];
unless you really only need to do it once, when the cell is created (and not every time it's reconfigured to display new data).
You should move all the digital and analog clocks out of the scope of check for dequeued reusable cell and all will be good. Your content is being repeated because the cell once scrolled out of view is kept for reuse.
In a UITableView I add a UIView as subview but ONLY for section 1. Section 1's content is loaded from a plist and the plist contains mutable content. If there are enough rows to allow scrolling, then the following happens: I scroll to the bottom, and back up, and the UITextField appears randomly on some of section 0's cells. I have no clue why this is happening! So what i do is this (in ´cellForRowAtIndexPath´):
if (indexPath.section == 0) {
//do stuff
}
else if (indexPath.section == 1) {
d = [UIView alloc] init];
[cell.contentView addSubview:d];
}
and this gets totally messed up when I scroll. The subviews appear in section 0 where they shoudnt, and on didSelectRowAtIdexPath I reload for section 1 and then subviews even appear twice (over each other)... Its a complete MESS! Please, Please help.......
Without seeing any code this seems to be an issue pertaining to reusable cells. What happens is that the cells that have scrolled off the screen are reused for the new content that is to be shown. So i reckon you need to make a distinction in cellForRowAtIndexPath for section 0 and 1 and basically use different set of cells for them.
EDIT: Ok ima give a shot to your problem here
UITableViewCell *cell;
if (indexPath.section == 0) {
cell = [tableView dequeueReusableCellWithIdentifier:#"CellWithoutSubview"];
if (cell ==nil ) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewStylePlain reuseIdentifier:#"CellWithoutSubview"] autorelease];
}
//do stuff with cell like set text or whatever
}
else if (indexPath.section == 1) {
cell = [tableView dequeueReusableCellWithIdentifier:#"CellWithSubview"];
if (cell ==nil ) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewStylePlain reuseIdentifier:#"CellWithSubview"] autorelease];
d = [[UIView alloc] init];
[cell.contentView addSubview:d];
[d release];
}
}
return cell;
So now you'll have two types of cells for the tableview that'll be reused one without the subview and one with the subview.
You must be using dequeueReusableCellWithIdentifier. The purpose of dequeueReusableCellWithIdentifier is to use less memory. If the screen can fit 4 or 5 table cells, then with reuse you only need to have 4 or 5 table cells allocated in memory even if the table has 1000 entries.
So the subviews in UITableViewCell are also cached. So when the cell is reused, you need to clean out the old view & then put in the new content.
UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier: #"your-id"];
if (cell)
{
//reusing old cell. reset all subviews before putting new content.
}
else
{
//Create a fresh new cell
}
You should use switch instead:
switch ( indexPath.section )
{
case 0:
{
/* do soemthing */
}
break;
case 1:
{
d = [UIView alloc] init];
[cell.contentView addSubview:d];
}
break;
}