Where/If to release properties in a UITableViewCell - iphone

EDIT: Ok, so i figured out how to remedy my original problem, but i'm not sure if this is the best way.
My new question is, say I have a subclass of UITableViewCell with the following property declaration in the header:
#property (nonatomic, retain) IBOutlet UILabel *levelLabel;
This is connected in IB. Is it ok to not release this in dealloc, and not release it at all? This is the only way I can figure out to get it to work, without giving me an exc_bad_access error. Before, it called dealloc when the tableviewcell went off the screen but then it still needed it. where do i release stuff, or does it take care of that for me?
Original Title: Memory leak in UITableView and exc_bad_access
Ok, I am confused. I was following along with this tutorial online making custom UITableViewCells. I made one, and i did everything like the tutorial told me. My UITableViewCell subclass contains 3 UILabels and 3 UIButtons, and has all of them defined as properties and connected in IB. I need them to be available to the class because i need to know when the buttons are pressed and be able to change the text. When I run the app, i start scrolling and after a few seconds it crashes, with exc_bad_access in main (no output in the console). But when I run the app in instruments with NSZombieEnabled, it does not crash at all, and runs just fine. However, since instruments shows you the allocations, i can see them going up very quickly, especially as I scroll. I dont know if this is all allocations, or if these are being released, but still it seems too fast.
Here is PointCoordinatesCell.h (my custom cell):
#import <UIKit/UIKit.h>
#interface PointCoordinatesCell : UITableViewCell
#property (nonatomic, retain) IBOutlet UILabel *levelLabelLabel;
#property (nonatomic, retain) IBOutlet UILabel *levelLabel;
#property (nonatomic, retain) IBOutlet UILabel *levelDescriptionLabel;
#property (nonatomic, retain) IBOutlet UIButton *beginningButton;
#property (nonatomic, retain) IBOutlet UIButton *developingButton;
#property (nonatomic, retain) IBOutlet UIButton *secureButton;
#end
PointCoordinatesCell.m:
#import "PointCoordinatesCell.h"
#implementation PointCoordinatesCell
#synthesize levelLabel, levelLabelLabel, levelDescriptionLabel, beginningButton, developingButton, secureButton;
- (void)dealloc{
[super dealloc];
[levelLabel release];
[levelLabelLabel release];
[levelDescriptionLabel release];
[beginningButton release];
[developingButton release];
[secureButton release];
}
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
// Initialization code
}
return self;
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated
{
[super setSelected:selected animated:animated];
// Configure the view for the selected state
}
#end
RootViewController.h has nothing in it other than a class declaration and standard imports. No variables or methods defined. It subclasses UITableViewController.
RootViewController.m:
#import "RootViewController.h"
#import "StatesAppDelegate.h"
#import "PointCoordinatesCell.h"
#implementation RootViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem;
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning]; // Releases the view if it doesn't have a superview
// Release anything that's not essential, such as cached data
}
#pragma mark Table view methods
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
// Customize the number of rows in the table view.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 50;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath;
{
return 293;
}
// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"PointCoordinatesCell";
PointCoordinatesCell *cell = (PointCoordinatesCell *) [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:#"PointCoordinatesCell" owner:self options:nil];
for (id currentObject in topLevelObjects){
if ([currentObject isKindOfClass:[UITableViewCell class]]){
cell = (PointCoordinatesCell *) currentObject;
break;
}
}
}
//cell.capitalLabel.text = [capitals objectAtIndex:indexPath.row];
//cell.stateLabel.text = [states objectAtIndex:indexPath.row];
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// Navigation logic may go here. Create and push another view controller.
// AnotherViewController *anotherViewController = [[AnotherViewController alloc] initWithNibName:#"AnotherView" bundle:nil];
// [self.navigationController pushViewController:anotherViewController];
// [anotherViewController release];
}
- (void)dealloc {
[super dealloc];
}
#end

It seems you are doing some complicated casting in your cellForRowAtIndexPath method. I do not think this is necessary. It does not seem logical to me to check for an object of class UITableViewCell and then cast it into a custom cell. The cell in your nib should already be a custom cell.
In the Apple sample the loading of the cell is much more straight forward. You link your custom cell to an IBOutlet in your view controller and then do this:
CustomCell *cell = (CustomCell *) [aTableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
[[NSBundle mainBundle] loadNibNamed:#"CustomCell" owner:self options:nil];
cell = customTableViewCell;
self.customTableViewCell = nil;
// etc.
}

No, it is not okay to not release the label. You declared the property with 'retain'-specifier. That means you will have to release it at least in dealloc (A safe way to do that is: self. levelLabel = nil;).
As you've noticed the memory consumption will raise during scrolling if you don't release the objects (memory leaks!).
You'll have to tell where the exc_bad_access error occurs so that we can help you ...

Related

Label in CustomCell stays nil

I created a custom UITableViewCell with an UILabel in it.
In the cellForRowAtIndexPath method I initialise the custom cell and give the UILabel a value.
While the custom cell is loaded (the heights of the cells are higher than default cells), I can't seem to give the UILabel a value.
SBDownloadCell.h
#import <UIKit/UIKit.h>
#interface SBDownloadCell : UITableViewCell
#property (weak, nonatomic) IBOutlet UILabel *title;
#property (weak, nonatomic) IBOutlet UIProgressView *progressbar;
#property (weak, nonatomic) IBOutlet UILabel *details;
- (IBAction)pause:(id)sender;
#end
SBDownloadCell.m
#import "SBDownloadCell.h"
#implementation SBDownloadCell
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
// Initialization code
}
return self;
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated
{
[super setSelected:selected animated:animated];
// Configure the view for the selected state
}
- (IBAction)pause:(id)sender {
}
#end
SBViewController.m
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *simpleTableIdentifier = #"SBDownload";
SBDownloadCell *cell = [tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil) {
cell = [[SBDownloadCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:simpleTableIdentifier];
}
SBDownload *dwnld = [[SBDownload alloc] init];
dwnld = [SBdownloads objectAtIndex:indexPath.row];
//cell.title.text = [dwnld name];
cell.title.text = #"Test";
cell.progressbar.progress = [dwnld percentDone];
cell.details.text = [NSString stringWithFormat:#"%f MB of %f MB completed, %#", [dwnld completed], [dwnld length], [dwnld eta]];
return cell;
}
Storyboard
I break just after cell.title.text = #"Test"; and still this is what I see:
What could it be?
note: i use Xcode DP-5 with iOS7
I see your properties are marked with IBOutlet, which means you have an Interface Builder file (either xib or storyboard) with your table view cell. Make sure to give the correct cell identifier in Interface Builder to the prototype cell in your table view. You should not be calling initWithStyle:. If dequeueReusableCellWithIdentifier: returns nil when using a UITableViewController and storyboards, this means incorrect setup, as dequeueReusableCellWithIdentifier: should always return a cell (it creates the new one if it has to).
To elaborate a bit further, when using xibs or storyboards, a table view cell's initWithStyle: will never be called. When a nib is loaded, the correct init method is initWithCoder:.
The problem is in static NSString *simpleTableIdentifier = #"SBDownload";. In your storyboard, you have set up the identifier as SBDownloadCell.
You need to allocate your UILabels and UIProgressView. Right now you set properties for them, but in your -initWithStyle method you need to call things like
self.title = [[UILabel alloc] initWithFrame:etc...];
If you do this for each of your properties on SBDownloadCell, the labels should be allocated properly.
If you custom your prototype cells in Storyboard, you need to present your TableViewController with segue in storyboard, or if you you need to present the TableViewController by programming, you need to use instantiateViewControllerWithIdentifier: method,
- (IBAction)showTableView:(id)sender
{
UIStoryboard *storybaord = [UIStoryboard storyboardWithName:#"Main_iPad" bundle:nil];
LJTableViewController *vc = (LJTableViewController*)[storybaord
instantiateViewControllerWithIdentifier:#"TableViewControllerID"];
[self presentViewController:vc animated:YES completion:nil];
}
if you initiate TableViewController with [TableViewContrller new], this will let you get an nil label in tableView:cellForRowAtIndexPath mehtod.
I encounter same problem but my identifier is correct for creating custom cell and show data in IBOutlets custom lable, but after cell allocation lables values are null. So for this I need to add method of tableview for showing lables values
- (void)tableView:(UITableView *)tableView willDisplayCell:(DropViewTableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
cell.lblHSNCode.text = objClass.HSNCode;
}

How do I add a UITableView to a parent that contains other objects?

This is essentially the layout I want:
The UITableView at the bottom should accomodate comments to a specific post, adding a row for each comment.
The UITableView at the bottom is wired to commentTable; all other elements are wired accordingly as well.
When I build and run, no errors, but I only see one empty table cell below the post.
I know there's something missing in loading/passing data to my table, but I wonder if someone can give me a direction on how to make this work.
DetailViewController.h
#import <UIKit/UIKit.h>
#interface DetailViewController : UIViewController {
IBOutlet UIImageView *postThumbView;
IBOutlet UILabel *postTextLabel;
IBOutlet UIImageView *postAuthorPictureView;
IBOutlet UILabel *postAuthorNameLabel;
IBOutlet UILabel *postTimestampLabel;
IBOutlet UIScrollView *scroller;
IBOutlet UITableView *commentTable;
}
#property (strong, nonatomic) id detailItem;
#end
DetailViewController.m
#import "DetailViewController.h"
#interface DetailViewController ()
- (void)configureView;
#end
#implementation DetailViewController;
- (void)viewDidLoad
{
[super viewDidLoad];
[self configureView];
}
- (void)configureView
{
if (self.detailItem) {
NSDictionary *post = self.detailItem;
NSString *postText = [post objectForKey:#"post_text"];
...
postTextLabel.text = postText;
...
}
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSDictionary *post = self.detailItem;
NSDictionary *commentThread = [post objectForKey:#"comment"];
return commentThread.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"commentCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
NSDictionary *post = self.detailItem;
NSDictionary *commentThread = [post objectForKey:#"comment"];
NSString *commentText = [commentThread objectForKey:#"comment_text"];
NSString *commentAuthorName = [commentThread objectForKey:#"comment_author_name"];
cell.textLabel.text = commentText;
cell.detailTextLabel.text = [NSString stringWithFormat:#"by %#", commentAuthorName];
return cell;
}
#end
It may be that the table view delegate method's you've written aren't being called. The first thing you should do is set breakpoints inside these methods, run your app, and see if they are being called.
If they're not being called, you may have failed to set your delegate. In this case, it appears that you are not using a discrete UITableViewController, rather you are attempting to have your DetailViewController supply the necessary information for the tableView to work as expected.
First, you need to conform your DetailViewController to the UITableViewDelegate protocol:
#interface DetailViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>
Second, you need to actually set the delegate #property of your UITableView. You can do this in interface builder (select the tableview, right click, drag it's delegate property to connect to your DetailViewController, which may or may not be File's Owner). If you'd rather do it in code, you just need to call (early in the VC's life, in viewDidLoad, for example):
self.tableView.delegate = self;
self.tableView.datasource = self;
So... assuming your delegate is all wired up properly, you should then go back and test those breakpoints to see if the table view's methods are being called. If they are being called, the next step would be to evaluate the variables when the breakpoints are called, examine for example if the numbers being return in numberOfRowsInSection and the values in cellForRowAtIndexPath match what you anticipate.
You need to declare your view controller as the delegate and data source for the table view
change this line in your .h file
#interface DetailViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>
Then in your viewDidLoad
commentTable.dataSource = self;
commentTableView.delegate = self;
[commentTableView reloadData];
[self configureView];
You can also look in the story board, and connect the outlets n the same way you connected commentTable to your UITableView, but by dragging in the opposite direction and selecting data source and delegate

Passing data from plist to detail view

I have a plist (array of dictionaries) which populates a table view and works properly. I use Xcode 4 with storyboards.
Now I've created a detail view from a regular UIViewController and of course I want the selected name to be displayed in the nameLabel in the detail view. But I can't make the right connection. This is my code so far:
WineObject.m:
#import "WineObject.h"
#implementation WineObject
#synthesize libraryContent, libraryPlist;
- (id)initWithLibraryName:(NSString *)libraryName {
if (self = [super init]) {
libraryPlist = libraryName;
libraryContent = [[NSArray alloc] initWithContentsOfFile:[[NSBundle mainBundle]
pathForResource:libraryPlist ofType:#"plist"]];
}
return self;
}
- (NSDictionary *)libraryItemAtIndex:(int)index {
return (libraryContent != nil && [libraryContent count] > 0 && index < [libraryContent count])
? [libraryContent objectAtIndex:index]
: nil;
}
- (int)libraryCount {
return (libraryContent != nil) ? [libraryContent count] : 0;
}
- (void) dealloc {
if (libraryContent) [libraryContent release];
[super dealloc];
}
#end
ViewController.h:
#import <UIKit/UIKit.h>
#class WineObject;
#interface WinesViewController : UITableViewController {
WineObject *wine;
}
#end
ViewController.m:
#import "WinesViewController.h"
#import "WineObject.h"
#import "WineCell.h"
#interface WinesViewController ()
#end
#implementation WinesViewController
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
}
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
#pragma mark - Table view data source
- (void)viewWillAppear:(BOOL)animated {
wine = [[WineObject alloc] initWithLibraryName:#"Wine"];
self.title = #"Vinene";
[self.tableView deselectRowAtIndexPath:[self.tableView indexPathForSelectedRow] animated:YES];
}
- (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 [wine libraryCount];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"wineCell";
WineCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
// Configure the cell...
cell.nameLabel.text = [[wine libraryItemAtIndex:indexPath.row] valueForKey:#"Name"];
cell.districtLabel.text = [[wine libraryItemAtIndex:indexPath.row] valueForKey:#"District"];
cell.countryLabel.text = [[wine libraryItemAtIndex:indexPath.row] valueForKey:#"Country"];
cell.bottleImageView.image = [UIImage imageNamed:[[wine libraryItemAtIndex:indexPath.row] valueForKey:#"Image"]];
cell.flagImageView.image = [UIImage imageNamed:[[wine libraryItemAtIndex:indexPath.row] valueForKey:#"Flag"]];
cell.fyldeImageView.image = [UIImage imageNamed:[[wine libraryItemAtIndex:indexPath.row] valueForKey:#"Fylde"]];
cell.friskhetImageView.image = [UIImage imageNamed:[[wine libraryItemAtIndex:indexPath.row] valueForKey:#"Friskhet"]];
cell.garvesyreImageView.image = [UIImage imageNamed:[[wine libraryItemAtIndex:indexPath.row] valueForKey:#"Garvesyre"]];
return cell;
}
#pragma mark - Table view delegate
#end
WineCell.h:
#import <UIKit/UIKit.h>
#interface WineCell : UITableViewCell
#property (nonatomic, strong) IBOutlet UILabel *nameLabel;
#property (nonatomic, strong) IBOutlet UILabel *districtLabel;
#property (nonatomic, strong) IBOutlet UILabel *countryLabel;
#property (nonatomic, strong) IBOutlet UIImageView *bottleImageView;
#property (nonatomic, strong) IBOutlet UIImageView *flagImageView;
#property (nonatomic, strong) IBOutlet UIImageView *fyldeImageView;
#property (nonatomic, strong) IBOutlet UIImageView *friskhetImageView;
#property (nonatomic, strong) IBOutlet UIImageView *garvesyreImageView;
#end
Are you using a XIB for interface or generating it programmatically?
If you are using a XIB, the issue is that you aren't loading it up:
Change
winesDetailViewController = [[WinesDetailViewController alloc] init];
To
winesDetailViewController = [[WinesDetailViewController alloc] initWithNibName:#"YourNibNameHere" bundle:nil];
Or, if you are generating it programmatically, you must first set nameLabel or it will be nil. #synthesize doesn't set the variable, it simply generates getters and setters so that you can set it from outside.
Inside your viewDidAppear: (or better yet inside your init) add:
self.nameLabel=[[UILabel alloc] initWithFrame:CGRectMake(100,100,100,100)];
EDIT: If you are using Storyboards, it appears that you have to do the following.
Storyboards are all about relationships. Inside the story board editor, you add buttons and tell them which view controller they connect to. The same idea applies to TableView Cells. You can add a prototype table view cell (and customize it) and assign a relationship to it. The relationship you will want to give it is your detail view.
1.) Subclass UITableViewCell and give it a property that is the dictionary that you are trying to send to the detail view
2.) When creating cells (cellForRowAtIndexPath:) you will need to make sure to dequeue your custom cell and assign your dictionary to the property that you gave it.
3.) Make sure that your detail view has the identifier: DetailView
4.) Inside the table view controller, add the following code:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if([segue.identifier isEqualToString:#"DetailView"])
{
//This works because by the time prepareForSeque: is called, the navigationController has loaded the new view
((DetailView *)[[self.navigationController viewControllers] objectAtIndex:0]).dataProperty=((MyCustomTableViewCell *)sender).dataProperty;
}
}
That ought to do it!

Use IBAction from UIButton inside custom cell in main view controller

I have created a custom cell with its own .m, .h and .xib file. In the cell, I have a UIButton that I added to the xib in IB.
I can receive the IBAction from the UIButton in this custom cell's .m, but really, I'd like to be forwarding that button press to the main view .m that is hosting the table (and so custom cell) and use an action there.
I've spent the last 4 hours attempting various ways of doing this - should I be using NSNotificationCenter? (I've tried Notifications lots but can't get it to work and not sure if i should be persevering)
You need to use delegate in .h file of cell.
Declare the delegate like this
#class MyCustomCell;
#protocol MyCustomCellDelegate
- (void) customCell:(MyCustomCell *)cell button1Pressed:(UIButton *)btn;
#end
then declare field and property
#interface MyCustomCell:UItableViewCell {
id<MyCustomCellDelegate> delegate;
}
#property (nonatomic, assign) id<MyCustomCellDelegate> delegate;
#end
in .m file
#synthesize delegate;
and in button method
- (void) buttonPressed {
if (delegate && [delegate respondToSelector:#selector(customCell: button1Pressed:)]) {
[delegate customCell:self button1Pressed:button];
}
}
Your view controller must adopt this protocol like this
.h file
#import "MyCustomCell.h"
#interface MyViewController:UIViewController <MyCustomCellDelegate>
.....
.....
#end
in .m file in cellForRow: method you need add property delegate to cell
cell.delegate = self;
and finally you implement the method from protocol
- (void) customCell:(MyCustomCell *)cell button1Pressed:(UIButton *)btn {
}
Sorry for my english, and code. Wrote it from my PC without XCODE
I had the same problem, and I solved it by subclassing the cell into it's own class, and put the buttons there as outlets, and filling the cell with data from the model, while using a method that returns the cell we're currently viewing.
For example, if you had a Person class, and each person had a name, a surname, and a number of friends. And each time you clicked a button in the cell, the number of friends for a specific person would increase by 1.
_______________DATA SOURCE___________________________
#import <Foundation/Foundation.h>
#interface Person : NSObject
#property (nonatomic, strong) NSString *name;
#property (nonatomic, strong) NSString *comment;
#property (nonatomic) NSInteger numberOfFriends;
+(instancetype)personWithName:(NSString *)aName Surname:(NSString *)aSurname;
#end
#import "Person.h"
#implementation Person
+(instancetype)personWithName:(NSString *)aName Surname:(NSString *)aSurname{
Person *person = [[Person alloc] init];
[person setName:aName];
[person setSurname:aSurname];
[person setNumberOfFriends:0];
return person;
}
#end
_____________________PERSON CELL________________________
#import <UIKit/UIKit.h>
#interface PersonCell : UITableViewCell
#property (strong, nonatomic) IBOutlet UILabel *friendsNum;
#property (strong, nonatomic) IBOutlet UIButton *friendsBtn;
#property (strong, nonatomic) IBOutlet UILabel *nameLabel;
#property (strong, nonatomic) IBOutlet UILabel *surnameLabel;
#end
Personally I created a private NSArray to hold the names of my Person objects, and a private NSMutableDictionary to hold my Person objects, and I set the keys to be the names of the people.
_____________________PERSON TABLE VIEW________________________
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
PersonCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
NSString *name = [peopleNames objectAtIndex:indexPath.row];
Person *person = [people objectForKey:name];
if(cell == nil)
{
cell = [[PersonCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
// Configure the cell...
cell.nameLabel.text = person.name;
cell.surname.Label.text = person.surname
[cell.friendsButton addTarget:self action:#selector(moreFriends:) forControlEvents:UIControlEventTouchUpInside];
cell.friendsNum.text = [NSString stringWithFormat:#"%i", person.numberOfFriends];
return cell;
}
- (IBAction)moreFriends:(id)sender {
UIButton *btn = (UIButton *)sender;
PersonCell *cell = [self parentCellForView:btn];
Person *person = [people objectForKey:cell.nameLabel.text];
person.numberOfFriends++;
[self.tableView reloadData];
}
-(PersonCell *)parentCellForView:(id)theView
{
id viewSuperView = [theView superview];
while (viewSuperView != nil) {
if ([viewSuperView isKindOfClass:[PersonCell class]]) {
return (PersonCell *)viewSuperView;
}
else {
viewSuperView = [viewSuperView superview];
}
}
return nil;
}
I would recommend using a delegate.
You might want to just add a selector for the button in your view controller which has your tableview.
in your cellForIndexPath function
[yourCell.button addTarget:self action:#selector(customActionPressed:) forControlEvents:UIControlEventTouchDown];
then handle the button press in your "customActionPressed:(id) sender" method
//Get the superview from this button which will be our cell
UITableViewCell *owningCell = (UITableViewCell*)[sender superview];
//From the cell get its index path.
NSIndexPath *pathToCell = [myTableView indexPathForCell:owningCell];
//Do something with our path
This may be a better solution for you unless there are more factors that you haven't listed.
I have a tutorial which is might explain more http://www.roostersoftstudios.com/2011/05/01/iphone-custom-button-within-a-uitableviewcell/
Why not create a delegate (using #protocol) for your custom cell. You can then designate the main view as each cell's delegate and process the action appropriately.

NSArray causing memory leak when simulated low memory warning

I have made a sample project that reproduces this issue which contains two views:
root header:
#import <UIKit/UIKit.h>
#import "view2.h"
#interface RootViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>{
view2 *secondView;
UITableView *table;
NSArray *array;
}
#property (nonatomic, retain) view2 *secondView;
#property (nonatomic, retain) IBOutlet UITableView *table;
#property (nonatomic, retain) NSArray *array;
#end
root main:
#import "RootViewController.h"
#implementation RootViewController
#synthesize table, array, secondView;
- (void)viewDidLoad
{
[super viewDidLoad];
if(self.array == nil){
self.array = [NSArray arrayWithObjects:#"1", #"2", #"3", #"4", nil];
}
}
- (void)didReceiveMemoryWarning
{
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
}
- (void)viewDidUnload
{
[super viewDidUnload];
table = nil;
array = nil;
secondView = nil;
}
- (void)dealloc
{
[table release];
[array release];
[secondView release];
[super dealloc];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [array 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] autorelease];
}
cell.textLabel.text = [array objectAtIndex:indexPath.row];
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if (secondView == nil) {
secondView = [[view2 alloc] init];
}
[self.navigationController pushViewController:secondView animated:YES];
}
#end
view2 simple contains a label with the text "view 2" for identification purposes.
All this code is doing in the root controller is creating an array with the values 1,2,3,4 and binding this text as rows to the table, clicking any row pushes view 2 onto the stack.
if you load up the app in the simulator using the leaks instruments tool, click on any row so that view2 is displayed and then simulate an error warning the following leaks appear:
image
for the line:
self.array = [NSArray arrayWithObjects:#"1", #"2", #"3", #"4", nil];
this is causing me a lot of problems in my main app as i am using arrays to provide data in tables all over the place.
i have tried various ways to fix this such as declaring the array in different ways to no avail.
any help is greatly appreciated!
thanks
In viewDidUnload you're mixing up property vs. direct ivar access.
array = nil simply sets the ivar to nil without using the synthesized accessor method. You have to use the dot notation: self.array = nil;
This way the accessor setArray: is used which handles memory management for you.
Mixing up ivars and properties is a frequent problem amongst Objective-C beginners. The confusion can easily be prevented by always using different names for properties and ivars:
#synthesize array = _array;
You can just leave out the ivar declaration in the class's #interface or name it as in the #synthesize directive.