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.
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.
Looking for help on how to properly load the data for a Grouped Table View. I am using following code to load the data which I think should prepare an array "details" for "cellForRowAtIndexPath". However when I run the program I get the same data for all groups and it happens to be the last row in my data (which is in an NSArray called "Users"). Obviously I am doing something wrong but not sure what... please help.
// Customize the number of rows in the table view.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
NSRange aRange;
aRange.length = 9; aRange.location = 16;
NSString *users = [self.Users objectAtIndex: section];
NSString *subtitle = [NSString stringWithString:[users substringWithRange:aRange]];
details = [[NSArray arrayWithObjects:subtitle, subtitle, subtitle, nil] retain];
return [details count];
}
The data loading routine is as follows:
- (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];
}
// Configure the cell.
UIImage *cellImage = [UIImage imageNamed:#"world.png"];
cell.imageView.image = cellImage;
cell.textLabel.text = [details objectAtIndex:indexPath.row];
return cell;
}
From what you've posted, details array is being reset every time the table views asks its data sources for the number of rows in a section. details is last set when -tableView:numberOfRowsInSection: is last called i.e. for the last section. Once set, the same array provides the data for cell.textLabel.text for all the sections. So you are getting incorrect data.
It would be appropriate to calculate and store all the details arrays in a different array, say detailsArray.
The data can then be accessed them like
[[detailsArray objectAtIndex:indexPath.section] objectAtIndex:indexPath.row]
Another problem with your code is that every time numberOfRowsInSection is called, data is being leaked as details is being reset without the earlier object being released.
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.