I want to check if my NSTableView is in editing or not.
I tried to use tableView: shouldEditTableColumn: row: and tableView: setObjectValue: forTableColumn: row: functions. For example:
- (BOOL)tableView:(NSTableView *)tableView shouldEditTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
isRenaming = YES;
return YES;
}
- (void)tableView:(NSTableView *)tableView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
isRenaming = NO;
...
}
However, tableView: shouldEditTableColumn: row: function was called even when I didn't try to edit the tableview.
So, sometimes, isRenaming is remained to YES.
How to know whether NSTableView is in editing?
Set your view controller to be a delegate for the text view in your table view cell.
Then you can set "isRenaming = YES" when the user triggers the [control: textShouldBeginEditing:] protocol method. You can also set "isRenaming = NO" when the user is done editing (or when they click the "done" or "save changes" button in your UI).
Finally, I found the missing case.
"tableView: shouldEditTableColumn: row:" function was called when the double-click event was occured on NSTableView.
So,
- (void)tableViewDoubleClicked:(id)sender {
isRenamed = NO;
...
}
It solved the problem.
Why not just check editedRow?
let isBeingEdited = yourTable.editedRow != -1
This property does not apply to view-based table views. In a view-based table view, the views are responsible for their own editing behavior. For other tables, the value reflects the index of the row being edited or –1 when there is no editing session in progress.
Related
I have a somewhat weird issue, and I can't quite figure out what am I missing. I have a UITableView which is editable in place (i.e. when my UI is loaded, I send my table the setEditing:YES animated:YES message). The last row in the table is intended to be the "Add New" row. All rows except the last row in my table can be moved around. None of the rows can be deleted.
The rows show up correctly, and the grabbers shows up on the right side of all rows except the last row (as intended). The problem is that I am unable to move the rows. When I tap on the grabber to move the row, it kind of jiggles in place, but I can't drag it up or down. Here's the relevant snippet of code:
- (UITableViewCellEditingStyle)tableView:(UITableView *)aTableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath {
return UITableViewCellEditingStyleNone;
}
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.row == [self.itemArray count] ) {
return NO;
}
return YES;
}
- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.row == [self.itemArray count]) {
return NO;
}
return YES;
}
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath {
Item *item = [self.itemArray objectAtIndex:fromIndexPath.row];
[self.itemArray removeObjectAtIndex:fromIndexPath.row];
[self.itemArray insertObject:item atIndex:toIndexPath.row];
}
- (NSIndexPath *)tableView:(UITableView *)tableView targetIndexPathForMoveFromRowAtIndexPath:(NSIndexPath *)sourceIndexPath toProposedIndexPath:(NSIndexPath *)proposedDestinationIndexPath {
if ([proposedDestinationIndexPath row] < [self.itemArray count]) {
return proposedDestinationIndexPath;
}
NSIndexPath *betterIndexPath = [NSIndexPath indexPathForRow:[self.itemArray count]-1 inSection:0];
return betterIndexPath;
}
On trying to debug, it seems that the tableView:moveRowAtIndexPath: gets called almost immediately even as I am holding on to the grabber (i.e. I have not lifted my finger yet). Moreover, my tableView:targetIndexPathForMoveFromRowAtIndexPath:proposedDestinationIndexPath: does not get invoked at all.
Any thoughts on what am I doing wrong? Any suggestions on what I should try to fix this issue?
I had the same problem, and #wilsontgh & #Andy responses took me in the right direction. However I couldn't afford removing the needed PanRecognizer from the superview, nor disabling it in certain views.
What worked for me was to set this on the main view recognizer, which avoids cancelling touches handled to other views/recognizers.
panRecognizer.cancelsTouchesInView = NO;
I had this problem too. Check if you have a UIGestureRecognizer (for me it was a UIPanGestureRecognizer) that is attached to the UITableView's superviews. I removed it and the reordering worked.
In my case, it is the "hide nav bar on swipe" feature that caused the re-order to fail. Therefore make sure it is turned off if you need the UITableView re-order feature.
navigationController?.hidesBarsOnSwipe = false
Note that navigation controller is shared across all view controllers of the same navigation stack, therefore one screen can affect all other ones even after pushing/pop-ing operations.
I recently had the same problem and this thread pointed me in the right direction, but the better approach is to use the gesture recognizer's delegate method gestureRecognizerShouldBegin: to determine if you are in a situation where you want to process it or not.
In my situation I had a swipe gesture on a main view controller and was displaying a table in a child view controller. In gestureRecognizerShouldBegin: I return a NO when the child view controller is active, which allows the table to see every gesture and the container view controller doesn't handle anything.
The method tableView:moveRowAtIndexPath:toIndexPath: does get called immediately upon touching the control. From the UITableViewDataSource Protocol Reference:
The UITableView object sends this message to the data source when the user presses the reorder control in fromRow.
So this is expected behavior.
I believe the problem is in your tableView:targetIndexPathForMoveFromRowAtIndexPath:toProposedIndexPath:. Do you have a specific reason to include this method? Have you tried leaving it out and going with the proposed position?
For example, it seems that ([proposedDestinationIndexPath row] < [self.itemArray count]) will always return true...
In my case, the problem was in the keyboardDismissMode property.
I have an NSTableView, the content of which is bound to an arrayController using a filterPredicate. The tableView is view-based, so its delegate is set to an object that has the
- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
method. I'm getting odd behavior, which may be related to my observation that this method is being called even when the controller's number of arrangedObjects is 0. Moreover, each time the method is called, the number of rows it asks for is the total number of objects in the array controller's content (as opposed to arrangedObjects). It think it may be because of this that when I set the objectValue of the cellView requested using
cellView.objectValue = [arrayController arrangedObjects] objectAtIndex:row];
that it is often the wrong one. Any ideas?
objectValueForTableColumn is not necessary for the case of bindings. Just banged my head against a wall for a few minutes and came back with this path of least resistance:
1) bind your NSOutlineView content to the NSTreeController (arrangedObjects controller key)
2) bind the subviews of each of the NSTableCellView to the enclosing NSTableCellView (objectValue.foo where foo is the key path for your target field)
3) set the identifier for each column in your outline view (I do this for tracking reordering of table columns between launches)
4) make sure each NSTableCellView has the identifier set to Automatic (or is the same as the table column) ** This is important and messed with me **
5) when you call makeViewWithIdentifier: make sure you pass tableColumn.identifier
This assumes you only have one cell view type per table column. Otherwise, you'll need to pass in the correct identifier for the cell view you want in makeViewWithIdentifier:.
Well, I still can't explain why the view was behaving as it was, but I did manage to get things to behave correctly, doing the following:
binding tableView content to arrayController's arrangedObjects
binding various elements of the tableCellview used in the tableView to the appropriate keyPath of the tableCellView's objectValue
providing each tableCellView as follows (where BILSelectableRoundedTableCellView happens to be a subclass of NSTableCellView):
(NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
NSInteger numberOfObjects = [self.arrayController.arrangedObjects count];
if (numberOfObjects == 0)
return nil;
if (row >= numberOfObjects)
return nil;
BILSelectableRoundedTableCellView *viewForTableColumnRow = [self.myTableView makeViewWithIdentifier:#"DataCell" owner:self];
return viewForTableColumnRow; }
Finally, using a datasource that implements the following:
(id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
NSInteger numberOfObjects = [self.arrayController.arrangedObjects count];
if (numberOfObjects == 0)
return nil;
if (row >= numberOfObjects)
return nil;
return [self.arrayController.arrangedObjects objectAtIndex:row]; }
The use of the later seems contrary to the documentation out there, but is necessary to get things to work properly. Interestingly, the other datasource method (numberOfRowsInTableView) is not called when using bindings, although it is called when i'm not using bindings.
How do I change the view for the selected row when using a view-based NSTableView? Specifically, I'd like to have a simple NSView subclass for unselected rows and a more complex NSView subclass for the selected row which allows editing of more information associated with the row item.
An example is the way Things allows you to expand the item being edited as seen here: http://culturedcode.com/things/
My guess is that you want to use a different NSTableCellView subclass when the row is selected. I think you should be able to do something like this:
- (void)tableViewSelectionDidChange:(NSNotification *)notification
{
NSTableView *table = [notification object];
NSIndexSet *allColumns = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [[table tableColumns] count])];
[table reloadDataForRowIndexes:[table selectedRowIndexes] columnIndexes:allColumns];
}
- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
{
if ([[tableView selectedRowIndexes] containsIndex:row])
{
// If the row is selected, return an instance of the class for selected views
SelectedTableCellView *selectedView = ...; // Get from makeViewWithIdentifier:
// Set up selectedView
return selectedView;
}
else
{
NonSelectedTableCellView *nonSelectedView = ...; // Get from makeViewWithIdentifier:
// Set up nonSelectedView
return nonSelectedView;
}
}
It might be nice if you elaborated a little bit more on what you mean by "change the view to a more complex view"
Nonetheless, you could for instance, implement - (void)tableViewSelectionDidChange:(NSNotification *)notification in the delegate of the table view, get the selected NSTableRowView if it is visible, and change it in what way you want, which includes making it more complex, expanding it (see below), etc.
To modify the size of a row, you would need to implement - (CGFloat)tableView:(NSTableView *)tableView heightOfRow:(NSInteger)row in the same delegate, and call the table view's -noteHeightOfRowsWithIndexesChanged to update the height for particular rows.
I think the app is created by NSOutlineView in outlineview only you can easily expand your selected row...
- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item
{
if ([item isKindOfClass:[NSDictionary class]])
{
return YES;
}else
{
return NO;
}
}
I think this way is write..
I have a flawless functioning view-based NSOutlineView with a proper set-up datasource in my project. Now I want to allow the user to change certain entries. So I made the NSTextField in the IB editable. For a cell-based NSOutlineView you can use the delegate method outlineView:setObjectValue:forTableColumn:byItem: however it's not available for a view-based NSOutlineView as stated in the header file for the NSOutlineViewData protocol:
/* View Based OutlineView: This method is not applicable.
*/
(void)outlineView:(NSOutlineView *)outlineView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn byItem:(id)item;
So I searched for another delegate method and found outlineView:shouldEditTableColumn:item:. However this delegate method doesn't get fired. Probably because I'm not editing a cell.
So my question is: Is there any other way to notice when a row changed than having a delegate for each NSTextField?
You are correct that your text field needs to be editable in Interface Builder.
Next, make your controller conform to NSTextFieldDelegate. Then, set the delegate for the text field in outlineView:viewForTableColumn:item:, like so:
tableCellView.textField.delegate = self
Here's a simplified example, where you've implemented the method for returning the table cell view for an item for your outline view.
-(NSView *)outlineView:(NSOutlineView *)outlineView viewForTableColumn:(NSTableColumn *)tableColumn item:(id)item
{
NSTableCellView *tableCellView = [outlineView makeViewWithIdentifier:#"myTableCellView" owner:self];
MyItem *myItem = (MyItem *)item; // MyItem is just a pretend custom model object
tableCellView.delegate = self;
tableCellView.textField.stringValue = [myItem title];
tableCellView.textField.delegate = self;
return result;
}
Then, the controller should get a controlTextDidEndEditing notification:
- (void)controlTextDidEndEditing:(NSNotification *)obj
{
NSTextField *textField = [obj object];
NSString *newTitle = [textField stringValue];
NSUInteger row = [self.sidebarOutlineView rowForView:textField];
MyItem *myItem = [self.sidebarOutlineView itemAtRow:row];
myItem.name = newTitle;
}
Well, it seems like Apple wants us to use the delegate methods of each NSTextField as stated here:
This method is intended for use with cell-based table views, it must not be used with view-based table views. Instead target/action is used for each item in the view cell.
So there's currently no other way to do this.
How would I use a tableView as a value selector?
So I have a series of input fields and what I want is when you select a cetian field it opens a tableview of options that you can pick from as a value for that field.
Upon selecting an option it returns to the previous View with the selected value filling that field.
This is what I do, similar to the Settings > General > International > Language table view in the iPhone/iPod.
The user can tap a row and a check mark will appear. The view is dismissed when "Done" or "Cancel" is tapped.
First, create a UITableViewController that will display your options. Have a toolbar on the top with a Cancel and Done button. Also have these properties:
SEL selector; // will hold the selector to be invoked when the user taps the Done button
id target; // target for the selector
NSUInteger selectedRow; // hold the last selected row
This view will be presented with the presentModalViewController:animated: method so it appears from the bottom of the screen. You could present it in any other way, but it seems kind of standard across iPhone applications.
Before presenting the view, set target and selector so a method will be called when the user taps the "Done" button.
Now, in your newly created UITableViewController you can implement the thetableView:didSelectRowAtIndexPath:` method as:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell * cell = [self.tableView cellForRowAtIndexPath:indexPath];
cell.accessoryType = UITableViewCellAccessoryCheckmark; // show checkmark
[cell setSelected:NO animated:YES]; // deselect row so it doesn't remain selected
cell = [self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:selectedRow inSection:0]];
cell.accessoryType = UITableViewCellAccessoryNone; // remove check from previously selected row
selectedRow = indexPath.row; // remember the newly selected row
}
Also implement cancel and done methods for the toolbar buttons:
- (IBAction)done:(UIBarButtonItem *)item
{
[target performSelector:selector withObject:[stringArray objectAtIndex:selectedRow]];
[self dismissModalViewControllerAnimated:YES];
}
- (IBAction)cancel:(UIBarButtonItem *)item
{
[self dismissModalViewControllerAnimated:YES];
}
You should use UITableViewDelegate's tableView:didSelectRowAtIndexPath:, remember the value somewhere in another object (a share instance/singleton maybe? - depending on your architecture) and then dismiss this table view.
I implemented a ViewController for Date pick.
I create a protocol to return the date picked to the previous view.
#protocol DataViewDelegate
#optional
- (void)dataViewControllerDidFinish:(NSDate*)dateSelected;
#end
...
- (void) viewDidDisappear:(BOOL)animated
{
if ([ (id)(self.delegate) respondsToSelector:#selector(dataViewControllerDidFinish:)])
{
[self.delegate dataViewControllerDidFinish:self.data];
}
[super viewDidDisappear:animated];
}
In the picker view you can use the
tableView:didSelectRowAtIndexPath:
to select the row you want. Here i set the data property.
The previous view is the delegate for the protocol.