I have a class UserData which I imported to this viewcontroller but the data on the cell
is blank.
again userdata is an object of UserData, another class. I did #import "UserData.h" but the table is empty
if I put #"something" in replacement of detailsstring or titlestring, it shows up on the table
- (void)viewDidLoad
{
detailsstring = userdata.details;
titlestring = userdata.title;
tabledata = [[NSMutableArray alloc] initWithObjects:detailsstring, nil];
tablesubtitles = [[NSMutableArray alloc] initWithObjects:titlestring, nil];
[super viewDidLoad];
}
and...
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
{
UITableViewCell *cell = nil;
cell = [tableView dequeueReusableCellWithIdentifier:#"homeworkcell"];
if(cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:#"homeworkcell"];
}
cell.textLabel.text = [tabledata objectAtIndex:indexPath.row];
cell.detailTextLabel.text = [tablesubtitles objectAtIndex:indexPath.row];
cell.textLabel.font = [UIFont systemFontOfSize:14.0];
You need to share data between view controllers, when you use #import "yourClass" you create an new instanse of that clas with the same name, but will not hold the same content(the new one will be nil). For sharing data between views you can use delegates like this answer, Core data or SQLite can bee used for the same purpose. But delegete is better as i have seen from all the answer&comments out there:)
EDIT As Jack Lawrence too has commented, NSUserDeffaults is not a proper way for sharing data between classes&objects. Just because of its easy-usability I mentioned defaults. I hope this edit will be useful for ones who see this answer later
if you are replacing detailsstring with a string literal and everything works, then detailsstring must be nil in viewDidLoad
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.
I have an tableview which is loaded from a mutablearray as listed below. However I need to asign each item in the array an ID and have a tickbox next to the item. Basically it's preferences for our search, it lets users prioritise by whichever tickboxes are ticked. So I'll want to save which items are ticked to a plist or similar.
Heres how the array is loaded:
arryTableIconsText = [[NSMutableArray alloc] init];
[arryTableIconsText addObject:#"Facilities for partially sighted or blind people"];
[arryTableIconsText addObject:#"An 'assistance dogs welcome' policy"];
[arryTableIconsText addObject:#"Disabled access facilities for wheelchair users (with assistance)"];
*more items added here*
arryTableIcons = [[NSMutableArray alloc] init];
[arryTableIcons addObject:#"visuallyImpaired_off.png"];
[arryTableIcons addObject:#"guidedogs_off.png"];
[arryTableIcons addObject:#"wheelchairassist_off.png"];
*more items added here*
And then loaded in to a table like so:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:CellIdentifier] autorelease];
}
cell.textLabel.lineBreakMode = UILineBreakModeWordWrap;
cell.textLabel.numberOfLines = 0;
cell.textLabel.font = [UIFont fontWithName:#"Helvetica" size:17.0];
cell.textLabel.text = [arryTableIconsText objectAtIndex:indexPath.row];
cell.imageView.image = [UIImage imageNamed:[arryTableIcons objectAtIndex:indexPath.row]];
return cell;
}
The result is the following:
But I don't know where to go from here to convert it in to a checkbox to the right of each cell with the ID saved?
Any tips really will be appreciated, Tom
Use an NSMutableIndexSet instance variable and populate it with the index of the cells being checked.
Then in the cellForRowAtIndexPath method, set the accessory type of the cell to UITableViewCellAccessoryTypeCheckmark or UITableViewCellAccessoryTypeNone depending on whereas the indexPath.row is in the NSMutableIndexSet or not.
Finally, when the cell is tapped, add the indexPath.row to the indexset if not alread, or remove it if it already was present, to toggle the status of the corresponding cell, then call reloadData on the tableView.
I see in your code too that you are not familiar with the reuse mechanism of UITableViewCells. You should read the "Table View Programming Guide" in Apple's documentation and learn how to implement cellForRowAtIndexPath in a more efficient and reactive way (in term of reactivity and memory footprint)
Example
// Let selectedCellIndexes be an instance variable in your .h of type NSMutableIndexSet*
// Initialize it (probably at the same place you initialise your texts & icons, once for all, probably in your init method
selectedCellIndexes = [[NSMutableIndexSet alloc] init];
Then to fill the cells:
-(UITableViewCell*)tableView:(UITableView*)tv cellForRowAtIndexPath:(NSIndexPath*)indexPath {
// Try to recycle and already allocated cell (but not used anymore so we can reuse it)
UITableViewCell* cell = [tv dequeueCellWithReuseIdentifier:...];
if (cell == nil) {
// If we didn't manage to get a reusable (existing) cell to recycle it
// then allocate a new one and configure its general properties common to all cells
cell = [[[UITableViewCell alloc] initWithStyle:... reuseIdentifier:...] autorelease];
// ... configure stuff that are common to all your cells : lineBreakMode, numberOfLines, font... once for all
cell.textLabel.lineBreakMode = UILineBreakModeWordWrap;
cell.textLabel.numberOfLines = 0;
cell.textLabel.font = [UIFont fontWithName:#"Helvetica" size:17.0];
}
// Then here change the stuff that are different between each cell
// (this code will be executed if the cell has just been allocated as well as if the cell is an old cell being recycled)
cell.textLabel.text = [arryTableIconsText objectAtIndex:indexPath.row];
cell.imageView.image = [UIImage imageNamed:[arryTableIcons objectAtIndex:indexPath.row]];
cell.accessoryType = [selectedCellIndexes containsIndex:indexPath.row] ? UITableViewCellAccessoryTypeCheckmark : UITableViewCellAccessoryTypeNone;
return cell;
}
And finally, to toggle the checkmarks:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if ([selectedCellIndexes containsIndex:indexPath.row]) {
[selectedCellIndexes removeIndex:indexPath.row];
} else {
[selectedCellIndexes addIndex:indexPath.row];
}
[tableView reloadData];
}
Just a quick question really:
I'm running a method to pull records from an sqlite database into an array, then assigning the contents of that array to an instance variable.
#interface {
NSArray *items;
}
#implementation
// The population method.
-(void)populateInstanceVariable
{
NSMutableArray *itemsFromDatabase = [[NSMutableArray alloc] init];
// Sqlite code here, instantiating a model class, assigning values to the instance variables, and adding this to the itemsFromDatabase Array.
self.items = itemsFromDatabase;
[itemsFromDatabase release];
}
// viewDidLoad is calling the method above
-(void)viewDidLoad
{
[self populateInstanceVariable];
[super viewDidLoad];
}
// TableViewDataSource method - cellforIndexPath
- (UITableViewCell *)tableView:(UITableView *)passedInTableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault];
// Load in my model from the instance variable - ***1
MyDataModel *model = [items objectAtIndexPath:indexPath.row];
// Assign the title to the cell from the model data
cell.textLabel.text = model.title;
// This is the part i'm stuck on, releasing here causes a crash!
[model release];
return cell;
}
#end
My question is two fold:
Is what i'm doing to assign data to the instance variable right? and am i managing the memory correctly?
How do i manage the memory for that model item in the tableview datasource? the only way i seem to be able to get it to run smoothly is if i don't release the *model object at all, but that causes leaks surely?
Cheers.
No, you're not managing memory correctly here:
you should use "reusable" UITableViewCells, most UITableView examples show how to do this, and
do not do [model release], you do not "own" the object in this case, you're just referring to it so you must not release it
Here's the typical cellForRowAtIndexPath:
-(UITableViewCell *) tableView:(UITableView *)atableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"CellIdentifier";
// Dequeue or create a cell of the appropriate type.
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier] autorelease];
// settings that do not change with every row
cell.selectionStyle = UITableViewCellSelectionStyleGray;
}
// settings that change with every row
cell.textLabel.text = #"fill in your label here";
return cell;
}
Also, if you're using a DB for your data, you may want to look in to Core Data, Apple's data persistence/management framework, it includes the ability to hook aspects of your data entities directly up to UITableViews.
1) Populate method is correct. Don't forget to set the instance variable to nil in the dealloc. (I suppose you added a property/synthesize as you used the 'self.').
2) Do NOT release the model object. You did not retain, copy or allocated it in that method. By the other hand your initialization of the cell is wrong. Use the following: (Better for performance)
- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *Identifier = #"CellIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:Identifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:Identifier] autorelease];
}
//Other code
}
i have tryed to display my NSMutableArray in a Table View by following a tutorial. It has completley failed for some reason, i think i have a good idea why but cannot get around it, this is my code:
- (void) scoreSystem {
scoreArray = [[NSMutableArray alloc] init];
NSNumber *onescore = [NSNumber numberWithInteger:score];
[scoreArray addObject:onescore];
NSNumber *twoscore = [NSNumber numberWithInteger:score];
[scoreArray addObject:twoscore];
NSNumber *threescore = [NSNumber numberWithInteger:score];
[scoreArray addObject:threescore];
NSNumber *fourscore = [NSNumber numberWithInteger:score];
[scoreArray addObject:fourscore];
NSNumber *fivescore = [NSNumber numberWithInteger:score];
[scoreArray addObject:fivescore];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [scoreArray count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];
}
cell.textLabel.text = [scoreArray objectAtIndex:indexPath.row];
return cell;
}
I think it is because it wont let me link up everything properly in IB, it lets me put the data source and delegate to the Files owner, but then when i drag from the files owner to my view it says 'delegate' instead of 'view', i think its because i am doing it in the 'main window' not VC.
Is there any way round this?
Thanks!
harry.
You want to set the class that your code is in as the tableview's datasource. Create an instance of your class in IB (use the NSObject, and rename its class to YourClass).
This will create an instance of your class that will be available when the nib is decoded.
Then, control-drag from the tableview to your class, and set the datasource.
That's it! You should be able to set breakpoints in your -numberOfRowsInSection: method above, and see it called as soon as the table view comes in view. If you don't, check your connections and check for typos: the runtime is case-sensitive.
Well for some reason someone bumped this old thread. I might as well chime in. The reason this code has problems is because it is trying to set the text property of the cell to a NSNumber.
cell.textLabel.text = [scoreArray objectAtIndex:indexPath.row];
Try this instead:
cell.textLabel.text = [[scoreArray objectAtIndex:indexPath.row] stringValue];
In the following bit of code, I'm setting the table view cell text with a value from the NSMutableArray 'categories' which is a property of my view controller. That works fine.
But when I try the exact same code in another method, it crashes (it compiles without errors or warnings). If I change the following line in the didSelectRowAtIndexPath method:
NSString *categoryName = [categories objectAtIndex:indexPath.row];
to
NSString *categoryName = [[NSString alloc] initWithString:#"test"];
It works... any ideas?
// Customize the appearance of table view cells.
- (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];
}
// Configure the cell.
NSString *categoryName = [categories objectAtIndex:indexPath.row];
cell.textLabel.text = categoryName;
return cell;
}
// Override to support row selection in the table view.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
printf("User selected row %d\n", [indexPath row] + 1);
ButtonsPageViewController *bView = [ButtonsPageViewController alloc];
NSLog(#"created instance of buttonspageviewcontroller");
NSString *categoryName = [categories objectAtIndex:indexPath.row];
NSLog(#"category name set");
bView.selectedCategory = categoryName;
NSLog(#"selected category property set");
[self.navigationController pushViewController:bView animated:YES];
NSLog(#"push view controller");
[bView release];
}
The difference between
NSString *categoryName = [categories objectAtIndex:indexPath.row];
and
NSString *categoryName = [[NSString alloc] initWithString:#"test"];
Is that the first line copies a pointer to the object (retain count does not change) whereas the second one creates a new object (retain count = 1).
In cellForRowAtIndexPath, when you set the text property, it copies or retains the string, so you're fine. In didSelectRowAtIndexPath you are setting a property of ButtonsPageViewController, which I assume is your own code, but perhaps it is not copying or retaining the object.
Also, the line
ButtonsPageViewController *bView = [ButtonsPageViewController alloc];
is going to lead to problems. You need to call init to properly initialize the object. All you've done in that line is allocate memory for it.
In general, it looks like you need to brush up on Retain/Release memory management. That should save you some trouble.
Like benzado says, it's an issue retaining the selectedCategory value in ButtonsPageViewController.
Are you using #property and #synthesize or are you writing your own accessors? If it's the former, you probably need to look at the property declaration attributes. Otherwise, it's probably a retain/release thing in your custom accessor.
The Declared Properties section of The Objective-C 2.0 Programming Laungauge is a good resource for rules of declaring synthesized accessors.