UITableViewCell Leak when deleting and saving the cell - objective-c

I noticed one of my custom UITableViewCell is leaking. It is not getting released on performing edit, remove bottom cell and tap on Save. This is my top cell in the table (I call it add cell). Once my table view is in edit mode, Add cell is shown up with all the other cells shown underneath with delete mode on. Now, after performing the delete operation and saving the data, Add Cell still hang around even if dealloc on my table view gets called.
I am attaching my code and instrument screenshot (I've removed first line (Malloc call) from the screenshot. To me, it appears to be something wrong in iOS internal handling. Please advise.
- (UITableViewCell *)tableView:(UITableView *)iTableView cellForRowAtIndexPath:(NSIndexPath *)iIndexPath {
MyTableViewCell *aCell = nil;
NSString *aCellType;
if (iIndexPath.row == 0 && self.inEditMode) {
aCellType = kMyAddCell;
aCell = (MyAddCell *)[iTableView dequeueReusableCellWithIdentifier:aCellType];
if (!aCell) {
aCell = [[MyAddCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:aCellType];
}
aCell.isGroupedView = YES;
aCell.delegate = self;
aCell.textLabel.text = #“Add More";
self.tableView.allowsSelectionDuringEditing = YES;
} else {
aCellType = kMyDefaultCell;
aCell = (MyTableViewCell *)[iTableView dequeueReusableCellWithIdentifier:aCellType];
if (!aCell) {
aCell = [[MyTableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:aCellType];
}
NSInteger anIndex = iIndexPath.row;
if (self.inEditMode) {
anIndex = anIndex - 1;
}
aCell.textLabel.text = #“Name";
aCell.selectionStyle = UITableViewCellSelectionStyleNone;
}
return aCell;
}
- (UITableViewCellEditingStyle)tableView:(UITableView *)aTableView editingStyleForRowAtIndexPath:(NSIndexPath *)iIndexPath {
return (iIndexPath.row == 0 && self.inEditMode) ? UITableViewCellEditingStyleNone : UITableViewCellEditingStyleDelete;
}
- (BOOL)tableView:(UITableView *)iTableView canEditRowAtIndexPath:(NSIndexPath *)iIndexPath {
return YES;
}
- (void)tableView:(UITableView *)iTableView commitEditingStyle:(UITableViewCellEditingStyle)iEditingStyle forRowAtIndexPath:(NSIndexPath *)iIndexPath {
if (iEditingStyle == UITableViewCellEditingStyleDelete) {
[self setSaveModeNavigationBar];
[self.tableView beginUpdates];
[self.enabledUsers removeObjectAtIndex:(self.inEditMode) ? iIndexPath.row - 1 : iIndexPath.row];
[self.tableView deleteRowsAtIndexPaths:#[iIndexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
[self.tableView endUpdates];
}
}
- (void)setEditing:(BOOL)iEditing animated:(BOOL)iAnimated {
[super setEditing:iEditing animated:iAnimated];
[self.navigationItem setHidesBackButton:YES animated:NO];
self.headerViewLabel.text = #“Edit Me";
UIBarButtonItem *aDoneButton = [[UIBarButtonItem alloc] initWithTitle:#“Done"
style:UIBarButtonItemStylePlain
target:self
action:#selector(cancelEditing)];
[self.navigationItem setRightBarButtonItem:aDoneButton];
[self.navigationItem setLeftBarButtonItem:nil animated:YES];
self.inEditMode = YES;
[self.tableView reloadData];
}
Code that gets called once I tap on Save button:
self.requestHandler = [[MyRequestHandler alloc] initWithEndPoint:myEndPoint body:aPostBody successHandler:^(NSDictionary *iResponse) {
[aBlockSelf addNoDataOverlay];
} andErrorHandler:^(NSString *iMessage, NSString *iKey, NSInteger iErrorCode, BOOL iIsNetworkError) {
[aBlockSelf addNoDataOverlay];
}];
[self.requestHandler executeRequest];
Instrument Allocation Stack:

Apparently it turned out to be a known issue being tracked in this thread. Tried the same steps in iPhone 5S (iOS 8) and issue has been resolved there.

Related

Reload data in tableview while scrolling with API

how to reload the data in the table while scrolling?
If i have a url "http:.......&start=0&limit=50".
How to reload the data in table with incrementing the start and limit value with 50.
Can anyone find a solution for this?
You need to implement the load more functionality in the table view for this.In order to do so, you need to track the table view's index while it is scrolling once table reaches the last cell you can call the api with increased page number and add the new records to the array once you get the positive response from the api.
See the below code for more understanding.
Variable initialization
#interface ExampleListVC (){
BOOL hasNextPage,isLoadingNextPage;
int currentPage;
NSMutableArray *arrTableData;
}
#end
In viewDidLoad method
arrTableData = [[NSMutableArray alloc]init];
currentPage=-1;
hasNextPage = YES;
In tableView numberOfRowsInSection method
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
int count = (int)arrTableData.count;
if (hasNextPage)
{
count++;
}
return count;
}
In cellForRowAtIndexPath method
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = nil;
if (indexPath.row<arrTableData.count)
{
//Load TableviewCell you intend to show
}
else
{
//Show loading cell
cell = [self loadingCell:indexPath];
if (!isLoadingNextPage)
{
[self fetchData];
}
}
return cell;
}
Loading Cell Code
-(UITableViewCell *) loadingCell :(NSIndexPath *)indexPath
{
UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle: UITableViewCellStyleDefault reuseIdentifier:nil];
cell.backgroundColor = [UIColor clearColor];
UIActivityIndicatorView *activityIndicatorView =[[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
activityIndicatorView.color = [UIColor blackColor];
activityIndicatorView.center = CGPointMake(self.tblView.center.x, cell.center.y);
[cell.contentView addSubview:activityIndicatorView];
[activityIndicatorView startAnimating];
cell.userInteractionEnabled = NO;
return cell;
}
Api call implementation
-(void)fetchData
{
isLoadingNextPage = YES;
if (currentPage==-1)
currentPage=0;
//API Call
{
DLog(#"API Call Response = %#",response);
isLoadingNextPage = NO;
if (response == nil)
{
hasNextPage=NO;
return;
}
if (success)
{
if (hasNextPage)
{
currentPage++;
}
[arrTableData addObjectsFromArray:response];
}
else
{
hasNextPage=NO;
}
//Reload tableview
}];
}
Alternative solution for this is
Use the SVPullToRefresh library in integrate infinite scrolling of it.
https://github.com/samvermette/SVPullToRefresh
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
if(self.comptabl.contentOffset.y >= (self.comptabl.contentSize.height - self.comptabl.bounds.size.height))
{
if(isPageRefreshing==NO){
isPageRefreshing=YES;
[appDelegate showIndicator:nil view1:self.view];
start=start+50;
[self makeRequest:start];
[self.comptabl reloadData];
NSLog(#"called %d",start);
}
}
}
I am using this method but at all time the value is increment with 50 ..how can i set limit to the value

Adding UITapGestureRecognizer in UITableViewCell with delete capabilities

I created a custom cell and upon click, it should display a popup.
However, when this is works successfully, it seems to create an issue where I am not able to click the delete button.
It seems that my UITapRecognizer supercedes my method to delete.(Means the popup is displayed instead when I click the delete button )
Any idea how to solve this?
Below is my code to handle tap in the cell (OfficeCell.m)
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(openOfficePopover)];
[tapGestureRecognizer setNumberOfTapsRequired:1];
[self setUserInteractionEnabled:YES];
[self addGestureRecognizer:tapGestureRecognizer];
self.textLabel.font = [UIFont boldSystemFontOfSize:15];
self.textLabel.textColor = mRgb(0x3a, 0x6c, 0x99);
}
return self;
}
Below is my code to handle the delete in the ViewController :
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
{
// Return NO if you do not want the specified item to be editable.
NSInteger section = [indexPath section];
if (section ==1 )
{
return YES;
}
return NO;
}
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete)
{
// Delete the row from the data source
NSInteger section = [indexPath section];
if(section == 1)
{
[_sectionOffice removeObjectAtIndex:indexPath.row];
}
[self.formView reloadData];
}
}
Just realize that the fix should be really simple :
changing 2 lines of code solve the issue :
From :
[self setUserInteractionEnabled:YES];
[self addGestureRecognizer:tapGestureRecognizer];
To :
[self.contentView setUserInteractionEnabled:YES];
[self.contentView addGestureRecognizer:tapGestureRecognizer];

Why is my app crashing during Table View Insert Row?

My app is only crashing in a specific way. Here's a break down of what's going on.
I can type in text in a UITextView and tap a button that saves the text and adds a row to a UITableView in another UIViewController. I can then tap on the desired cell from the UITableView and that UIViewController will dismiss and the text will appear again on the main UIViewController.
I have another button that simply clears out the UITextView so I can type in new text.
If I view the text from an added row and then tap the "Add" button to input new text and then tap the "Save" button my app crashes.
Here's some of the code:
didSelectRow Code:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
//Setting the text stored in an array into a NSString here
_displayString = [_savedNoteArray objectAtIndex:indexPath.row];
[self dismissViewControllerAnimated:YES completion:nil];
}
save button code:
- (IBAction)saveNote
{
if (_noteView.aTextView.text == nil)
{
[_noteArray addObject:#""];
Note * tempNote = [[Note alloc] init];
_note = tempNote;
[_savedNotesViewController.savedNoteArray addObject:tempNote];
NSIndexPath * tempNotePath = [NSIndexPath indexPathForRow: [_savedNotesViewController.savedNoteArray count]-1 inSection:0];
NSArray * tempNotePaths = [NSArray arrayWithObject:tempNotePath];
[_savedNotesViewController.noteTableView insertRowsAtIndexPaths:tempNotePaths withRowAnimation:NO];
[[NSNotificationCenter defaultCenter] postNotificationName:#"AddNote" object:nil];
}
else
{
[_noteArray addObject:self.noteView.aTextView.text];
Note * tempNote = [[Note alloc] init];
_note = tempNote;
[_savedNotesViewController.savedNoteArray addObject:tempNote];
NSIndexPath * tempNotePath = [NSIndexPath indexPathForRow:[_savedNotesViewController.savedNoteArray count]-1 inSection:0];
NSArray * tempNotePaths = [NSArray arrayWithObject:tempNotePath];
//**** This is where the app is crashing *****
[_savedNotesViewController.noteTableView insertRowsAtIndexPaths:tempNotePaths withRowAnimation:NO];
[[NSNotificationCenter defaultCenter] postNotificationName:#"AddNote" object:nil];
}
Note * myNote = [Note sharedNote];
myNote.noteOutputArray = _noteArray;
}
add butt code (makes a new UITextView):
- (IBAction)addButtonTapped
{
[[NSNotificationCenter defaultCenter] postNotificationName:#"AddNote" object:nil];
}
in my viewWillAppear to show the selected row text I do this:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
self.noteView.aTextView.text = _savedNotesViewController.displayString;
}
note code (singleton class):
static Note * sharedNote = nil;
- (id)initWithNote:(NSString *)newNote
{
self = [super init];
if (nil != self)
{
self.note = newNote;
}
return self;
}
+ (Note *) sharedNote
{
#synchronized(self)
{
if (sharedNote == nil)
{
sharedNote = [[self alloc] init];
}
}
return sharedNote;
}
When the app crashes I get this:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Note isEqualToString:]: unrecognized selector sent to instance 0x8875f20'
Stepping through my code, the text is being added to the array, but when it comes time to insertRowsAtIndexPaths the app blows up.
Any advice is much appreciated!
* EDIT // TableView Code **
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [_savedNoteArray 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];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
//I have a feeling this could be where an issue is.
NSString * cellString = [_savedNoteArray objectAtIndex:indexPath.row];
cell.textLabel.text = cellString;
return cell;
}
One potential issue (which may not be your crash, but will cause issues regardless) is that you are storing Note objects in the savedNoteArray BUT you are trying to use them as strings (your code below):
//Setting the text stored in an array into a NSString here
_displayString = [_savedNoteArray objectAtIndex:indexPath.row];
Then you assign that displayString to a UITextView's text property (which is supposed to be an NSString*):
self.noteView.aTextView.text = _savedNotesViewController.displayString;
The short form of this issue can be summarized as...
Note *note = [[NSNote alloc] init];
[array addObject:note];
textView.text = array[0];
This will clearly cause issues. You're basically assigning a 'Note' object to something that is supposed to be a string.
This probably leads into the crash that you're experiencing in the cellForRowAtIndexPath: method of your table view data source. Are you using Note objects there as well, or are you properly assigning NSStrings to views?

how to clear a data in tableviewcell again start reload?

i'm developing a bluetooth apps.1) I want to hide a tableview when i start the apps..after i pressed a action button i want to enable a tableview.. 2)if i again press a action button means tableviewcell clear the data and show empty before searching..give me an idea..
some of the code-
- (IBAction)connectButtonTouched:(id)sender
{
[self.deviceTable reloadData];
self.connectButton.enabled = YES;
[self.deviceTable reloadData];
peripheralManager = [[PeripheralManager alloc] init];
peripheralManager.delegate = self;
[peripheralManager scanForPeripheralsAndConnect];
[self.deviceTable reloadData];
[NSTimer scheduledTimerWithTimeInterval:(float)5.0 target:self selector:#selector (connectionTimer:) userInfo:nil repeats:YES];
alert=[[UIAlertView alloc] initWithTitle:#"Bluetooth" message:#"Scanning" delegate:nil cancelButtonTitle:#"Cancel" otherButtonTitles:nil];
UIActivityIndicatorView *progress=[[UIActivityIndicatorView alloc] initWithFrame:CGRectMake(125, 50, 30, 30)];
[alert addSubview:progress];
[progress startAnimating];
[alert show];
}
tableview
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [device 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];
}
cell.textLabel.text=[device objectAtIndex:indexPath.row];
cell.accessoryType=UITableViewCellAccessoryDetailDisclosureButton;
return cell;
}
TableView Delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell=[tableView cellForRowAtIndexPath:indexPath];
[self performSegueWithIdentifier:#"TableDetails" sender:tableView];
}
1.If you want to hide table View before clicking a button you can use
tableView.hidden=YES;
and inside
- (IBAction)connectButtonTouched:(id)sender, you can make it visible by using
tableView.hidden=NO;
[tableView reloadData]
OR
2.If you are developing app for iPad, then you can use UIPopOverController to show the data. You can load the tableView inside UIPopOverController when button is clicked.
Add BOOL flag in .h file
Now in ViewDidLoad add this:
flag = FALSE;
in tableview delegate method
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if(flag)
return [device count];
else
return 0;
}
In your button action method:
- (IBAction)connectButtonTouched:(id)sender
{
if(!flag)
flag = TRUE;
else
flag = FALSE;
[self.deviceTable reloadData];
//your other code here
}
if they are in the same view, why not give the tableView a alpha of 0 and userInteractionEnabled = NO ?
Although, I think it would be easier if, instead of linking your tableView from interface builder, keep it as a member and initialize/reinitialize it when you tap your button.
something like:
`- (IBAction)connectButtonTouched:(id)sender {
if (self.deviceTable) {
[self.deviceTable removefromSuperview];
self.deviceTable = nil;
}
self.deviceTable = [[UITableView alloc] init];
// and so on
`
Also you might put another method in peripheralManager delegate ... something like
-(void)didFinishScanning and inside redraw your table view.
I hope I understood the question well, and helped you in any way

UITableView - inserting rows and scrolling to a specific section

I have a tableview like..
The cells xxxx, yyyy & zzzz are fixed, so that, there is no click action to them. But the cell "Show" is clickable. I want to show some six cells under the "Show" cell when it is clicked.
So, after clicking the "Show" cell, the table will look like..
Here, what I done is,
Changed cell.textLabel.text to "Show" to "Hide"
Used [tableView reloadData]; in tableView:didSelectRowAtIndexPath: method.
My code is:
- (void)viewDidLoad
{
[super viewDidLoad];
self.title = #"My title";
// This Mutable array contains the hided cell objects
hidedCellsArray = [[NSMutableArray alloc] initWithObjects:#"Cell 1", #"Cell 2", #"Cell 3", #"Cell 4", #"Cell 5", #"Cell 6", nil];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
...
else if(indexPath.section == 1)
{
if (isShowClicked) //isShowClicked is a boolean value that determines whether the show cell was clicked or not
{
if (indexPath.row == 0)
{
cell.textLabel.text = #"Hide";
cell.selectionStyle = UITableViewCellSelectionStyleBlue;
cell.textLabel.textAlignment=UITextAlignmentCenter;
}
else
{
cell.textLabel.text = [hidedCellsArray objectAtIndex:indexPath.row-1];
cell.selectionStyle = UITableViewCellSelectionStyleBlue;
cell.textLabel.textAlignment=UITextAlignmentCenter;
}
}
else
{
cell.textLabel.text = #"Show";
cell.selectionStyle = UITableViewCellSelectionStyleBlue;
cell.textLabel.textAlignment=UITextAlignmentCenter;
}
}
...
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.section == 1)
{
if (isShowClicked)
{
isShowClicked = NO;
}
else
{
isShowClicked = YES;
}
[tableView reloadData];
}
}
What I need:
I want to animate the cells when I click on the show/hide button. I come to know that, the method insertRowsAtIndexPaths: withRowAnimation: should be used to achieve the insertion effect. But I really don't see any simple example for this. Should I include any other methods like setEditing:animated: or tableView: commitEditingStyle: forRowAtIndexPath: methods to do this?
The second thing is, before the animation (cell insertion) happen, I want to move the tableview to section 2 (That is, the "show" cell) to the top. Then the animation of inserting cells should be happen. So, the final appearance of the table after clicking the show cell should like..
Help needed.. I just confused!!
For the first part, you can check "Batch Insertion, Deletion, and Reloading of Rows and Sections" section under Table View Programming Guide for iOS. The most important thing is you need to make sure your data source is matched with the change.
For the second problem, you can set the contentOffset of the table view to the point of the origin of second section title. Something like:
self.tableView.contentOffset = CGPointMake(0, [self.tableView rectForSection:1].origin.y);
If you want to use animation for better UE, just do
[UIView animateWithDuration:duration animations:^{
//Move table view to where you want
} completion:^(BOOL finished){
//insert rows when table view movement animation finish
}];
Try with UIView CommitAnimation
Not to nitpick but the "hidedArray" should really be "hiddenArray", for grammatical correctness.
Anyway, I don't think you need a separate array for the hidden items. For the sake of example let's say you are using "dataArray" to populate the tableview.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
...
if ((indexpath.section == 1) && (indexpath.row == 0)) {
if (isShowClicked) {
cell.textLabel.text = #"Hide";
cell.selectionStyle = UITableViewCellSelectionStyleBlue;
cell.textLabel.textAlignment=UITextAlignmentCenter;
} else {
cell.textLabel.text = #"Show";
cell.selectionStyle = UITableViewCellSelectionStyleBlue;
cell.textLabel.textAlignment=UITextAlignmentCenter;
}
}
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if ((indexpath.section == 1) && (indexpath.row == 0) && (isShowClicked == NO)) {
isShowClicked = YES;
[self.tableView beginUpdates];
[dataArray addObjectsFromArray:[NSArray arrayWithObjects:#"cell1",#"cell2",#"cell3",#"cell4",#"cell5",#"cell6", nil]];
NSArray *paths = [NSArray arrayWithObject:[NSIndexPath indexPathForRow:[dataArray count]-1 inSection:1]];
//the first section is section 0, so this is actually the second one
[[self tableView] insertRowsAtIndexPaths:paths withRowAnimation:UITableViewRowAnimationFade];
[tableView endUpdates];
showIsClicked = YES;
self.tableview.contentOffset = xy;
//the value of the tableView's contentOffset when it is scrolled to the right position. You can get it by NSLog-ing the contentOffset property and scrolling to the desired position
} else if ((indexpath.section == 1) && (indexpath.row == 0) && (isShowClicked == YES)) {
isShowClicked = NO;
[self.tableView beginUpdates];
[array removeObjectsAtIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(indexpath.row + 1, 6)]];
[tableView endUpdates];
self.tableview.contentOffset = xy;
//the value of the tableView's contentOffset when it is scrolled to the right position. You can get it by NSLog-ing the contentOffset property and scrolling to the desired position
}
}
There might be a few mistakes but i think it's mostly correct.