I am trying to create an application that has two UITableViews placed side-by-side. The left one lists article categories and the right one displays article previews (kind of like flipboard's search view).
On the left tableview's didSelectRowAtIndexPath, I am supposed to download the article and display the previews on the right UITableView. However, I cannot seem to make this work.
My assumption is that I reload the data on the tableview before the download is finished. Any suggestions?
EDITED:
Here's my current code:
- (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];
if (tableView.tag == 1)
{
//if it's the left tableView (no problem here)
NSDictionary *catDic = [[Category categories] objectAtIndex:indexPath.row];
cell.textLabel.text = [catDic valueForKey:#"name"];
cell.textLabel.font = [UIFont fontWithName:#"HelveticaNeue-Bold" size:[UIFont labelFontSize]];
}
if (tableView.tag == 2)
{
//if it's the right tableView
ArticlePreview *articleView = [[ArticlePreview alloc] initFlexibleHeightRowForArticleInfo:[self.articleInfos objectAtIndex:indexPath.row]];
//ArticlePreview is a custom class that create the articlePreview view,
//articleInfos is a variable that holds the articles in core data
[cell.contentView addSubview:articleView];
[articleView release];
}
}
return cell;
}
-(void) loadArticlePreview: (NSNumber *)_idx
{
[Category downloadArticlesforIndex:[_idx intValue]];
AppDelegate *delegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [delegate managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"ArticleInfo" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSError *error;
self.articleInfos = [context executeFetchRequest:fetchRequest error:&error];
[fetchRequest release];
[self.articlePreviewTableView reloadData];
//articlePreviewTableView is the right table view identifier, hooked with IBOutlet and all
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (tableView.tag == 1) //if it's the left table
{
[self performSelectorInBackground:#selector(loadArticlePreview:) withObject:[NSNumber numberWithInt:indexPath.row]];
}
}
The problem is that the right tableview does not refresh. I think these methods are where the problem probably is.
According to your code, if you dequeue a UITableViewCell, its going to use the old cell, without any modifications for the actual cell that you need. Change it to so:
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}//after this, use the tableView tag to identify.
You are also adding a content view to the preview table cell. I strongly recommend you create a custom UITableViewCell class when you do this. I found that to be the only way the adding subviews works in cells and is a lot easier to manage the cells with the custom class.
I assume you are doing the downloading in some method in the ArticlePreview. You don't need to reload the tableView once the download is finished. Since the ArticlePreview object has been added as the cell's subview, when the download is finished in it, call setNeedsDisplay when the view is content is downloaded.
You cannot reload your tableview in a background thread. You will have to create a method like this
-(void)reloadTable
{
[self.articlePreviewTableView reloadData];
}
Then call this method on the main thread inside the -(void) loadArticlePreview: (NSNumber *)_idx method
Hope this solves ur problem.
Related
I have an app consisting of a TabBar with a few TabBarControllers. One Controller contains a very simple table, which is supposed to display the contents of a NSMutableDictionary. When you hit the appropriate button, the Dictionary is updated in a separate Controller and the view switches to the UITableViewController, displaying the newly updated table.
I can see the Dictionary being updated. But the TableView never reflects the changes. In fact, it seems to display the changes only the 1st time I enter that screen.
I have tried [self table.reloadData] and while it gets called, the changes aren't reflected to the UITableView.
Does anyone have any suggestions? I am happy to post code, but am unsure what to post.
Update: the table is updated and refreshed properly only the 1st time it is displayed. Subsequent displays simply show the original contents.
Background:
The tableview gets filled from a dictionary: appDelegate.currentFave. The tableview should get refreshed each time the ViewController is invoked by the TabBarController.
- (void)viewWillAppear:(BOOL)animated
{
NSLog(#"in viewWillAppear");
[super viewWillAppear:animated];
[self loadFavesFile];
[self.tableView reloadData];
}
// load the Favorites file from disk
- (void) loadFavesFile
{
// get location of file
NSString *path = [self getFavesFilePath];
// The Favorites .plist data is different from the Affirmations in that it will never be stored in the bundle. Instead,
// if it exists, then use it. If not, no problem.
if ([[NSFileManager defaultManager] fileExistsAtPath:path]) {
// read Faves file and store it for later use...
NSMutableDictionary *tempDict = [NSMutableDictionary dictionaryWithContentsOfFile:path];
appDelegate.sharedData.dictFaves = tempDict;
// grab the latest quote. Append it to the list of existing favorites
NSString *key = [NSString stringWithFormat:#"%d", appDelegate.sharedData.dictFaves.count + 1];
NSString *newFave = [NSString stringWithFormat:#"%#", appDelegate.currentFave];
[appDelegate.sharedData.dictFaves setObject:newFave forKey:key];
} else {
NSLog(#"Favorites file doesn't exist");
appDelegate.sharedData.dictFaves = nil;
}
}
// this gets invoked the very first call. Only once per running of the App.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// reuse or create the cell
static NSString *cellID = #"cellId";
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:cellID];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellID];
}
// allow longer lines to wrap
cell.textLabel.numberOfLines = 0; // Multiline
cell.textLabel.lineBreakMode = UILineBreakModeWordWrap;
cell.textLabel.font = [UIFont fontWithName:#"Chalkduster" size:(16)];
cell.textLabel.textColor = [UIColor yellowColor];
// NOTE: for reasons unknown, I cannot set either the cell- or table- background color. So it must be done using the Label.
// set the text for the cell
NSString *row = [NSString stringWithFormat:#"%d", indexPath.row + 1];
cell.textLabel.text = [appDelegate.sharedData.dictFaves objectForKey:row];
return cell;
}
I found the problem. I was not properly initializing and assignng the TableView in my view controller. See below
- (void)viewDidLoad
{
[super viewDidLoad];
tableView = [[UITableView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame] style:UITableViewStylePlain];
tableView.dataSource = self;
tableView.delegate = self;
tableView.backgroundColor=[UIColor blackColor];
self.view = tableView;
}
Assuming the code you have put up is correct, you want to use [self.table reloadData]. You have the . in the wrong place.
I had this same problem yesterday, for me it turned out I had set the wrong file owner in interface builder and hadn't set up the data source and delegates for the table view properly.
Try going into interface builder and right-clicking on the file owner, this should show you if anything isn't connected up properly.
You should make sure that your Interface Builder connections are set up properly, but what this problem really sounds like is that you have your UITableViewCell setup code in cellForRowAtIndexPath: inside your if(cell == nil) statement. Which it shouldn't be. Let me explain. If you have a list of cells, and you want to set the titles to each cell to a string in an array called myArray, right now your (incorrect) code looks like this:
- (UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"cellIdentifier"];
if (cell == nil) {
// No cell to reuse => create a new one
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:#"cellIdentifier"] autorelease];
[[cell textLabel] setText:[myArray objectAtIndex:[indexPath row]]];
}
return cell;
}
Can you see the problem with that logic? The cell will only get an updated title if no reusable cell can be found, which, in your case, sounds like the situation. Apple says that you should create a 'new' cell each time cellForRowAtIndexPath: is called, which means that you put all of your setup code outside of the if(cell == nil) check.
Continuing with this example, the proper code would look like this:
- (UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"cellIdentifier"];
if (cell == nil) {
// No cell to reuse => create a new one
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:#"cellIdentifier"] autorelease];
}
[[cell textLabel] setText:[myArray objectAtIndex:[indexPath row]]];
return cell;
}
This way, the cell gets assigned the proper string whether or not a reusable cell is found and so calling reloadData will have the desired effect.
In my app I have a UITableViewCobtroller which creates the table view with checkmark accessory type. Table View loads and works correctly. In didSelectRowAtIndex method I wrote method of adding data in sqlite dataBase:
[self.dataBaseController openSettingsDB];
[self.dataBaseController updateRecordIntoTableNamed:kTableName withField:kSField1Name fieldValue:newCell.textLabel.text];
[self.dataBaseController closeDB];
It works well. So what I want is to retrieve the recorded data from dataBase and when the application is relaunched to select the row, that has the title, that I retrieved from sqlite dataBase.
I tried this:
-(void)viewDidLoad
{
NSArray *array = [[NSArray alloc] initWithObjects:
kCourse1, kCourse2, kCourse3, kCourse4, kCourse5, kCourse6, nil];
self.list = array;
[self.dataBaseController openSettingsDB];
[self.dataBaseController getRowFromTableNamed:kTableName whichRow:kSField1Name];
self.chosenStr = self.dataBaseController.dataString;
[lastIndexPath indexAtPosition:[array indexOfObjectIdenticalTo:self.chosenStr]];
UITableViewCell *cell = [[UITableViewCell alloc] init];
cell = [self.tableView cellForRowAtIndexPath:lastIndexPath];
cell.accessoryType = UITableViewCellAccessoryCheckmark;
[self.tableView reloadData];
[array release];
[super viewDidLoad];
}
But it doesn't work. Please, suggest me any ideas. Thanks in advance.
Alick
Updated Answer:
You should not be doing this in viewdidLoad in the first place. Its a bad practice, you should do it in cellForRowAtIndexPath:.
More explaination:
The table is not loaded as of yet in viewDidLoad. You have to do [self.tableView reloadData]; before doing anything else. Doing that will call the delegate methods for table (it has no idea how many cells there are so getting the cell for any specfic index path doesn't make sense). See Table View Programming guide.
Also:
UITableViewCell *cell = [[UITableViewCell alloc] init];
cell = [self.tableView cellForRowAtIndexPath:lastIndexPath];
cell.accessoryType = UITableViewCellAccessoryCheckmark;
First, this is wrong. You are leaking memory with that alloc/init. Just do:
UITableViewCell *cell = nil;
cell = [self.tableView cellForRowAtIndexPath:lastIndexPath];
cell.accessoryType = UITableViewCellAccessoryCheckmark;
You need a call to UITableView's - scrollToRowAtIndexPath:atScrollPosition:animated: method. I would put this the viewDidAppear: method of your UITableViewController. See UITableView Class Reference.
I may be using it incorrectly but i think i have it right. I load data into my UITableViewController like so.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
CoCoachAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
// Configure the cell...
switch (indexPath.section) {
case 0:
[cell.textLabel setText:#"Click to add new rower"];
cell.textLabel.textAlignment = UITextAlignmentCenter;
break;
case 1:
[cell.textLabel setText:[[appDelegate teamRoster]objectAtIndex:indexPath.row]];
break;
}
return cell;
}
And everything works fine, i then push the user to a different viewController and allow them to enter their name into a text field. I take their name and add it into the same array that populated my UITableViewController, and from the UITableViewController i call:
[self.tableView reloadData];
But nothing happens. If i check my array i can see that it has the correct number of objects, and their name is the last entry, but the tableview remains unchanged...
I was thinking maybe i just dont know how to use reloadData, but from what i have been reading elsewhere this should be working.
Any thoughts?
Your crash is due to the fact that you are doing
NSLog(#"%#",[[appDelegate teamRoster] count]);
when you should be doing
NSLog(#"%d",[[appDelegate teamRoster] count]);
Using %# sends the objec the message description, which does not work for ints (or floats or BOOLs).
The NSLog crashes because you are using the wrong formatter type (#"%#"), it should be:
NSLog(#"%d",[[appDelegate teamRoster] count]);
Other than that, where is reloadData being called from? Make sure it's happening on the Main thread.
i can successfuly delete data from tableview , but when i go to main menu and come back then i see all the old data,
how to make data consistent , i.e once deleted it will be gone forever
- (void)viewDidLoad
{
[super viewDidLoad];
appDelegate = (DatabaseTestAppDelegate *)[[UIApplication sharedApplication] delegate];
{
appDelegate.favDetail = [[NSMutableArray alloc] initWithObjects:#"1", #"2",#"4",nil];
}
}
- (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];
}
appDelegate = (DatabaseTestAppDelegate *)[[UIApplication sharedApplication] delegate];
NSString *cc=[appDelegate.favDetail objectAtIndex:indexPath.row];
NSLog(#" vale is %d",indexPath.row);
// Configure the cell...
NSArray *theParameters = [cc componentsSeparatedByString:#","];
NSLog(#"array is %#",cc);
NSLog(#"arraytheParameters is %#",theParameters);
cell.text=cc;//theParameters;
return cell;
}
// Override to support editing the table view.
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{ NSLog(#"matrix is her%#",appDelegate.favDetail);
if (editingStyle == UITableViewCellEditingStyleDelete) {
// Delete the row from the data source
[appDelegate.favDetail removeObjectAtIndex: indexPath.row];
[tableView beginUpdates];
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject: indexPath] withRowAnimation:UITableViewRowAnimationFade];
[tableView endUpdates];
}
/*
else if (editingStyle == UITableViewCellEditingStyleInsert) {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
} */
}
- (void)viewWillAppear:(BOOL)animated
{ [tableView reloadData];
[super viewWillAppear:animated]; // no use , it shows old data
}
thanks
If you are looking to save the state of your model beyond the life of your table view controller, there are few things you can do. One is to write the array to a file in the documents directory and read it back on reload. This would work well will small sets of data. But if you are going to use a large data set, you have to use Core Data.
As for this specific case, you are reinitializing the data on every viewDidLoad. I suggest you do this in viewDidLoad –
...
if ( appDelegate.favDetail == nil ) {
appDelegate.favDetail = [[NSMutableArray alloc] initWithObjects:#"1", #"2",#"4",nil];
}
...
This will make sure that you don't reinitialize it.
you have to delete the data in both your view and your model
Check out the place where appDelegate.favDetail is getting data from...
While deleting you are removing from appDelegate.favDetail but When you go to main menu and come back you are reassigning the values to appDelegate.favDetail...
Checkout that appDelegate.favDetail only gets the value once.. or another option is you remove the data from the main source from where the favDetail gets the value
Happy iCoding....
I have a UITableView that displays various nsstrings from a custom object called a "Transaction." in CellForRowAtIndexPath it the allocates a new instance of a transaction and then copies the the values from a particular transaction that lives in the delegate array so I can access it from different views. It then uses the new transaction 'copy' and it's properties to popluate the table cell. The problem I'm having is if I release the copied transaction object when I go to redisplay the table the app crashes. if I comment out the release the program runs well, but I'm worried about memory management obviously. My question is what the heck should I do, is there another place to release this?
Here is the code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"TransCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
// Set up the cell...
NSUInteger row = [indexPath row];
CashAppDelegate *mainDelegate = [(CashAppDelegate *) [UIApplication sharedApplication] delegate];
Transaction *cellTrans = [[Transaction alloc] init];
cellTrans = [mainDelegate.transactionArray objectAtIndex:row];
NSString *string = [NSString stringWithFormat:#"$%1.2f %# | %#", cellTrans.amount, cellTrans.category, cellTrans.descriptionString];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
cell.text = string;
//[cellTrans release];
return cell;
}
You don't actually need to allocate a new Transaction object seeing as you only want/need a reference to one that already exists in your delegate transaction array. So instead of:
Transaction *cellTrans = [[Transaction alloc] init];
cellTrans = [mainDelegate.transactionArray objectAtIndex:row];
and tidying up with
[cellTrans release];
just obtain a reference with this one line:
Transaction *cellTrans = (Transaction *)[mainDelegate.transactionArray objectAtIndex:row];
You want:
Transaction *cellTrans = [mainDelegate.transactionArray objectAtIndex:row];
instead of:
Transaction *cellTrans = [[Transaction alloc] init];
cellTrans = [mainDelegate.transactionArray objectAtIndex:row];
Then you wont need the release. The problem is cellTrans is a pointer. What you've done is create a new object, point to it, then point to something else ignoring the object you just made. Then you try to get rid of the object in the array rather than the one you just made.