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;
}
Related
I have two UICollectionViewLayout, one for showing stacked cells and another one to show individual cells on full screen whe they're selected.
I need to animate the cell's subviews to certain positions each time the cell is selected, and then move again those subviews when the cell is deselected.
STEP 1: Cell is selected. Subview is animated from its original position to a new one. No problem here.
STEP 1: Cell is deselected. Subview is animated to another position, BUT the animation starts from the original position, not the new position I indicated in the previous step.
Is there any way to save the modified status, and start the animations from there?
UPDATE
Here's how I change layouts on cell selection (ViewController.m):
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath{
__weak CustomCellCollectionViewCell *cell = (CustomCellCollectionViewCell *)[self.collectionView cellForItemAtIndexPath:indexPath];
// Hide single view
if ([indexPath isEqual:self.exposedItemIndexPath]) {
self.exposedItemIndexPath = nil;
[cell deselectedAnimations];
[self animateCollectionView:NO];
}
// Select single view
else if (self.exposedItemIndexPath == nil) {
self.exposedItemIndexPath = indexPath;
[cell selectedAnimations];
[self animateCollectionView:YES];
}
}
- (void)setExposedItemIndexPath:(NSIndexPath *)exposedItemIndexPath
{
if (![exposedItemIndexPath isEqual:_exposedItemIndexPath]) {
if (exposedItemIndexPath) {
self.audiosContentOffset = self.collectionView.contentOffset;
SelectedLayout *singleLayout = [[SelectedLayout alloc] initWithExposedItemIndex:exposedItemIndexPath.item];
[self.collectionView setCollectionViewLayout:singleLayout animated:YES];
}else{
self.stackedLayout.overwriteContentOffset = YES;
self.stackedLayout.contentOffset = self.audiosContentOffset;
[self.collectionView setCollectionViewLayout:self.stackedLayout animated:YES];
[self.collectionView setContentOffset:self.audiosContentOffset animated:YES];
}
_exposedItemIndexPath = exposedItemIndexPath;
}
}
selectedAnimations moves a UIView inside the cell, and deselectedAnimations moves it somewhere else when the cell is deselected. BUT, deselectedAnimations starts from the UIView's original position, not from where it was left at the end of selectedAnimations.
The cells and its subviews are based on a nib file. It seems to me that, on layout change, the cell is being rendered again following the "instructions" on the nib file, and that's why the subview is animated starting from its original position instead of where i left it on selectedAnimations.
My question is: is there any way to "save" the cell's subviews positions and start deselectedAnimations from there?
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).
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 need to present my table view with a two column list. I read some related posts about making some grid by overriding drawRect (here). However, I'm seeking a simple way to design my cell with IB in nib, then load it and push two cells on each row. The example with drawRect is not appropriate because it involves manual setting of positions. I just need to push those two cells with some autosizing, that's it. Is it possible?
I'm looking for something like (in cellForRowAtIndexPath):
cell.contentView = emptyUIViewContainer;
[cell.contentView addSubview:FirstColumnUIView];
[cell.contentView addSubview:SecondColumnUIView];
I don't need to have two separate nibs for those two columns because every column is of the same format just with some other data. Any ideas?
UPDATE: Intuitively, I'm trying to do something like this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell1 = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell1 == nil) {
cell1 = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
// Configure the first cell.
cell1.textLabel.text = ...some text
// Configure the second cell
UITableViewCell *cell2 = [[UITableViewCell alloc] init];
cell2.textLabel.text = ...some text
//set row as container for two cells
UITableViewCell *twoColumnRowView = [[UIView alloc] init]; //initWithFrame:CGRectMake(0, 0, 200, 20)];
cell1.contentView.frame = CGRectMake(0, 0, 100, 20);
[twoColumnRowView addSubview:cell1];
cell2.contentView.frame = CGRectMake(100, 0, 100, 20);
[twoColumnRowView addSubview:cell2];
return twoColumnRowView; // cell;
}
It's just a raw prototype I'm playing with now. But the code crashes at runtime with "Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[UIView setTableViewStyle:]: unrecognized selector sent to instance"
UPDATE 2. I have changed a code to look more practical. Strange but after several tries I got the app working without crash but with weird black background in all cells. Here is the code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *FirstCellIdentifier = #"FirstCellIdentifier";
static NSString *SecondCellIdentifier = #"SecondCellIdentifier";
// initialize first cell
UITableViewCell *cell1 = [tableView dequeueReusableCellWithIdentifier:FirstCellIdentifier];
if (cell1 == nil) {
cell1 = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:FirstCellIdentifier] autorelease];
}
//initialize second cell
UITableViewCell *cell2 = [tableView dequeueReusableCellWithIdentifier:SecondCellIdentifier];
if (cell2 == nil) {
cell2 = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:SecondCellIdentifier] autorelease];
}
cell1.textLabel.text = ...data
cell2.textLabel.text = ...data
UITableViewCell *twoColumnRowView = [[UITableViewCell alloc] init];
[twoColumnRowView addSubview:cell1];
//cell2.contentView.frame = CGRectMake(100, 0, 100, 20);
[twoColumnRowView addSubview:cell2];
return twoColumnRowView; // cell;
}
I'm not reusing twoColumnRowView but both other does do.
You can't (or at least, shouldn't) put two table views next to each other. And, as you already know, a table view only ever has a single column.
However, you can put as much data as you want in each cell.
You can do most of this in Interface Builder. Create a XIB with a UITableViewCell. Drag and drop a couple of UILabels into the right place. You could update the labels by finding them with the viewWithTag: method, but it would be better to create a custom UITableViewCell class with properties pointing to each label.
Since tables are so central to UIKit, there are a lot of samples in Apple's documentation set and some good WWDC talks. You can download the videos from iTunes.
Hope this will help you...
http://www.iphonedevx.com/?p=153
else the best way is to put two table views side by side, if they need to scroll independently.
Hope this will help you...
In my app I change the value of a text label from an initial value of 0 and increment it on a touch event. Sometimes, but not all the time, the new value will be overlayed over the 0, which is not cool..
This is the relevant code:
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
UILabel *label = (UILabel *)[cell viewWithTag:1000];
label.text = qtyString;
I've tried removing the label from the view, then adding another with the new value, but it didn't affect the problem at all. When I scroll the cell (the labels are part of a table cell) out of the screen and back in, the labels display correctly. Oh, and I've also tried doing
[tableView reloadData];
And it works better, but if I select a cell and then scroll while it is higlighted it poops out on that cell.
Please help :(
check the "clears graphics context" works for me, i carelessly deselected it at first~~
Under your UILabel options UNCHECK 'opaque', that will fix it.
I had the same problem with UILabel's text getting all jumbled, I just read the other solutions, went to IB, then checked "Clear context before drawing", and that solved it!
Changing opaque doesnt change it altho it would have been logic. What you can do if giving your label an exact height .. This height will match your well height. If you are using
(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return 80; //returns floating point which will be used for a cell row height at specified row index
}
Like i did then make the height of the label 80 as well that will stop it ..
imho, the best way to go around this is to add an else to:
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:CellIdentifier] autorelease];
}else {
NSArray *cellSubs = cell.contentView.subviews;
for (int i = 0 ; i < [cellSubs count] ; i++) {
[[cellSubs objectAtIndex:i] removeFromSuperview];
}
}
Hope this helps.
In my case, changing Opaque to NO solve the problem of updating the UILabel.text
I create the uilabel in the awakeFromNib.