UITableView not scrolling smoothly...(iPhone SDK) ..!!
I have implemented UITableView DataSource and Delegate methods in an individual separate classes.(one for delegate and one for datasource) in main program i write only:
//assume that all objects are allocated
ObjTableView.dataSource=ObjDataSource;
ObjTableView.delegate = ObjDelegate;
[self.view addSubView: ObjTableView];
when i run this code , UITable view appears but when i try to scroll it, it doesn't scroll smoothly.
I have also checked that UITableViewCell doesn't redraw once the cell is initialized.
can any one tell me why this happens ? How can i solve this problem ??
From comments:
ListDataSource *ObjListDataSource = [[ListDataSource alloc]initWithArray:[[sender object] valueForKey:#"List"]];
ListDelegate *ObjListDelegate = [[ListDelegate alloc]initWithArray:[[sender object] valueForKey:#"List"]];
tblList = [[UITableView alloc]initWithFrame:CGRectMake(0, 0, 320, 460)];
tblList.dataSource = ObjListDataSource; tblList.delegate = ObjListDelegate;
[self.view addSubview:tblList]; [tblShopList release];
More from comments:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *CellIdentifier = [NSString stringWithFormat:#"%i",indexPath.row];
UITableViewCell *cell = (UITableViewCell*)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectMake(0,0,320,100) reuseIdentifier:CellIdentifier] autorelease];
//custom cell code
}
return cell;
}
More Information:
I have used NSNotification which notifies to current class when parsing is complete, after receiving notification , current class method calls DataSource, Delegate methods (which is defined in a separate class file).
So UItableViewCell customization (which is in ListDataSource) and table view(in current class) both are in different classes.
A problem is
NSString *CellIdentifier = [NSString stringWithFormat:#"%i",indexPath.row];
The id needs to be the same for all cells of the same class, otherwise you never reuse them. As you can see in most examples, it is indeed a constant in most (all?) cases.
Little explaination on the reuseIdentifier: every time a cell gets out of screen, you can reuse it instead of creating a new one. To reuse it, you need a cell in queue with the same identifier as the one you pass to dequeueReusableCellWithIdentifier. The way you did, the cells are never reused, because each id is unique (they may or may not be reused in case a row reappears on screen, depending on queue size, which is not configurable AFAIK). This is why personalization of the cell should happen OUTSIDE the "cell == nil" block. Long story short, you are using the reuseIdentifier not as intendend.
I think Michele is correct, but I would also add that it looks like you are doing your cell customization where the cell gets created. What you should be doing is something more like this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *CellIdentifier = #"CellIdentifier";
UITableViewCell *cell = (UITableViewCell)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectMake(0,0,320,100) reuseIdentifier:CellIdentifier] autorelease];
//custom REUSABLE cell code here, e.g. text color, etc.
}
NSString *cellText = [dataArray objectAtIndex:indexPath.row]; //assuming you have a simple array for your data
cell.textLabel.text = cellText;
return cell;
}
I would also add that I'm not sure why you are able to run the app with the code you have here, since UITableViewCell cell = ... is an invalid initializer. It should be UITableViewCell *cell = ....
It would be helpful to see how you are customizing your cell, since without that it's hard to see what's happening.
Related
just created an UITableView,
trying to display some custom data from an array, but what ever I do, i get no text displayed.
NSLog tell me the right text and right amout but no text in table cell.
here is the code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier];
}
cell.textLabel.text = #"test";
[self configureCell:cell atIndexPath:indexPath];
return cell;
}
Things to check:
Is cellForRowAtIndexPath getting called? Put a breakpoint in
Make an array in ViewDidLoad (property, alloc'd) and addObjects #"One", #"Two", #"Three", nil and then cell.textLabel.text = [theArray objectAtIndex:indexPath.row];
IfcellForRow is being called, this will show in the cells.
What doesConfigureCell do? Include the code please.
Also check your tableView delegate methods are being called (NumberofRowsInSection etc)
Always set cell properties (text, images, accessory views, etc) in the
tableView:willDisplayCell:forRowAtIndexPath
delegate method.
UITableView sometimes send a prepareForReuse to the cell after it is returned from
tableView:cellForRowAtIndexPath
which causes the cell to reset it's labels and images.
In my tableview have custom cells that I initialize from a UITableViewCell class. I have sections for first letters of records and have an indexPath that is being created dynamically.
I wanted to add a search display controller to my tableview. So I did, created all methods to filter data. I am sure that my functions are working well because I am printing array count to screen for search results.
My problem is that the first time view loads, the data is on the screen. But when I hit the search input and type a letter, than I get 'UITableView dataSource must return a cell from tableView:cellForRowAtIndexPath:' error. After I used a breakpoint I saw that my custom cell is nil after searching. Data is exist, but cell is not being initialized.
Here is the code I use for custom cell initializing:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"ObjectCell";
SpeakerCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
NSDictionary *myObject = [[sections valueForKey:[[[sections allKeys] sortedArrayUsingSelector:#selector(localizedCaseInsensitiveCompare:)] objectAtIndex:indexPath.section]] objectAtIndex:indexPath.row];
cell.label1.text = [myObject objectForKey:#"myValue"];
return cell;
}
I believe I made a mistake when putting controls in IB. So I added screenshots of objects:
Connections inspector for my table view
Connections inspector for my search display controller
EDIT: Problem is actually solved, I have used a UISearchBar instead of Search Display Controller but I guess this issue remains unsolved. So I'm willing to try any ways to make it work.
As of here search display controller question,
you need to access the self.tableView instead of tableView:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:#"CellId"];
// do your thing
return cell;
}
For those using iOS 5 and StoryBoards, you would want to use the following method instead of initWithIdentifier:
initWithStyle:(UITableViewCellStyle)stylereuseIdentifier:(NSString *)reuseIdentifier
Example:
NSString *cellIdentifier = #"ListItemCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
}
I'm not sure about how this should work in storeboarding.
But normally you would check if the [tableView dequeueReusableCellWithIdentifier:CellIdentifier] returns a cell.
Because if the cell in not loaded before or there aren't any cells to reuse you will have to create a new cell:
SpeakerCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[SpeakerCell alloc] initWithIdentifier: CellIdentifier];
}
Also when in declaring local variables in Objective-C we tent not to capitalize the first letter.
I had the same issue, with custom cells (built in Storyboard) not being drawn as soon as the first letter was put in the search field. The search was successful however.
Finally I found a good tutorial from Brenna Blackwell suggesting to configure manually the cell drawing in the corresponding subclass of UITableViewCell, adding UILabels and other items.
Below is code for UITableView, But when i scroll its behaves weirdly (too annoying)... This problem is due to reuseIdentifier.... but dont know how to solve..
- (UITableViewCell *)tableView:(UITableView *)tableView1 cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView1 dequeueReusableCellWithIdentifier:CellIdentifier];
NSInteger imgTag = 1;
NSInteger lblTag = 2;
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
UIImageView *imgView = [[UIImageView alloc] initWithFrame:CGRectMake(2, 2, 52, 52)];
// Image:[UIImage imageNamed:[self.glassType objectAtIndex:indexPath.row]]];
imgView.tag = imgTag;
[cell.contentView addSubview:imgView];
[imgView release];
UILabel *lblName = [[UILabel alloc] initWithFrame:CGRectMake(60, cell.frame.size.height/4, 200, 21)];
// lblName.text = [self.glassName objectAtIndex:indexPath.row];
lblName.tag = lblTag;
[cell addSubview:lblName];
[lblName release];
}
NSInteger imgIndex = 2;
NSInteger lblIndex = 3;
((UIImageView *)[cell viewWithTag:imgTag]).image = [[self.glassType objectAtIndex:indexPath.row] objectAtIndex:imgIndex];
((UILabel *)[cell viewWithTag:lblTag]).text = [[self.glassName objectAtIndex:indexPath.row] objectAtIndex:lblIndex];
return cell;
}
- (void)tableView:(UITableView *)tableView1 didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView1 cellForRowAtIndexPath:indexPath];
cell.accessoryType = UITableViewCellAccessoryCheckmark;
}
How to make Cell for row at index so that it remains constant even when scrolled??? Also how to make single selection in UITableView??
The answer is that you should not add subviews to your table cells outside of the "if (cell == nil) { ..." clause or they get added over and over again to the same cell when it gets re-used.
See my answer to this question for a more detailed explanation, including code for how to fix it:
cellForRowAtIndexPath memory management
You also cannot store state in table cells because as soon as they scroll offscreen they are recycled and re-appear at a different index in your table. You need to set up an array of model objects to store state for your table cells (such as what their accessory type should be). A more detailed explanation can be found in my answer to this question:
Looping through UITableViewCells of a UITableView
If you fix how you are adding subviews to the cells, and store your "ticked" state in an array of model objects as well as setting the cell.accessoryType (so that it can be restored when the cell is dequeued), then your approach to row selection is otherwise correct.
So put this in your tableView:cellForRowAtIndexPath: method, just before the return cell;:
MyModelObject *object = [self.arrayOfModelObjects objectAtIndex:indexPath.row];
BOOL isChecked = object.checked;
cell.accessoryType = isChecked? UITableViewCellAccessoryCheckmark: UITableViewCellAccessoryNone;
And in your tableView: didSelectRowAtIndexPath: method, get rid of the current logic and replace it with:
- (void)tableView:(UITableView *)tableView1 didSelectRowAtIndexPath:(NSIndexPath *)indexPath
for (int i = 0; i < [self.arrayOfModelObjects count]; i++)
{
MyModelObject *object = [self.arrayOfModelObjects objectAtIndex:i];
object.checked = (i == indexPath.row); // only check the one we just tapped
}
//refresh table to update the accessory views for all rows
[tableView1 reloadData];
}
Obviously replace the arrayOfModelObjects with your own model implementation. You could just use an array of NSNumber objects containing bools if you don't want to create a custom class for this purpose.
The recycling queue is like a pool where previously created Cells are stored before to reuse them. For example when you scrolls up, at the moment the cell disappears above, it is stored in the queue and becomes available for the cell that will appear at the bottom. Ok ?
Actually the number of cells really created is exactly the max simultaneous cell you can display in your table (in most cases from 3 to 8). In other words your if (cell == nil) code is executed (more or less from 3 to 8 times) at the first reloadData to create the pool of cells your table needs.
Then all you make on a cell is kept as it and appears again when you dequeue it. It's now easy to understand that, in your code, you have to make all strictly row-dependant settings outside the if (cell == nil) block. The same way, do not add subViews outside the if (cell == nil) block, you can imagine the thousands of subview you will add each time you reset a dequeued cell !
Tip: if you need some custom cleanup before reusing a cell (like to set an image to blank), you can create a custom UITableviewCell class and implements the prepareForReuse method.
Is it clear ?
Always reload your tableView in viewWillAppear method instead of viewDidLoad.
(void) viewWillAppear:(BOOL)animated{
[self.tableView reloadData];
}
This avoids most of all unexpected and annoying problems. :)
i have a tableview that contains a uiwebview in its first row. i would like to change this tables' webview object with new one when user clicks a button. i am using the code given below but it does not work fine. older object is there and the newer one is over it although i recreate the webview. how can i remove the older one from cell?
thanks...
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
NSLog(#"NİL.......");
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
} else {
NSLog(#"NOT NİL.......");
}
[cell addSubview:webView];
return cell;
}
Since you are using "dequeueReusableCellWithIdentifier", your should reconfigure the cell each time when the cell is displayed.
Please remember that the cells with same identity will be reused. It's better to set the same identity to one particular type of cells, typically cells with same subviews and layout.
This is sample for how to load data and reconfigure a cell: http://code.google.com/p/tweetero/source/browse/trunk/Classes/MessageListController.m
Here is a tutorial to get familiar with UITableView:
http://developer.apple.com/library/ios/#documentation/userexperience/conceptual/TableView_iPhone/CreateConfigureTableView/CreateConfigureTableView.html
I have a UITableView with several datasources. This is because, I need to switch the data with a UISegmentedControl, and if I add them as subviews, I cannot use the statusBar to scroll up, etc.
At first, I show a login screen:
self.tableView.dataSource = loginTableView;
self.tableView.delegate = loginTableView;
[self.tableView reloadData];
Then, once the user has logged in, I do the following to change to index:1 of the segmentedControler, which is their profile:
self.tableView.dataSource = profileTableView;
self.tableView.delegate = profileTableView;
[self.tableView reloadData];
However, the table view updates, but is a bit of a mix between the two dataSources. Some of the text has changed, some is overlapping, while some of it is completely missing.
Is there another way I should be changing the dataSource for a UITableView?
Thx
I had the exact same problem. My solution was to make the tableView hidden, change it's source, reload the data and make the tableView visible again.
Example in C# (MonoTouch):
tableView.Hidden = true;
tableView.Source = newTableViewSource;
tableView.ReloadData();
tableView.Hidden = false;
Not sure why this is happening from the code you have posted.
Instead of changing the delegate and datasource, swap out whatever ivar represents the data being displayed:
- (NSArray*)tableData{
if(showingLogin)
return self.loginData;
return self.profileData;
}
Now you only have 1 UITableViewController instance, but a BOOL to tell you which datasource to use.
The table view is caching the cells internally it uses for displaying your data. So if you change the data source you should also check that your is - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath method is updating all the cells to the correct new values.
From your description it sounds like it is using the cached UITableViewCell instances and is not updating it to the correct new data in all cases. Perhaps code like this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
cell.frame = CGRectZero;
cell.textLabel.font = //Set font;
cell.selectionStyle = UITableViewCellSelectionStyleNone;
cell.textLabel.text = #"My Text for this cell"; // <==== Not good! Move it out of this if block
}
// Set cell text here
}
The simplest solution I found for this sort of problem is to just make the String you use for the cell creation (CellIdentifier) depending of the data Source. In this case you don't mix the cell of the different content types (and this helps you also if the cells need to have a different look depending on the mode).
I had this problem, turned out it was because my CellIdentifiers were the same...
static NSString *CellIdentifier = #"Cell";
Change one of them and the cells layout properly
static NSString *CellIdentifier2 = #"Cell2";
Wow, that's freaky.
The last time I did something like this, I simply used multiple views, hiding one and showing another when the segmented control was tapped. There are other possible solutions, but this is probably easiest and perhaps most efficient.
I have the same issue and what you have to do is use a different cell identifier within - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath.
if (self.segmentedControl.selectedSegmentIndex == 0) {
self.cellIdentifier = #"segmentOne";
} else {
caseAtIndexPath = [self.awaitingReviewCaseList caseAtIndex:indexPath.row];
self.cellIdentifier = #"segmentTwo";
}
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:self.cellIdentifier forIndexPath:indexPath];