So i have this very basic ipad view controller and i was doing some testing with multiple UITableViews in the same view. The issue I was having was when I selected a row, it would throw a EXC_BAD_ACCESS so I turned on the stack logging and found this
*** -[VCTables tableView:didSelectRowAtIndexPath:]: message sent to deallocated instance 0x4c0ad40
Then i started looking at my code and could not figure out for the life of me what is causing it. I have UITableViewDataSource and UITableViewDelegate on my view controller and have all the appropriate code to handle did select row at index. It draws the tables properly, it just doesnt seem to be hitting self as the datasource.
I have tried building the UITableViews in code, and also in Interface builder. I have re-downloaded and installed XCode 3.2.3 SDK 4.0.2 after uninstalling it and restarting. I couldn't for the life of me see what I am missing but after doing the previous, I am convinced now (i guess) that it is a code issue rather than the IDE, I just cant open my eyes wide enough to see the code issue.
Also, this happens with just one table as well. And with 2 tables, it happens no matter which table I select
here is some code:
VCTables.h
#import <UIKit/UIKit.h>
#interface VCTables : UIViewController<UITableViewDelegate,UITableViewDataSource> {
UITableView *table1;
UITableView *table2;
}
#end
VCTables.m
#import "VCTables.h"
#implementation VCTables
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
return 50;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 7;
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *CellIdentifier = [[NSString alloc] initWithString:#"yourCell"];
if(tableView.tag == 2000){
[CellIdentifier release];
CellIdentifier = [[NSString alloc] initWithString:#"myCell"];
}
UITableViewCell *cell = (UITableViewCell*)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
if(tableView.tag == 2000){
[cell.textLabel setText:[NSString stringWithFormat:#"%d",indexPath.row]];
}else{
[cell.textLabel setText:[NSString stringWithFormat:#"%d%d",indexPath.row,indexPath.section]];
}
[CellIdentifier release];
return cell;
}
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
return NO;
}
- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath {
return NO;
}
-(void)tableView:(UITableView*)tableView didSelectRowAtIndexPath:(NSIndexPath*)indexPath {
NSLog(#"selected");
}
- (void)viewDidLoad {
[super viewDidLoad];
table1 = [[UITableView alloc] initWithFrame:CGRectMake(20, 73, 320, 480) style:UITableViewStylePlain];
table1.delegate=self;
table1.dataSource=self;
table1.tag=2000;
[self.view addSubview:table1];
table2 = [[UITableView alloc] initWithFrame:CGRectMake(483, 73, 320, 480) style:UITableViewStylePlain];
table2.delegate=self;
table2.dataSource=self;
table2.tag=2000;
[self.view addSubview:table2];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return YES;
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
- (void)viewDidUnload {
[super viewDidUnload];
}
- (void)dealloc {
[super dealloc];
}
#end
I'm not sure it gets more basic than that. Please tell me the rookie mistake that is so painfully obvious that I must stop posting here. Thanks in advance
message sent to deallocated instance 0x4c0ad40
This error indicates that your VCTables instance has been released. The UITableView still has a reference to it (the delegate property), so it is trying to send a message to a released object. The common term for this is a zombie.
In order to debug, you should look at how the memory management is being done for your VCTables object. Where is it created, and who owns it?
If you can make use of Apple's performance tools, try using the zombies tool to find out where that VCTables object is released.
Related
first off I'm new to iOS development so this may be a simple issue,
I've got the following code
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 1;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] ;
cell.textLabel.text = #"Hello!";
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
NSLog(#"Creating cell for %i:%i", indexPath.section, indexPath.row );
return cell;
}
Now the table shows, but all the rows are blank. And the cells are being created. Not sure if it matters but im not using xib's or story boards so i dont know if its a styling issue.
I'm really in the dark here about what im doing. I've tried to follow a few tutorials and every time i end up at the same place with a blank table.
Thank you for all help! I'm open to ideas and possible tutorials etc.
(This is more of a comment, but it's too big for the comment field.)
I've copied your methods exactly into my .m file, and it works. The rest of the file looks like this:
#interface MYViewController ()
#end
#implementation MYViewController
- (void)viewDidLoad
{
[super viewDidLoad];
UITableView *table = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, 320, 640) style:UITableViewStylePlain];
[table setDataSource:self];
[table setDelegate:self];
[self.view addSubview:table];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
I'm assuming you set the data source, since you're getting the NSLogs to print.
Are you sure that the UITableView is not hidden, does not have a 0 alpha, and is above all other views?
If the data you display has the possibility of changing when you go out and back into the view, I'd advice use of
[self.yourTableViewObject reloadData];
This will call all the delegate methods (getting the sections, individual cells) and repopulate your table view.
I am implementing a swipe to delete functionality in uitableview. I am able to achieve that, but now i wish to display a alert message to confirm delete. I have created the tableview programmatically.
ViewController.h
#interface ViewController : UIViewController<UITableViewDataSource,UITableViewDelegate>
{
UITableView *tView;
NSMutableArray *myArray;
NSIndexPath *lastIndexPath;
}
#property(nonatomic,retain) UITableView *tView;
#property(nonatomic,retain) NSMutableArray *myArray;
#property(nonatomic,retain) NSIndexPath *lastIndexPath;
#end
ViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
self.view.backgroundColor=[UIColor lightGrayColor];
myArray = [[NSMutableArray alloc] initWithObjects:#"1",#"2",#"3",#"4",#"5",#"6",#"7",#"8",#"9",#"10", nil];
// Do any additional setup after loading the view, typically from a nib.
tView = [[UITableView alloc] initWithFrame:CGRectMake(30, 30, 700, 800) style:UITableViewStylePlain];
tView.delegate=self;
tView.dataSource=self;
[self.view addSubview:tView];
[tView release];
}
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return 100.0;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [myArray count];
}
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
// Return YES if you want the specified item to be editable.
return YES;
}
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Confirm Delete" message:#"Are you sure?" delegate:self cancelButtonTitle:#"Cancel" otherButtonTitles:#"Delete", nil];
[alert show];
[alert release];
if (editingStyle == UITableViewCellEditingStyleDelete) {
lastIndexPath = indexPath;
NSLog(#"%#......%d",lastIndexPath,lastIndexPath.row);
}
}
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
if(buttonIndex==0)
{
NSLog(#"cancel");
}
else {
NSLog(#"delete");
[myArray removeObjectAtIndex:lastIndexPath.row]; //memory error
[tView deleteRowsAtIndexPaths:[NSArray arrayWithObject:lastIndexPath] withRowAnimation:UITableViewRowAnimationFade];
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
//..do required thing and return cell
}
I get a memory error in the clickButtonAtIndex method for alert view. I think it is for the lastIndexPath, but the nslog() give the correct value in lastIndexPath.
What am i doing wrong?
I hate it when answers just say "You should't do that"... So I'll explain why you are getting an error and then hopefully appeal to your better judgement as to why you really ought not to do this (from a UI point of view).
You are getting an error because you assign a value you do not own (you have not retained it) to an ivar directly. You probably meant to say self.lastIndexPath.
What happens is that the alert view does not really appear until the next time through the run loop at which point indexPath has been autoreleased and you try to access it through a pointer.
Changing lastIndexPath = indexPath; to self.lastIndexPath = indexPath; should solve the memory issue. (Since you marked the property as retain, assuming you synthesized it and did not write your own handler, when you use the self. prefix the synthesized accessor will retain it for you).
(Incidentally this is why is not a bad idea to name your ivars differently from your properties (e.g. Apple uses <property_name>_, on my site I use m<Property_name> it makes it hard to make this sort of mistake).
OK... But back to why you should not do this....
You user has, at this point, made a recognizable gesture by swiping left and has then pressed a red button marked 'delete'... And now you are going pop an alert and ask them to confirm? Really? OK, if you must...
I have UITableView...when user tap on row, another screen is opened. The problem is, that sometimes, I tap once, but didSelectRowAtIndexPath calls several times. How to prevent that ?
The one case how to reproduce that situation is (you even can try to reproduce that on native iPhone settings):
Tap one row but do not release finger
SLIDE few next rows from left to right or from right to left (not just tap, you should slide) next few rows in different order by other hand
Release finger
You will see that blue selection is on several rows, and what screen will be opened is random
UPDATE:
In didSelectRow I just started new controller, where in viewDidLoad synchronization begin.
And if to reproduce my scenario step by step, than synch can be started several times
- (void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
SecondViewController *secondViewController =
[SecondViewController alloc] init];
[self.navigationController
pushViewController:secondViewController animated:YES];
[secondViewController release];
}
Yes, I find the same situation.
Tap one row but do not release finger.
Keep pressing and moving the finger slightly until the row deselected.
Keep the first finger pressing, and tap the screen some times by another finger.
Release all fingers.
Then you can see didSelectRowAtIndexPath method called several times.
I created a new project for test it, and just used the following code. It was reproduced in every times.
So I think it is a bug of iOS SDK !
#import "SPViewController.h"
#interface SPViewController ()
#property (nonatomic, strong) UITableView *tableView;
#end
#implementation SPViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStylePlain];
self.tableView.delegate = self;
self.tableView.dataSource = self;
[self.view addSubview:self.tableView];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark - UITableViewDataSource
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return 30;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *cellIdentifier = #"cellIdentifier";
UITableViewCell *cell = (UITableViewCell *)[tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if(cell == nil){
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
}
cell.textLabel.text = [NSString stringWithFormat:#"Test Cell %d", indexPath.row];
return cell;
}
#pragma mark - UITableViewDelegate
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
return 66;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
NSLog(#"%s %#", __FUNCTION__, indexPath);
}
#end
I get stuck on the following: I want to use a single cell of a UITableView as a header view for another UITableView. The second table view is in turn an object that has been inherited from a different UITableView class, since it shares it's functionality completely. The only difference in fact is that the second UITableView has a header view (which should be the single celled UITableView).
The interface for the UITableView with the header looks like:
#import <UIKit/UIKit.h>
#import "ScheduleDetailsController.h"
#interface TodayDetailsViewController : ScheduleDetailsController <UITableViewDelegate, UITableViewDataSource> {
UITableView *headerView;
}
#property (nonatomic, retain) UITableView *headerView;
#end
And the implementation like:
#import "TodayDetailsViewController.h"
#implementation TodayDetailsViewController
#synthesize headerView;
- (void)loadView {
[super loadView];
self.headerView = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, 320, 44)];
self.headerView.backgroundColor = [UIColor clearColor];
self.headerView.opaque = NO;
self.headerView.delegate = self;
self.headerView.dataSource = self;
// This is a UITableView defined as a property in the parent class
self.scheduleDetailsTable.tableHeaderView = self.headerView;
}
...
#pragma mark -
#pragma mark Table View Data Source Methods
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
if (tableView == self.scheduleDetailsTable.tableHeaderView)
return 1;
else
return [self.scheduleDetailsTable numberOfSections];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (tableView == self.scheduleDetailsTable.tableHeaderView)
return 1;
else
return [self.scheduleDetailsTable numberOfRowsInSection:section];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
if (tableView == self.scheduleDetailsTable.tableHeaderView) {
...
return cell;
} else
return [self.scheduleDetailsTable cellForRowAtIndexPath:indexPath];
}
#end
I get no errors when I compile and run this, but then I also don't see anything, i.e. the view stays empty. I have also tried it with replacing the header view by a UIView and then everything works like expected (i.e. the table view in the parent class is implemented correctly). I'm probably just missing something obvious, but I just can't figure it out at the moment. If someone could spot the error or could give some advice I would be very thankful.
Not for nothing, but do you really need to create a whole UITableViewController and UITableView just to use a UITableViewCell as a header? A UITableViewCell is a subclass of UIView, after all.
This works, more or less, (had to make the labels not opaque myself; I guess the UITableView normally handles that):
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
UITableViewController *testTableViewController = [[UITableViewController alloc] initWithStyle:UITableViewStylePlain];
UITableViewCell *tableViewCell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:NULL];
tableViewCell.textLabel.text = #"Test";
tableViewCell.textLabel.opaque = NO;
tableViewCell.detailTextLabel.text = #"123";
tableViewCell.detailTextLabel.opaque = NO;
testTableViewController.tableView.tableHeaderView = tableViewCell;
[tableViewCell release];
// self.window is initilized and hooked up in MainMenu.xib
self.window.rootViewController = testTableViewController;
[self.window makeKeyAndVisible];
[testTableViewController release];
return YES;
}
For that matter, if the above wont meet your needs why not just bump everything down in your table view by one section, and use the very top section as the "header":
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return ([super numberOfSectionsInTableView:tableView] + 1);
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if (section == 0) {
return 1;
} else {
return [super tableView:tableView numberOfRowsInSection:(section + 1)];
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.section == 0) {
// ***** Build and return your header cell *****
} else {
NSIndexPath *newIndexPath = [NSIndexPath indexPathForRow:indexPath.row inSection:(indexPath.section + 1)];
return [super tableView:tableView cellForRowAtIndexPath:newIndexPath];
}
}
Repeat as necessary for all the other data source delegates.
Just a thought...
Well, I solved the problem by replacing the return statements of the delegate methods in my original posting from
return [self.scheduleDetailsTable cellForRowAtIndexPath:indexPath];
to
return [super tableView:tableView cellForRowAtIndexPath:indexPath];
And similarly for the other delegate methods. Now everything works like expected. Thanks to peterjb for using this syntax in his response to my original question.
I know this issue being mentioned before, but resolutions there didn't apply. I'm having a UINavigationController with an embedded UITableViewController set up using IB. In IB the UITableView's delegate and dataSource are both set to my derivation of UITableViewController. This class has been added using XCode's templates for UITableViewController classes. There is no custom UITableViewCell and the table view is using default plain style with single title, only.
Well, in simulator the list is rendered properly, with two elements provided by dataSource, so dataSource is linked properly. If I remove the outlet link for dataSource in IB, an empty table is rendered instead.
As soon as I tap on one of these two items, it is flashing blue and the GDB encounters interruption in __forwarding__ in scope of a UITableView::_selectRowAtIndexPath. It's not reaching breakpoint set in my non-empty method didSelectRowIndexPath. I checked the arguments and method's name to exclude typos resulting in different selector.
I recently didn't succeed in whether delegate is set properly, but as it is set equivalently to dataSource which is getting two elements from the same class, I expect it to be set properly. So, what's wrong?
I'm running iPhone/iPad SDK 3.1.2 ... but tried with iPhone SDK 3.1 in simulator as well.
EDIT: This is the code of my UITableViewController derivation:
#import "LocalBrowserListController.h"
#import "InstrumentDescriptor.h"
#implementation LocalBrowserListController
- (void)viewDidLoad {
[super viewDidLoad];
[self listLocalInstruments];
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
- (void)viewDidUnload {
[super viewDidUnload];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [entries 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];
if ( ( [entries count] > 0 ) && ( [indexPath length] > 0 ) )
cell.textLabel.text = [[[entries objectAtIndex:[indexPath indexAtPosition:[indexPath length] - 1]] label] retain];
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if ( ( [entries count] > 0 ) && ( [indexPath length] > 0 ) )
{
...
}
}
- (void)dealloc {
[super dealloc];
}
- (void) listLocalInstruments {
NSMutableArray *result = [NSMutableArray arrayWithCapacity:10];
[result addObject:[InstrumentDescriptor descriptorOn:[[NSBundle mainBundle] pathForResource:#"example" ofType:#"idl"] withLabel:#"Default 1"]];
[result addObject:[InstrumentDescriptor descriptorOn:[[NSBundle mainBundle] pathForResource:#"example" ofType:#"xml"] withLabel:#"Default 2"]];
[entries release];
entries = [[NSArray alloc] initWithArray:result];
}
#end
Apple's documentation says that didSelectRowAtIndexPath:index will not be invoked when selectRowAtIndexPath:indexPath is called. To call didSelectRowAtIndexPath use the following:
[[tableView delegate] tableView:tableView didSelectRowAtIndexPath:index];
This basically invokes the delegate.
Try didSelectRowAtIndexPath. The selector as you typed it was missing the word "At" in the selector name.
Are you calling
- (void)selectRowAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated scrollPosition:(UITableViewScrollPosition)scrollPosition
If you are, then that will not call the delegate methods tableView:willSelectRowAtIndexPath: or tableView:didSelectRowAtIndexPath: You will have to call them yourself.
See the UITableView Reference.
For what it's worth I have not seen any difference in the table view behavior in the 3.0.x or 3.1.x releases.
Well, after trying to retain the delegate of UITableView instance just to check for leaking memory with success, I investigated on that issue and stumbled over some tutorials on how to combine views and controllers using detached NIBs as I do here. This tutorial finally made me do the trick:
Combining View Controllers
Focusing on error in detail there were two UITableViewControllers ... one in main NIB set as root controller for a tabbed Navigation controller and a second time in referenced NIB used to provide a raw UITableView instance instead.