Make NSTableView active - objective-c

Cocoa noob here.
I've got a simple Mac app that includes an NSTextField that fetches some results and puts them into an NSTableView. I'd like to be able to press up/down while in the text field to activate the first/last item in the table view.
I've done the following:
- (void)keyUp:(NSEvent *)theEvent {
switch([theEvent keyCode]) {
case 125: {
NSLog(#"I need to move down");
NSIndexSet *indexSet = [NSIndexSet indexSetWithIndex:0];
[resultsTableView selectRowIndexes:indexSet byExtendingSelection:NO];
[resultsTableView becomeFirstResponder];
break;
}
case 126: {
NSLog(#"I need to move up");
NSIndexSet *indexSet = [NSIndexSet indexSetWithIndex:[results count]-1];
[resultsTableView selectRowIndexes:indexSet byExtendingSelection:NO];
[resultsTableView becomeFirstResponder];
break;
}
}
}
Which is partially what I want. It does select the first or last item in the resultsTableView, but the selected item stays grayed out, and the textview stays active.
I thought calling becomeFirstResponder on the resultsTableView would do the trick, but it didn't.
Any help would be greatly appreciated!

Never call becomeFirstResponder directly. Use NSWindow's makeFirstResponder:
[[resultsTableView window] makeFirstResponder:resultsTableView];

Related

Nested NSNotification for NSTableViews

I have two table views controlling the display/sorting of a collection of objects (namely, by Category and Localization, i.e. Lieu).
My problem is : I want the selection to be updated when the user clicks on a cell inside any of those Table Views (which is working just fine using NSTableViewDelegate), but I also want to restore the selection to the default one in the other Table View.
My problem is then obvious : every call to tableViewSelectionDidChange triggers another call to himself, which makes the result quiet unstable. Is there a way to prevent this call [tableViewCategory selectRowIndexes:[NSIndexSet indexSetWithIndex:0] byExtendingSelection:NO]; from triggering a notification ?
- (void)tableViewSelectionDidChange:(NSNotification *)aNotification
{
if ([[[aNotification object]identifier]isEqualToString:#"table2"]){
//First, reset AnnonceWithCategory
[tableViewCategory selectRowIndexes:[NSIndexSet indexSetWithIndex:0] byExtendingSelection:NO];
//Then
[self showAnnoncesWithLieu];
}
else if ([[[aNotification object]identifier]isEqualToString:#"table3"]){
//First, reset AnnonceWithLieu
[tableViewLieu selectRowIndexes:[NSIndexSet indexSetWithIndex:0] byExtendingSelection:NO];
[self showAnnoncesWithCategory];
}
}
You can't prevent the NSTableView from sending the notification, but you can prevent your class from responding to it. You could do something like this:
- (void)tableViewSelectionDidChange:(NSNotification *)aNotification
{
if ([[[aNotification object]identifier]isEqualToString:#"table2"] && ! _currentlyUpdatingTable2){
//First, reset AnnonceWithCategory
_currentlyUpdatingTable2 = YES;
[tableViewCategory selectRowIndexes:[NSIndexSet indexSetWithIndex:0] byExtendingSelection:NO];
_currentlyUpdatingTable2 = NO;
//Then
[self showAnnoncesWithLieu];
}
else if ([[[aNotification object]identifier]isEqualToString:#"table3"] && ! _currentlyUpdatingTable3){
//First, reset AnnonceWithLieu
_currentlyUpdatingTable3 = YES;
[tableViewLieu selectRowIndexes:[NSIndexSet indexSetWithIndex:0] byExtendingSelection:NO];
_currentlyUpdatingTable3 = NO;
[self showAnnoncesWithCategory];
}
}
...where _currentlyUpdatingTable2 and _currentlyUpdatingTable3 are ivars of the object receiving the notification.

Input Accessory View Realization

Absolutely beginner obj-c question.
I am totally frustrated last few days about to do my task, but it seems like I have a problem bigger than my level of knowledges now. Before posting this question I asked few times about this in different forms, but so far I haven't understanding of how to do it, so I search for turnkey solution.
Task:
Plain UITableView with two sections. I am interested only in first section to improve Input Accessory View to switch between four textFields in cells.
http://uaimage.com/image/62f08045
Custom cells are inherited from UITableViewCell and have a UITextField's in them as a property. So my task is to set first responder to different textFields.
Ideas:
to set textFields as a delegate for ViewController and then resign and set first responder in input acessory view methods
to tag textFields, set NSMutableArray filled with this textFields and then in -inputAccessoryViewNext and in -inputAccesoryViewPrev change responder
to tag cells by indexPath and get textFields from cell
But I'm unable to realize any of this advances correctly and nothing works for me yet, so very need help.
I'm attached UITableViewController.m and FDTextFieldCell.m (custom cell) above:
https://docs.google.com/open?id=0B5rYA7McNhFlSXBrLTFVU2hVd1U
Will be glad to reward anyone who can help by a modest bounty of reputation.
I think your second idea is the right approach. Add an instance variable to your table view controller like NSMutableArray *_textFields and initialize it in viewDidLoad.
Then, in your tableView:cellForRowAtIndexPath: method, add something like this every time you :
if ([indexPath row] == 0) {
FDTextFieldCell *cell = [self textFieldCell];
[[cell textLabel] setText:#"Ваше Имя"];
[[cell textField] setPlaceholder:#"Обязательно"];
[[cell textField] setText:[profile name]];
[[cell textField] setReturnKeyType:UIReturnKeyNext];
[[cell textField] setKeyboardType:UIKeyboardTypeDefault];
// ADD THIS
[[cell textField] setTag:[indexPath row]];
if (![_textFields containsObject:[cell textField]]) {
[_textFields addObject:[cell textField]];
[_textFields sortUsingDescriptors:#[[NSSortDescriptor sortDescriptorWithKey:#"tag" ascending:YES]]];
}
return cell;
}
From there, you have a sorted array of your text fields, so you can implement your accessory methods like so:
- (void) inputAccessoryViewDidSelectNext:(FDInputAccessoryView *)view {
UITextField *textField = nil;
for (textField in _textFields) {
if ([textField isFirstResponder])
break;
}
NSInteger indexOfFirstResponder = [_textFields indexOfObject:textField];
NSInteger nextIndex = indexOfFirstResponder + 1;
if (nextIndex == [_textFields count])
nextIndex = 0;
UITextField *nextField = [_textFields objectAtIndex:nextIndex];
[nextField becomeFirstResponder];
}
- (void) inputAccessoryViewDidSelectPrev:(FDInputAccessoryView *)view {
UITextField *textField = nil;
for (textField in _textFields) {
if ([textField isFirstResponder])
break;
}
NSInteger indexOfFirstResponder = [_textFields indexOfObject:textField];
NSInteger previousIndex = indexOfFirstResponder - 1;
if (previousIndex < 0)
previousIndex = [_textFields count] - 1;
UITextField *previousField = [_textFields objectAtIndex:previousIndex];
[previousField becomeFirstResponder];
}

Clickable Text with Hover Effect in NSTableView

I'm trying to create a column of clickable URL-type text (NOT URLs like this, but essentially a borderless, title button or text field cell with a tracking area for a hover effect) within an NSTableView.
1.) When the user hovers over a particular cell the text in that cell should draw an underline below the text (hover/trackable area effect).
2.) When the user clicks the text it should perform an action.
I've subclassed NSCell and NSTableView and added a tracking area within the custom tableview to try and track the mouse location of the individual cell of the table to notify the cell when to redraw itself. I can get the current row and column of the mouse location, but can't seem to get the right cell in my custom tableview's mouseMoved: method
-(void)mouseMoved:(NSEvent *)theEvent {
[super mouseMoved:theEvent];
NSPoint p = [self convertPoint:[theEvent locationInWindow] fromView:nil];
long column = [self columnAtPoint:p];
long row = [self rowAtPoint:p];
id cell = [[self.tableColumns objectAtIndex:column] dataCellForRow:row];
}
It gets the cell for the column, but doesn't get the right cell for that particular row. Perhaps I'm not fully understanding the dataCellForRow: function for NSTableColumn?
I know you can't quite add a tracking area for cells, but instead you must create the hit test for mouse clicks and then begin tracking once the hit test is successful (meaning the mouse is already down) and then use startTracking:, continueTracking:, and stopTracking: to get the mouse's position. The idea though is that it has a hover effect before any mouseDown: action.
Also, I can't just use a view-based tableview (which would be incredible) because my app must be 10.6 compatible.
I'm not sure what's wrong with your method of getting the cell, but you don't really need to get that to do what you want. I tested a way to do this that entailed creating a table view subclass to do the tracking in the mouse moved method. Here is the code for that subclass:
-(void)awakeFromNib {
NSTrackingArea *tracker = [[NSTrackingArea alloc] initWithRect:self.bounds options:NSTrackingMouseEnteredAndExited|NSTrackingMouseMoved|NSTrackingActiveInActiveApp owner:self userInfo:nil];
[self addTrackingArea:tracker];
self.rowNum = -1;
}
-(void)mouseMoved:(NSEvent *)theEvent {
NSPoint p = theEvent.locationInWindow;
NSPoint tablePoint = [self convertPoint:p fromView:nil];
NSInteger newRowNum = [self rowAtPoint:tablePoint];
NSInteger newColNum = [self columnAtPoint:tablePoint];
if (newColNum != self.colNum || newRowNum != self.rowNum) {
self.rowNum = newRowNum;
self.colNum = newColNum;
[self reloadData];
}
}
-(void)mouseEntered:(NSEvent *)theEvent {
[self reloadData];
}
-(void)mouseExited:(NSEvent *)theEvent {
self.rowNum = -1;
[self reloadData];
}
I put the array and table delegate and data source code in the app delegate (probably not the best place, but ok for testing).
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
self.theData = #[#{#"name":#"Tom",#"age":#"47"},#{#"name":#"Dick",#"age":#"21"},#{#"name":#"Harry",#"age":#"27"}];
[self.table reloadData];
self.dict = [NSDictionary dictionaryWithObjectsAndKeys:#2,NSUnderlineStyleAttributeName,[NSColor redColor],NSForegroundColorAttributeName,nil];
}
- (NSInteger)numberOfRowsInTableView:(RDTableView *)aTableView {
return self.theData.count;
}
- (id)tableView:(RDTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex {
if (self.table.colNum == 0 && rowIndex == self.table.rowNum && [aTableColumn.identifier isEqualToString:#"Link"]) {
NSString *theName = [[self.theData objectAtIndex:rowIndex] valueForKey:#"name"];
return [[NSAttributedString alloc] initWithString:theName attributes:self.dict];
}else if ([aTableColumn.identifier isEqualToString:#"Link"]){
return [[self.theData objectAtIndex:rowIndex] valueForKey:#"name"];
}else{
return [[self.theData objectAtIndex:rowIndex] valueForKey:#"age"];
}
}
- (void)tableViewSelectionDidChange:(NSNotification *)aNotification {
if (self.table.colNum == 0)
NSLog(#"%ld",[aNotification.object selectedRow]);
}
I use the delegate method tableViewSelectionDidChange: to implement the action if you click on a cell in the first column (which has the identifier "Link" set in IB).

Issue with a checkBox in a viewbased NSTableView

I have an NSDictionary that holds all the data:
One title (not important for this question)
One link (not important for this question)
One array of NSDictionary containing again 1 title and 1 link
I'm displaying this data in a view based table view like this:
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tv
{
if (tv == _downloadTable)
//I use this "if" because I have another tableView that has nothing to do
//with this one
{
return [[myDictionary objectForKey:#"myArray"] count];
}
}
I want 2 columns in this tableView, one to display the title and one with a checkbox, that would do something letting me know which row is checked.
- (NSView *)tableView:(NSTableView *)tv viewForTableColumn :(NSTableColumn *)tableColumn row :(NSInteger)row
{
if (tv == _downloadTable)
{
if (tableColumn == _downloadTableTitleColumn)
{
if ([[[myDictionary objectForKey:#"myArray"]objectAtIndex:row]objectForKey:#"title"])
{
NSString *title = [[[myDictionary objectForKey:#"myArray"]objectAtIndex:row]objectForKey:#"title"];
NSTableCellView *result = [tv makeViewWithIdentifier:tableColumn.identifier owner:self];
result.textField.stringValue = title;
return result;
}
}
if (tableColumn == _downloadTableCheckColumn)
{
NSLog(#"CheckBox"); //I wanted to see exactly when that was called
//But it didn't help me :(
NSButton *button = [[NSButton alloc]init];
[button setButtonType:NSSwitchButton];
[button setTitle:#""];
return button;
}
}
}
Right now when I run it and click on the checkbox it does nothing
(of course because I don't know how to make it do something.
Where should I put the code that should do something?
The main goal is an editable list of downloads, right now the list is displayed, with the checkbox right next to the title at each lines.
I would like to know which checkBox are checked and which are not.
I tried this:
[button setAction:#selector(checkBoxAction:)];
- (void)checkBoxAction: (id)sender
{
NSLog(#"I am button : %# and my state is %ld", sender, (long)[sender state]);
}
But I can't figure out how to get the row of that button, to know which title is associated with this checkBox.
I also tried the setObjectValue method of the tableView without success.
The way I would like it to work is:
I have a "start downloading" button that check if each checkbox is checked or not and launch the next action (downloading) only with the checked row.
I would like to avoid bindings because I plan to make it work on iOS too and I don't want to have different code for iOS.
You can use the NSTableView method -rowForView: to get the row a particular view is in.
In your case you'd have something like this:
- (void)checkBoxAction:(id)sender
{
NSInteger row = [_downloadTable rowForView:sender];
NSLog(#"The button at row %ld was clicked.", row);
}
Here are the docs for NSTableView: https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ApplicationKit/Classes/NSTableView_Class/Reference/Reference.html
You could try using the button' tag property setting it for each button you place as the number (location) in the tableview. Look here!!!
Detecting which UIButton was pressed in a UITableView
[EDIT1]
If people actually decided to read the linked post you would realize that the answer is actually there.
Try adding:
[button setTag:row];
[button addTarget:self action:#selector(checkBoxAction:) forControlEvents:UIControlEventTouchUpInside];
inside the else of your viewForTableColumn routine:
In your checkBoxAction routine:
- (void)checkBoxAction: (id)sender{
NSLog(#"I am button : %# and my state is %#", sender.tag, [sender state]);
}
I also think that once you begin digging further into your code, you are going to want to start using the auto-dequeuing capability of the TableViewCell objects. I believe that you are going to find yourself in a memory alloc/dealloc problem.

NSTextField events and resigning first responder

I want to implement an NSTextField where when I click it, it selects all the text. (to give the user easy way to delete all current text)
when I finish editing it, either by pressing enter/tab or moving the mouse outside of it's rect, I will move the focus out of the field, and change it's alpha values to 0.5.
My Code:
H file:
#import <Foundation/Foundation.h>
#interface MoodMsgTextField : NSTextField<NSTextFieldDelegate>
#end
M file:
-(BOOL) becomeFirstResponder
{
NSLog(#"become first responder");
BOOL result = [super becomeFirstResponder];
if(result)
{
[self setAlphaValue:1.0];
[self performSelector:#selector(selectText:) withObject:self afterDelay:0];
}
return result;
}
-(BOOL) refusesFirstResponder
{
return NO;
}
-(BOOL) resignFirstResponder
{
NSLog(#"resigning first responder");
BOOL result = [super resignFirstResponder];
NSText* fieldEditor = [self.window fieldEditor:YES forObject:self];
[fieldEditor setSelectedRange:NSMakeRange(0,0)];
[fieldEditor setNeedsDisplay:YES];
[self setAlphaValue:0.5];
return result;
}
-(void)awakeFromNib
{
self.delegate = self;
[self setAlphaValue:0.5];
[self setBordered:YES];
[self setWantsLayer:YES];
self.layer.borderWidth = 0.5;
self.layer.borderColor = [[NSColor grayColor] CGColor];
}
- (void)controlTextDidChange:(NSNotification *)aNotification
{
NSLog(#"the text is %#",self.stringValue);
}
- (void)controlTextDidEndEditing:(NSNotification *)aNotification
{
NSLog(#"end editiing : the text is %#",self.stringValue);
[self.window makeFirstResponder:nil];
}
- (void)mouseEntered:(NSEvent *)theEvent
{
[self setWantsLayer:YES];
self.layer.borderWidth = 0.5;
self.layer.borderColor = [[NSColor grayColor] CGColor];
}
- (void)mouseExited:(NSEvent *)theEvent
{
[self setWantsLayer:YES];
self.layer.borderWidth = 0;
}
So, I have a few problem:
1.
When I press inside the NSTextField (when the focus is outside) it instantly becomes and resigns first responder and I get editing end message. Why is that ?
The log I get on click is this :
2011-08-02 18:03:19.044 ooVoo[42415:707] become first responder
2011-08-02 18:03:19.045 ooVoo[42415:707] resigning first responder
2011-08-02 18:03:19.104 ooVoo[42415:707] end editing : the text is
2.
When I press the enter key it just selects all text inside and doesn't move the mouse focus. When I press the tab is does seem to move focus, however neither of the two causes the resignFirstResponder to get called. Why ?
3.
None of the mouse event function are getting called. Do I need to to do something special for that ? I thought that since they are NSResponder's ones, I will get those for free by inheriting from NSTextField. Do I need NSTrackingInfo here as well ?
4.
Last but not least, for some reason, every couple letters, one letter seems to be bold.
I have no idea why.
I'd appreciate any help.
Thanks
I am not sure why this is happening in this case but you should read about the Field Editor concept. Basically a NSTextField does not handle its own input but uses an NSTextView called the field editor to accept input.
You need to react to the Enter key yourself. Take a look at the key handling documentation. Here is an answer with an example.
To get mouse events you can use NSTrackingArea. See the docs for Mouse Tracking.
I do not have any input on this except that sometimes text drawing can look bold when really what is happening is that the text is being drawn multiple times without erasing the background.