Select text (content) instead of cell with NSCell - objective-c

I'm currently working in a project with a NSOutlineView...
I use, of course, NSCell(s) and I need to let the ability to select text inside the cell...
Or at least... prevent the selection (and highlight) of the cells...
I search all options on IB, but can't found the right one...
Is there a way, programmatically or not, to prevent selection/highlighting of cell, nor let user select cell content ?
Thanks =)

That's not much NSCell related, maybe you're looking to implementing outlineView:shouldSelectItem: in your delegate.
On the NSCell, setEnabled:NO, may help too. From the documentation:
setEnabled:(BOOL)flag
The text of disabled cells is changed to gray. If a cell is disabled, it cannot be highlighted, does not support mouse tracking (and thus cannot participate in target/action functionality), and cannot be edited. However, you can still alter many attributes of a disabled cell programmatically. (The setState: method, for instance, still works.)

Try setting:
cell.selectionStyle = UITableViewCellSelectionStyleNone;
You could also try overriding highlightSelectionInClipRect:, but I'm not totally sure this will work.

Let's take a quick example like the outline view below. There are 3 columns: firstName, lastName, and fullName.
In this particular example, let's say we want to only allow firstName and lastName to be editable while fullName (which is potentially derived from firstName and lastName) is not. You could set this up in Interface Builder by checking or unchecking the editable checkbox for the table column. To do that, click 3 times on one of the table columns (not the header, but inside the outline view); this first selects the NSScrollView, then the NSOutlineView, then an NSTableColumn:
You'd set the attributes like the following:
That provides a start by setting a default editable value for the entire column. If you need more control over whether a particular row's item value should be editable or not, you can use the outlineView:shouldEditTableColumn:item: delegate method:
#pragma mark -
#pragma mark <NSOutlineViewDelegate>
- (BOOL)outlineView:(NSOutlineView *)anOutlineView
shouldEditTableColumn:(NSTableColumn *)tableColumn
item:(id)item {
if ([[tableColumn identifier] isEqualToString:#"firstName"] ||
[[tableColumn identifier] isEqualToString:#"lastName"]) {
return YES;
} else if ([[tableColumn identifier] isEqualToString:#"fullName"]) {
return NO;
}
return YES;
}
If you want to control whether a particular row in the outline view is selectable (for example, you could prevent selection of a group item), you can use outlineView:shouldSelectItem:.
- (BOOL)outlineView:(NSOutlineView *)anOutlineView shouldSelectItem:(id)item {
// if self knows whether it should be selected
// call its fictional isItemSelectable:method:
if ([self isItemSelectable:item]) {
return YES;
}
/* if the item itself knows know whether it should be selectable
call the item's fictional isSelectable method. Here we
are assuming that all items are of a fictional
MDModelItem class and we cast `item` to (MDModelItem *)
to prevent compiler warning */
if ([(MDModelItem *)item isSelectable]) {
return YES;
}
return NO;
}

Related

How to bring the custom view cell above table rows in NSTableView?

When I use a custom view as the cell of a view-based NSTableView, the custom view is somewhat below the table row. When I click on it, instead of affecting the elements (e.g. text field) custom view, the table row was selected (and highlighted). I have to reclick to select the text field.
- (NSView*)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
NSLog(#"We are creating views!");
NSTableCellView *newView;
newView = [tableView makeViewWithIdentifier:#"PostCell" owner:self];
NSTextField *newTextField = [[NSTextField alloc] init];
[newView addSubview:newTextField];
return newView;
}
When I disable the row selection according to NSTableView - Disable Row Selection, there was no selection.
- (BOOL)selectionShouldChangeInTableView:(NSTableView *)tableView {
return NO;
}
But I still cannot select directly the text field. What's worse, I cannot even select it using the mouse. Only tab on the keyboard works.
There seem to be something above it. But is it the "table column" shown in interface builder? Or something else?
How can I fix this?
Use a custom subclass of NSTableView and override -validateProposedFirstResponder:forEvent: to return YES.
See this blog entry from the Apple engineer who wrote the view-based table view code.
Make sure following code is present.
- (BOOL)tableView:(NSTableView *)aTableView shouldSelectRow:(NSInteger)rowIndex {
return YES;
}
You may try logging the subviews Or you can check superviews of view.
This will help to understand view hierarchy.
Also on side note if one of the view's userInteraction is disable then it's subview's won't be able to receive the events. Please verify that all the views and it's subviews userInteraction is enable.
I hope this helps.

NSTableView Multiple Row Types

I'm trying to create a homework planner app that has two types of TableCellViews in a View Based NSTableView. One type is a narrow bar that just has a label of what subject the below homework is for, and the other type is a row to input homework items. (I'll include a screenshot below.)
My question is: when creating new rows in a TableView, how do you specify which type of row you'd like to create? I'm assuming it has something to do with identifiers, but I can't find any information on how to use them in this way.
This is basically how it would look:
You are on the right track with the identifiers. Here's how you use them.
First setup your NSTableView with your specific row types (as you've probably already done). In the screenshot below I made one row with a title and description and another with a few buttons.
Next, you need to setup the desired identifiers. Click the first row in Interface Builder and select the Identity Inspector. Pick a unique identifier for your first row. Do the same for the other(s).
Finally, in your implementation create a new row of a specific type using the following code:
TableViewController.m
#pragma mark - NSTableViewDelegate
- (NSView *)tableView:(NSTableView *)tableView
viewForTableColumn:(NSTableColumn *)tableColumn
row:(NSInteger)row {
NSTableCellView *cell;
if(someCondition == YES) {
cell = [self.tableView makeViewWithIdentifier:#"ButtonRow" owner:self];
} else {
cell = [self.tableView makeViewWithIdentifier:#"TitleDescriptionRow" owner:self];
}
return cell;
}
If you're looking for a more in depth tutorial, check out Cocoa Programming L51 - View-Based NSTableView (YouTube video, not by me).

Can I disable selection in specific UIPickerView item?

I can find a post that teach to use delegate method to check if the row valid and then scroll to the right one if not.
But that's not I want. Can I disable the specific row after the pickerView initialized?
Like "Gray-out" the row and can't stop indicator on it.
Thank you, experts.
There are two parts to doing this.
Use -[<UIPickerViewDelegate> pickerView:viewForRow:forComponent:reusingView:] to return a custom view for each row, such as a UILabel. For the row you want to show up as disabled, you'll need to configure the view to reflect that disabledness.
In -[<UIPickerViewDelegate> pickerView:didSelectRow:inComponent:], you'll need to detect when the disabled is selected, and then use -[UIPickerView selectRow:inComponent:animated:] to "rollback" to a "valid" row.
You could also just leave a blank in the implementation, e.g.:
- (void)pickerView:(UIPickerView *)thePickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component {
if(row==0){
//Code
}
if(row==1){
//BLANK
}
if(row==2){
//Code
}

Toggle-style button : how to toggle title in this case?

I have a CoreData app presenting data with a TableView, textfields, buttons... It deals with people situations and one of the button is a toggle-style button with title "Close". When we consider the user's case closed, we press and it changes the state of a boolean-type attribute in the entity, representing the closed/open state of the case, using a simple binding to the attribute value. The button title also becomes "Reopen" as the case may be reopened in the future.
Then additional things had to be done with the data on pressing the button, so I had to create an IBAction method instead of simply use the former binding. Problem: when button is pressed, the action is done, but the button title is not toggled. It makes sense since nothing tells it to toggle anymore.
I decided to remove the action on the boolean from the IBAction and use again the value binding, so the boolean change is performed by the binding and the other operations are performed by the IBAction. Problem: it modifies the data unexpectedly, sometimes working fine, sometimes not doing all things in a coherent way as expected.
So I'm back with all changes handled by the IBAction and this time, I'm using the Title/Alternate title bindings instead of the value binding. Now the button title toggles, but instead of displaying the word "Close" and "Reopen", it displays the boolean values "0" and "1".
I should perhaps handle the button title change in the IBAction as well, using "setTitle", but then I see a new problem coming. On app start-up, how will it pick the appropriate entity record for reference? And what if the table is in a "No Selection" situation? Looks like a quite extensive piece of code to handle such a small issue...
Any advice on a short, more direct way of handling this is welcome. Thanks.
Sounds like you probably have a couple of different options.
This first option is a bit more involved than the others, but is still good to know for the record. This option basically would not use bindings for the close/re-open button, but instead, set the title, etc. programmatically. The basic game plan would be as follows:
The close/re-open button's initial title when the document opens is set in the nib file (e.g. Close).
Optionally, when the document opens, you could disable the button in -awakeFromNib, since the table view will have no initial selection.
The only actions that would necessitate the button's title or enabled state to be changed is 1) when the tableview's selection is changed, and 2) when you've clicked the close/re-open button to toggle the case's state.
To achieve the desired result, you'd create an IBOutlet to the close/re-open button if you don't already have one. You'd then connect up the tableview's delegate outlet to your controller class, which will let your controller class know when the tableview's selection has been changed (via the - (void)tableViewSelectionDidChange:(NSNotification *)notification <NSTableViewDelegate> protocol method). You would also need to update the close/reopen IBAction method to make sure it switches the title on the button after being clicked (since the tableview selection wouldn't change during that operation). The controller class's code might look something like this:
// add this declaration to avoid compiler warnings:
#interface LHController (LHPrivate)
- (void)updateCloseReopenButtonTitle;
#end
- (void)awakeFromNib {
[self updateCloseReopenButtonTitle];
}
- (void)tableViewSelectionDidChange:(NSNotification *)notification {
[self updateCloseReopenButtonTitle];
}
- (IBAction)toggleCloseReopenButton:(id)sender {
// do your existing code here and then add:
[self updateCloseReopenButtonTitle];
}
- (void)updateCloseReopenButtonTitle {
// assuming 'casesController' is an IBOutlet to your NSArrayController
NSArray *selectedObjects = [casesController selectedObjects];
// loop through the selected cases to determine whether
// they're all closed, all open, or mixed to set
// the title of the button appropriately
BOOL allClosed = YES;
for (LHCase *case in selectedCases) {
if ([case isOpen]) {
allClosed = NO;
break;
}
}
[closeReopenButton setTitle:(allClosed ? #"Reopen" : #"Close")];
[closeReopenButton setEnabled:[selectedObjects count] > 0];
}
Before bindings came along, this is how we used to have to do things.
Another option might be reconsidering your user interface: maybe rather than a push/toggle button whose title you need to toggle, you could instead just have a checkbox titled Closed, which would signify whether the selected cases were closed or not. You could use bindings for the checkbox's state, like shown in the image below:
You could then have an IBAction method that handles the extra stuff that needs processing. You can ask the casesController for the -selectedObjects and then loop through them. As long as you make sure the checkbox Allows Mixed states, it has the added advantage of better representing mixed-case scenarios, in case of a selection of cases with mixed open/closed states (the checkbox a dash instead of a full check).
Another option if you want to stick with a toggle button is to create and specify a custom NSValueTransformer for the title and alternate title bindings. This value transformer would take in the boolean closed/open state of the case and turn it into a string more fitting than just 0 or 1 (which is what's being displayed now). It might look something like this:
+ (Class)transformedValueClass {
return [NSString class];
}
+ (BOOL)allowsReverseTransformation {
return NO;
}
- (id)transformedValue:(id)value {
BOOL isClosed;
if (value == nil) return nil;
// Attempt to get a reasonable value from the
// value object.
if ([value respondsToSelector: #selector(boolValue)]) {
isClosed = [value boolValue];
} else {
[NSException raise: NSInternalInconsistencyException
format: #"Value (%#) does not respond to -boolValue.",
[value class]];
}
return (isClosed ? #"Reopen" : #"Close");
}

TextField clear button

I have 5 textField popovers that are all dependant on each other. The value in the first field sets what will show in popover for second field and so on...
If the user removes one of the fields, I want to be able to clear all fields following that are linked to this field. I started by using the textFieldShouldClear method, but I can't seem to figure out how to tell it which textField is being cleared. It either clears everything, or doesn't clear at all.
Thanks
It sounds like your textFields are set up in IB, which means you can declare 5 IBOutlet UITextField objects in your header file and hook these up to the fields in IB. Then in your delegate method, you can do something like this:
- (BOOL)textFieldShouldClear:(UITextField *)textField {
if([textField isEqual:myField1]) {
myField2.text=#"";
myField3.text=#"";
...
}
else if([textField isEqual:myField2]) {
myField3.text=#"";
...
}
//etc....
return YES;
}
Hope this helps!