I am creating a small ipad application using the UISplitViewController which has a UIMasterViewController and a UIDetailViewController. I have removed the UITableView the comes with the UIMasterViewController and I have created my own UITableView by dragging and dropping one from the Xcode Objects panel. I have maned to populate the UITableView with data successfully however when i try to delete a cell or record from it I seem to get an "Thread 1: signal SIGABRT" error.
Below is my edit button code. (Which is a custom button):
//Edit Button
int cnt = 0;
- (IBAction)buttonEditPressed:(id)sender {
if (cnt == 0){
[self.myTableView setEditing:YES animated:YES];
_buttonEdit.title = #"Done";
cnt++;
}
else if (cnt == 1){
[self.myTableView setEditing:NO animated:YES];
_buttonEdit.title = #"Edit";
cnt--;
}
}
And the following code is where the deleting should happen (But gives me the erros instead):
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if(editingStyle == UITableViewCellEditingStyleDelete){
[self.moduleTitleStack removeObjectAtIndex:indexPath.row];
[self.myTableView deleteRowsAtIndexPaths:[NSMutableArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
}
[self.myTableView reloadData];
}
[UPDATE] numberOfRowsInSection implementation:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
//id <NSFetchedResultsSectionInfo> sectionInfo = [self.fetchedResultsController sections][section];
//return [sectionInfo numberOfObjects];
return [_numberOfRows count];
}
I would think that this would do the job but not sure why I am getting this error.
For the answer:
You don't need to do [tableView reloadData]; if you just deleted the row and nothing else changed.
Also, the crash you are getting, is that you are deleting an object from your self.moduleTitleStack, but the row count is checking for _numberOfRows. (I'm guessing they are different arrays), and that's the problem you are getting. You need to update the _numberOfRows to reflect the new setup of your table view
A general tip to know more about the exceptions you are getting:
Go to the breakpoints panel on the left, and on the lower left, click Add > Exception Breakpoint and then just hit OK. The debugger will stop whenever an exception is thrown, giving you some info about the exception, as well as the exact stack trace of where and how it happened
I am using Xcode 4.5.2, and iOS6.
I have 3 View Controllers, FirstViewController contains dynamic table view.
What I want is, when the first row is selected it takes the user to SecondViewController, and when the second row is selected to the ThirdViewController.
Below is the didSelectRowAtIndexPath method of my code
(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSIndexPath *path = indexPath;
NSInteger theInteger = path.row;
switch (theInteger)
{
case 0:
//here i wanna present FirstViewController
break;
case 1:
//here i wanna present SecondViewController
break;
default:
break;
}
}
I am not sure if this is the best practice but this is all what I know. I hope I made it clear.
I have a table view with 3 items, one of which I have behind a button. When the button is selected, I want to hide that button, revealing the item behind it. I am displaying the table row using a table view cell. When I select the one button to hide, scrolling through the table hides more buttons. The hiding of the button seems to hide a button based on some location within the viewable rows of the current view. I'm trying to hide the button on a specific row.
I can write to the NSLog whenever I hit the code to hide a button and I will only get there once, but as I scroll through the table, the hidden attribute for the button applies to other rows that come into view. If I select the button on row 53 I want only the button in row 53 hidden, not buttons on other rows in the 120 row table.
Has anyone ever done what I am trying to do? Any help I can get to figure out what is happening would be appreciated. Thanks.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *ElementCellIdentifier = #"ElementCellIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ElementCellIdentifier];
if (cell == nil) {
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"ElementRowCell"
owner:self options:nil];
if ([nib count] > 0) {
cell = self.tvCell;
} else {
NSLog(#"failed to load ElementRowCell nib file!");
}
}
NSUInteger row = [indexPath row];
UILabel *atomic_number = (UILabel *)[cell.contentView viewWithTag:1];
atomic_number.text = [NSString stringWithFormat:#"%d",elements_table[row].atomic_number];
UILabel *element_name = (UILabel *)[cell.contentView viewWithTag:2];
element_name.text = [NSString stringWithCString:elements_table[row].element_name];
UILabel *element_symbol = (UILabel *)[cell.contentView viewWithTag:3];
element_symbol.text = [NSString stringWithCString:elements_table[row].element_symbol];
return cell;
}
- (IBAction)buttonPressed:(id)sender {
NSLog(#"Getting to buttonPressed from row button");
UIButton *pressedButton = (UIButton *)sender;
NSIndexPath *indexPath = [self.mainTableView indexPathForCell: (UITableViewCell *)[sender superview]];
pressedButton.hidden = TRUE;
}
Sorry.
Basically what's happening is you are hiding the instance of the button in that specific table view cell. The problem is when it gets dequeue'd for another row nothing is restoring it's state. And if you were to just restore it's state to visible then the rows you clicked would be forgotten. You will need to save the rows that have been clicked already to be able to properly restore state in tableView:cellForRowAtIndexPath:.
How I would handle this is declare an NSMutableSet *selectedIndexPaths;. And use this to store the rows I have selected. Then when the button is clicked add that indexPath to the set like so.
- (IBAction)buttonPressed:(UIButton *)button{
if (![button isKindOfClass:[UIButton class]]) return;
UIView *finder = button.superview;
while ((![finder isKindOfClass:[UITableViewCell class]]) && finder != nil) {
finder = finder.superview;
}
if (finder == nil) return;
UITableViewCell *myCell = (UITableViewCell *)finder;
NSIndexPath *indexPath = [self.mainTableView indexPathForCell:myCell];
[selectedIndexPaths addObject:indexPath];
button.hidden = TRUE;
NSLog(#"IndexPathRow %d",indexPath.row);
}
Now to properly restore state when scrolling in tableView:cellForRowAtIndexPath: use an if statement to set the button's hidden property, like so:
buttonPropertyName.hidden = ([selectedIndexPaths containsObject:indexPath]);
I have a table with objects added with NSArray called listOfConjProcedures I want to use an insert control that appears above the top row in the table to add rows to my table when tapping the edit button in a UInavigation controller and I cannot find a good sample code.
The edit function looks like this:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if ([tableView isEditing])
return [listOfConjProcedures count] + 1;
else
return [listOfConjProcedures count];
}
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (editingStyle == UITableViewCellEditingStyleDelete) {
// Delete the row from the data source.
[listOfConjProcedures removeObjectAtIndex:indexPath.row];
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
} else if (editingStyle == UITableViewCellEditingStyleInsert) {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view.
}
}
I don't know how to proceed with the insert function to introduce a new row when the edit button is tapped (At the bottom of the posted code).
Thank you in advance.
This links helps you for insert new row link1 link2 link3
My app has an NSOutlineView and an NSTableView, and I'm having the same problem with both. With a row in either selected, pressing the tab key puts the first column into edit mode instead of making the next key view first responder. To get to the next key view, you need to tab through all of the columns.
Also, shift-tabbing into either view results in the last column going into edit mode, necessitating more shift-tabs to get into its previous key view.
In case it matters, I'm using the autocalculated key view loop, not my own, with my NSWindow set to autorecalculatesKeyViewLoop = YES. I would like tabbing between the columns once the user elects to edit a column, but I don't think it's standard behavior for the tab key to trigger edit mode.
Update
Thanks to the helpful responses below, I worked it out. Basically, I override -keyDown in my custom table view class, which handles tabbing and shift-tabbing out of the table view. It was tougher to solve shift-tabbing into the table view, however. I set a boolean property to YES in the custom table view's -acceptsFirstResponder if it's accepting control from another view.
The delegate's -tableView:shouldEditTableColumn:row checks for that when the current event is a shift-tab keyDown event. -tableView:shouldEditTableColumn:row is called and it's not a shift-tab event, it sets the table view's property back to NO so it can still be edited as usual.
I've pasted the full solution below.
/* CustomTableView.h */
#interface CustomTableView : NSTableView {}
#property (assign) BOOL justFocused;
#end
/* CustomTableView.m */
#implementation CustomTableView
#synthesize justFocused;
- (BOOL)acceptsFirstResponder {
if ([[self window] firstResponder] != self) {
justFocused = YES;
}
return YES;
}
- (void)keyDown:(NSEvent *)theEvent
{
// Handle the Tab key
if ([[theEvent characters] characterAtIndex:0] == NSTabCharacter) {
if (([theEvent modifierFlags] & NSShiftKeyMask) != NSShiftKeyMask) {
[[self window] selectKeyViewFollowingView:self];
} else {
[[self window] selectKeyViewPrecedingView:self];
}
}
else {
[super keyDown:theEvent];
}
}
#end
/* TableViewDelegate.m */
. . .
- (BOOL)tableView:(NSTableView *)tableView shouldEditTableColumn:(NSTableColumn *)tableColumn
row:(NSInteger)row
{
NSEvent *event = [NSApp currentEvent];
BOOL shiftTabbedIn = ([event type] == NSKeyDown
&& [[event characters] characterAtIndex:0] == NSBackTabCharacter);
if (shiftTabbedIn && ((CustomTableView *)tableView).justFocused == YES) {
return NO;
} else {
((CustomTableView *)tableView).justFocused = NO;
}
return YES;
}
. . .
This is the default behavior. If there's no row selected, the table view as a whole has focus, and the Tab key switches to the next key view. If there is a row selected, the table view begins editing or moves to the next cell if already editing.
From AppKit Release Notes:
Tables now support inter-cell
navigation as follows:
Tabbing forward to a table focuses the entire table.
Hitting Space will attempt to 'performClick:' on a NSButtonCell in
the selected row, if there is only one
instance in that row.
Tabbing again focuses the first "focusable" (1) cell, if there is one.
If the newly focused cell can be edited, editing will begin.
Hitting Space calls 'performClick:' on the cell and sets the datasource
value afterwards, if changed. (2)
If a text cell is editing, hitting Enter will commit editing and focus
will be returned to the tableview, and
Tab/Shift-tab will commit the editing
and then perform the new tab-loop
behavior.
Tabbing will only tab through a single row
Once the last cell in a row is reached, tab will take the focus to
the next focusable control.
Back tabbing into a table will select the last focusable cell.
If you want to change this behavior, the delegate method tableView:shouldEditTableColumn:row: may be helpful. You may also have to subclass NSTableView if you really want to affect only the behavior of the Tab key.
The solution using keyDown didn't work for me. Perhaps because it is for cell-based table view.
My solution for a view-based table view, in Swift, looks like this:
extension MyTableView: NSTextFieldDelegate {
func controlTextDidEndEditing(_ obj: Notification) {
guard
let view = obj.object as? NSView,
let textMovementInt = obj.userInfo?["NSTextMovement"] as? Int,
let textMovement = NSTextMovement(rawValue: textMovementInt) else { return }
let columnIndex = column(for: view)
let rowIndex = row(for: view)
let newRowIndex: Int
switch textMovement {
case .tab:
newRowIndex = rowIndex + 1
if newRowIndex >= numberOfRows { return }
case .backtab:
newRowIndex = rowIndex - 1
if newRowIndex < 0 { return }
default: return
}
DispatchQueue.main.async {
self.editColumn(columnIndex, row: newRowIndex, with: nil, select: true)
}
}
}
You also need to set the cell.textField.delegate so that the implementation works.
My blog post on this tricky workaround: https://samwize.com/2018/11/13/how-to-tab-to-next-row-in-nstableview-view-based-solution/
I've had to deal with this before as well. My solution was to subclass NSTableView or NSOutlineView and override keyDown: to catch the tab key presses there, then act on them.
How convenient! I was just looking at this myself yesterday, and it's good to see some confirmation of the approach I took - keyDown: handling.
However, I have one small possible refinement to your approach: I worked out that the method triggering editing on shift-tabbing back to the table was the becomeFirstResponder call. So what I did on a NSTableView subclass was:
Add a synthesized property to control whether tab-editing behaviour was disabled
On keydown, check the first character (also check for [[theEvent characters] length] to avoid exceptions for dead keys!) for tab; if tab editing is disabled, move on to the next/previous view, as per your code sample.
Override becomeFirstResponder:
- (BOOL)becomeFirstResponder {
if (tabEditingDisabled) {
[self display];
return YES;
}
return [super becomeFirstResponder];
}
This keeps all the code in the tableview subclass, keeping the delegate cleaner :)
The only danger is I don't know what else NSTableView does in becomeFirstResponder; I didn't notice anything breaking, but...
This worked for me:
- (BOOL)tableView:(NSTableView *)tableView shouldEditTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
NSEvent *e = [NSApp currentEvent];
if (e.type == NSKeyDown && e.keyCode == 48) return NO;
return YES;
}