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...
Related
here is the thing. I have a tableview with a variable number of rows. When you tap one of them, a detail view is shown.
Everything is ok if you tapped rows from 0 to 9, 11 to 19, 21 to 29 etc... But it doesn't work for rows number 10, 20, 30 etc... Even a long tap is detected on this rows, so an alert is shown, in order to delete the row if necessary (and I can remove it without problems), but didselectrowatindexpath is Never called.
I think is something involved that the tableview only keeps 10 rows at the same time. But in cellForRowAtIndex everything loads ok, for all rows. I keep all the data in some NSMutableArray, to access the data I need with [indexPath row].
I have already spend some time looking for similar post, but don't find anything like this.
any help will be appreciate. Thanks!
Some code:
// in .h
#import <UIKit/UIKit.h>
#import "PullRefreshTableViewController.h"
#import "DetalleMailViewController.h"
#interface MensajesTableViewController : PullRefreshTableViewController <DetalleMailViewControllerDelegate>
#property (nonatomic, strong) NSMutableArray *mailAuthors;
#property (nonatomic) int row_tabla_delete;
- (IBAction)presentMenu:(id)sender;
#end
//in .m make the synthesize of mailAuthors and some relevant code:
- (void)viewDidLoad
{
[super viewDidLoad];
self.tableView.allowsSelection = YES;
//This gesture works fine
UILongPressGestureRecognizer *lpgr = [[UILongPressGestureRecognizer alloc]
initWithTarget:self action:#selector(presentMenu:)];
lpgr.minimumPressDuration = 0.5; //seconds
lpgr.numberOfTouchesRequired =1;
lpgr.delegate = self;
[self.tableView addGestureRecognizer:lpgr];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *idCell = #"mailTableCell";
MailCell *celda = [tableView dequeueReusableCellWithIdentifier:idCell];
if(celda == nil){
celda = [[MailCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:idCell];
}
celda.mailAuthor.text = [self.mailAuthors objectAtIndex:[indexPath row]];
return celda;
}
-(void) viewWillAppear:(BOOL)animated{
[[self tableView] reloadData];
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
//I really use a segue here, but for the sake of simplicity, I just put a trace, never called for rows 10, 20, 30... The segue works fine the rest of the time...
NSLog(#"tapped %d", [indexPath row]);
}
- (IBAction)presentMenu:(id)sender {
if ([sender state] == UIGestureRecognizerStateBegan){
CGPoint p = [sender locationInView:self.tableView];
NSIndexPath * indexPath = [self.tableView indexPathForRowAtPoint:p];
if (indexPath != nil){
self.row_tabla_delete = [indexPath row];
}
//some code to delete the row
}
}
In MailCell.hI have theUILabelfor mailAuthor as a property, and linked in the storyboard with the UILabel inside theUITableViewCell. In the storyboard, I have aTableViewController linked to my own class "MensajeTableViewController", theUITableViewCellto my "MailCell`".
If there is some need to add some other relevant code, or something else, please Help me.
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.
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.
I have a peculiar problem. I created a normal Objective C class called SampleTable. I then extended UITableView instead of NSObject. Then in the initWithFrame constructor I initialized the table. I also made a NSMutableArray object for the datasource. I also conformed UITableViewDelegate and UITableViewDatasource. I have overridden the necessary methods also.
Now I made an object of this class in another class, and added the object as a subview. The tableView is getting drawn according to the CGRectMake() coordinates I gave to the initWithFrame constructor. But it is not getting populated with the data. I don't know what the problem is.
SampleTable.h
#import <Foundation/Foundation.h>
#interface SampleTable : UITableView {
NSMutableArray *ItemArray;
}
#property (nonatomic,retain) NSMutableArray *ItemArray;
-(NSMutableArray *) displayItemArray;
#end
SampleTable.m
#import "SampleTable.h"
#implementation SampleTable
#synthesize ItemArray;
-(id)initWithFrame:(CGRect)frm {
[super initWithFrame:frm];
self.delegate=self;
self.dataSource=self;
[self reloadData];
return self;
}
-(NSMutableArray *) displayItemArray {
if(ItemArray==nil) {
ItemArray=[[NSMutableArray alloc] initWithObjects:#"1",#"2",#"3",#"4",#"5",nil];
}
return ItemArray;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [ItemArray 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];
[cell autorelease];
}
NSString *temp=[self.ItemArray objectAtIndex:indexPath.row];
cell.textLabel.text = temp;
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"didselect");
}
-(void) dealloc {
[ItemArray release];
[super dealloc];
}
#end
You did never initialize the ItemArray within your code snippet. Remove the displayItemData method and change the initializer towards:
-(id)initWithFrame:(CGRect)frm
{
if ((self = [super initWithFrame:frm])) != nil)
{
self.delegate=self;
self.dataSource=self;
ItemArray=[[NSMutableArray alloc] initWithObjects:#"1",#"2",#"3",#"4",#"5",nil];
[self reloadData];
}
return self;
}
You could also simply call that displayItemArray method within the initializer. I feel that method makes no sense as it stands and hence my recommendation to remove it altogether.
Without trying it myself, I am still pretty confident that you can also get rid of that [self reloadData] within the initializer.
During the -(id)initWithFrame:(CGRect)frm method your code calls -reloadData on the UITableView. At this point in time your ItemArray is not available.
Therefore, when UITableView calls it's delegates -numberOfSectionsInTableView: and -tableView:numberOfRowsInSection: methods to get information on what to display in the view, they return one section with zero rows.
Nothing displayed!
It might be a (one) solution to change your initialization of the ItemArray:
-(NSMutableArray *) displayItemArray {
if(ItemArray==nil) {
ItemArray=[[NSMutableArray alloc] initWithObjects:#"1",#"2",#"3",#"4",#"5",nil];
[self reloadData];
}
return ItemArray;
}
print value of
[self.ItemArray objectAtIndex:indexPath.row];
in NSLog... check wether it prints valid value or not....It seems that array's object releasing somewhere before it..