I'm working on a project that parse data from web service and shows in a table view. Everything is ok, but I'm not satisfied with the performance of tableview. After parsing data form web I called reload data to show the data, But it is not showing cells immediately. It shows the data after 10/15 seconds later. I've checked all data's are loaded before I called reload data. And the strange thing is that it shows the cells immediately if I try to drag the table.
Any idea?
Update
-(void)receivedCategories:(NSMutableArray *)categoryItems{
[self.spinner stopAnimating];
self.categories=categoryItems;
if (self.tableView!=nil) {
[self.tableView reloadData];
}
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [self.categories count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"CategoryCell";
CategoryCell *cell = [self.tableView dequeueReusableCellWithIdentifier:CellIdentifier];
cell.category = [self.categories objectAtIndex:indexPath.row];
return cell;
}
CategoryCell.m
#implementation CategoryCell
- (void)setCategory:(Category *)category
{
[self.categoryTitle setText:category.title ];
[self.categorySubTitle setText:category.description];
}
#end
It seems you're not calling [self.tableView reloadData]; from the main thread and that could be the cause of your problem.
You could try:
[self.tableView performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:YES];
Also check the accepted answer here for more info.
I had the same problem, the time lag between [tableview reloaddata] and the app calling the method [cellForRowAtIndexPath] was too long (a couple of seconds). Putting the [reloaddata] method inside a dispatch_async(dispatch_get_main_queue(), ^{} solved the issue.
Are you using custom fonts?
I had a very similar problem, where my UITableView was taking considerably long to load. I finally found the solution, and it didn't had anything to do with my code or the way I was loading custom cells.
The problem occurred at run time, when the system loaded my custom UITableViewCells (with predefined custom fonts) from the storyboard, and because the custom fonts where not correctly installed, the system would look all over the place for the font files, causing a considerable lag when loading the file.
To make sure your fonts are correctly installed:
Include your fonts in your XCode project
Make sure that they’re included in the target
Double check that your fonts are included as Resources in your bundle
Include your iOS custom fonts in your application plist
Find the name of the font (To find out the correct name, you can print all installed fonts)
for (NSString* family in [UIFont familyNames]){
NSLog(#"Family %#", family);
for (NSString* name in [UIFont fontNamesForFamilyName: family]){
NSLog(#"\t Font%#", name);
}
}
Use UIFont and specify the name of the font
[UIFont fontWithName:#"FONT_NAME" size:size];
With the amount of code you are showing (none), I would have to say that the performance hit you are having is because you are calling reloadData with a very large number of records, or because you are doing some time consuming parsing/calculation when creating the cell. Also, If you are showing images on those cells, that might also cause performance problems.
When you create CategoryCell? In first time dequeueReusableCell will return nil. So then make the Category cell.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"CategoryCell";
CategoryCell *cell = [self.tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (!cell) {
cell = [[[CategoryCell alloc] init] autorelease]; }
cell.category = [self.categories objectAtIndex:indexPath.row];
return cell;
}
Related
I'm stuck with a seemingly very simple app that I'm trying to develop
Basically, I have a UITable, and two buttons: red/blue
When a button is pressed, a row with corresponding title of that button is append to the table
I'm overwhelmed by how complicated UITableView has to be implemented (datasource, delegate, resuable identifier, etc)
Can anyone help me out with this, preferably show me detailed codes
For my Buttons, I have something like this
- (IBAction)buttonPressed:(UIButton *)sender{
NSString *item = sender.currentTitle;
[self.cellArray addObject:item];
[self.myTable reloadData];
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.cellArray.count;
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"The Table Cell";
self.myTableCell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (self.myTableCell == nil) {
self.myTableCell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
self.myTableCell.textLabel.text = [self.cellArray objectAtIndex:indexPath.row];
return self.myTableCell;
}
Since you are learning this..i will post a simple solution.
Make add a member variable NSMutableArray *cellArrays;
initialize it in your viewDidLoad
in buttonPressed check;
if([#"red" isEqualToString:[YourButton titleForState:UIControlStateNormal]])
{
[self.cellArrays addObject:#"red"];
}
else
{
[self.cellArrays addObject:#"blue"];
}
[self.YOURTABLEVIEW reloadData];
Now in your table view datasource method
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.cellArrays.count;
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//in between your code;
YOURCELL.textlabel.text = [self.cellArrays objectAtIndex : indexpath.row];
}
Try this ..
I'd recommend subclassing UITableViewController, then override the dataSource and delegate methods and you should be good to go. Going straight to UITableView is more complicated without any obvious benefits.
You may not be using Core Data, but I still recommend this lecture because it hooks up a TableViewController and works great - all code included. Check it out:
http://www.stanford.edu/class/cs193p/cgi-bin/drupal/node/289
The lectures in iTunes U explain everything further.
Enjoy,
Damien
I wanted to add that "One more thing, the compiler shows a warning says no reusable identifier. what exactly is that?" above...
His answer is correct, but to get rid of that compiler warning...
The tableviewcell of your tableview has to have an identifier. In IOS 5, in your storyboard, highlight the TableViewCell in your TableView, and enter a value in the Identifier. This must match the value in your code of the cell that you are creating.
i ve upgrated to iOS 5 with XCode 4.2 version.
since i ve upgraded i'm not able to create proper workign table view controller.
1) Table view is shown on the device, but when i try to scroll up, it crashes without any error message
2) when i try to select a cell , didSelectRowAtIndexPath is not called.
NOTE: the same code was workign for me before upgrating to iOS5.
This is the code for TableViewController.m file
#import "CTableViewController.h"
#implementation CTableViewController
#synthesize arrNames;
-(id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
NSLog(#"CTableViewController::initWithStyle");
// Custom initialization
}
return self;
}
-(void)viewDidLoad
{
[super viewDidLoad];
arrNames = [[NSMutableArray alloc]initWithObjects:#"Peter Frank Brauer",#"Robert Schneider",#"Winfried Walter Kem",#"Eugen Germen Bachle",#"Clara Weib",#"Heinz Guther Winkler",#"Roland Bendel",#"Swen Aschemeyer",#"Patrick Bodingmeier",nil];
}
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 1;
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return [self.arrNames count];
}
-(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];
}
// Configure the cell...
cell.textLabel.text = [self.arrNames objectAtIndex:indexPath.row];
cell.textLabel.font = [UIFont systemFontOfSize:16];
NSLog(#"CTableViewController::cellForRowAtIndexPath");
return cell;
}
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#"called didSelectRow method");
}
#end
Thank you, Please help
Suse
If it's crashing without any error message than it's probably a memory management issue and it is likely to happen because of some other part of the code, or even because a wrongly formatted nslog.
The compiler doesn't seem to notice a few obvious things like setting a fraction value for an int which will crash the app in an instant but without any meaningful error message.
Try to look for mundane things in your code and not just in the tableView implementation.
Also, is there any specific reason your are populating your arrNames with alloc,init (non-autorelease) instead of arrayWithObjects?
Also: Run the static analyzer! It may help. Keep the Run button pressed and an Analyze button will pop up.
I copy and pasted your source code into a fresh project. A few things jumped out at me.
You are subclassing UITableViewController. Try just subclassing UIViewController, and delete your initWithStyle method.
Make sure that in the nib, the tableview delegate and datasource are both attached to File's Owner.
If you are still experiencing crashes, then this code is NOT your problem.
How you can select a row if is crashing first?
By the way,the code seems to be not the problem.
Try this after restarting your Xcode:
Go to Organizer
Select the Project tab
Click on your opened project and DELETE Derived data.
Then wait for the re-indexing. (You can see the top bar loding and the code with new index target).
So like usual Clean All Target and Build.
It works for me in similar cases.
Hope it help.
in My case it was controller instance declared locally and it was losing delegade so i declared instance in header and everything works.
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];
I have one last problem that is killing me.
I am playing around with an older project which used the tableView:accessoryTypeForRowWithIndexPath method, of which i am trying to rewrite the app without it.
I have deleted the tableView:accessoryTypeForRowWithIndexPath method, but my solution isnt working just yet.
The problem is that now when I press the 'Edit' button on my DetailViewController View, the 'editing' accessory types dont show.
Below is my actual code, and I have also included the project file for this, because I am desperate for a solution that works.
My question is, what code do I have to change, to get the accessory to change, and no other effects. (I know the RootViewController works, but how can i get the DetailViewController table to do the same?)
Regards, #norskben
Project File Download: Get it here.
setEditing
- (void)setEditing:(BOOL)editing animated:(BOOL)animated {
[super setEditing:editing animated:animated];
[self.navigationItem setHidesBackButton:editing animated:animated];
[tableView reloadData];
}
cellForRowAtIndexPath
- (UITableViewCell *)tableView:(UITableView *)tblView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:CellIdentifier] autorelease];
cell.accessoryType = UITableViewCellAccessoryDetailDisclosureButton;
cell.editingAccessoryType = UITableViewCellAccessoryCheckmark;
}
switch(indexPath.section) {
case 0:
cell.textLabel.text = coffeeObj.coffeeName;
break;
case 1:
cell.textLabel.text = [NSString stringWithFormat:#"%#", coffeeObj.price];
break;
}
return cell;
}
Project File Download: Get it here.
In your setEditing:animated: method you are doing two things that you are really not supposed to do. Reloading the table data there makes no sense. And if you have to change the buttons in your tab bar manually then you have not set them up properly. The system takes care of that for you. If you have properly setup an edit button then you don't even manually have to call setEditing:animated: actually.
I think you should review some UITableView sample code.
I am producing an iPhone app for which part of the interface is exactly like the 'Most Popular' section of the iPhone YouTube app.
This 'popular' section is accessed from a Tab Bar at the bottom and the navigation bar at the top contains a UISegmentedControl to select 'Today, This Week, Month etc..'
Because most of the app consists of UITableViews with cells containing very similarly structured content, I have created a common MyAppTableViewController which inherits UITableViewController. My 'popular' section thus consists of a PopularTableViewController which inherits MyAppTableViewController. The actual UITableView resides within MyAppTableViewController.
PopularTableViewController has the method:
- (void) segmentChangeTimeframe:(id)sender {
UISegmentedControl *segCtl = sender;
if( [segCtl selectedSegmentIndex] == 0 )
{
// Call [self parse-xml-method-which-resides-in-MyAppTableViewController]
}
//... ... ...
}
The MyAppTableViewController makes use of NSXMLParser and thus has the code:
- (void)parserDidEndDocument:(NSXMLParser *)parser {
[self.myTableView reloadData];
}
(There are other methods which updates the data structure from which the table view gets it's data)
I have put console output code into the xml parsing methods, and when run, selecting the different segments causes the correct xml files to be parsed fine and the data structure seems to contain the correct values.
The problem is that the contents of the table cells wont change! grr! UNLESS!... A cell is scrolled out of view, and then back into view... THEN its changed!
I have done lots of searching about for this problem and one suggestion for a similar problem was to place the [self.myTableView reloadData] into its own method e.g. myReloadDataMethod and then use:
[self performSelectorOnMainThread:#selector(myReloadDataMethod) withObject:nil waitUntilDone:NO];
I tried placing the above code into the parserDidEndDocument method and it made absolutely no difference! I'm absolutely stumped and am wondering if anybody has any idea what's going on here.
Update:
The code to populate the cells is done with:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *MyIdentifier = #"MyIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:MyIdentifier] autorelease];
}
// Set up the cell
int itemIndex = [indexPath indexAtPosition: [indexPath length] - 1];
NSString *artistName = [[myItemList objectAtIndex: itemIndex] objectForKey: #"itemA"];
NSString *mixName = [[myItemList objectAtIndex: itemIndex] objectForKey: #"itemB"];
cell.textLabel.text = itemA;
cell.detailTextLabel.text = itemB;
cell.accessoryType = UITableViewCellAccessoryDetailDisclosureButton;
return cell;
}
The above code is in MyAppTableViewController which is also where myItemList resides.
Your -performSelectorOnMainThread: code is for when you make changes to the model classes on a background thread. UI events (including -reloadData) need to occur on the main thread. If you're not using a background thread, then this is unnecessary. If you are, something like it is mandatory.
If you are changing the value of a specific cell, the way you achieve that is to change the cell itself. On iPhone, cells are full views (unlike on Mac), so if you want to change their data, you just change their data and call -setNeedsDisplay. You can get the cell (view) for a given location using -cellForRowAtIndexPath:. You can determine if a given cell is onscreen by using -indexPathsForVisibleRows or -visibleCells.
It is very rare to need to call -reloadData. You should only do that if you are throwing away everything and loading completely different data. Instead, you should use the insertion/deletion routines to add/remove rows, and you should just update the views of existing rows when their data change.
I had this same problem, and it was because I had a [tableView beginUpdates] call without an endUpdates call after.
Have you tried [tableView setNeedsDisplay:YES]?
After calling -reloadData, do you recieve callback to tableView:numberOfRowsInSection: ?
I'm almost sure, that self.myTableView is nil here:
- (void)parserDidEndDocument:(NSXMLParser *)parser {
[self.myTableView reloadData];
}