Question

I have a subclass of NSTextField that I made so that when a user is done editing the field, the text field will lose focus. I also have it set up so whenever the user clicks on the main view, this will act as losing focus on the textfield. And this all works great. Now I want to add some additional capabilities to the subclass.

I want the textfield to send a textDidEndEditing every time a user clicks anywhere outside of the box. This includes when a user clicks on another UI component. The behavior I'm seeing right now is that when a user clicks on another UI component (let's say a combo box) the action does not trigger. Is there a way to force this? Besides manually adding it as a part of the other components actions?

Any help would be appreciated!

Here's the code for my textDidEndEditing function

- (void)textDidEndEditing:(NSNotification *)notification
{
  NSString *file = nil;
  char c = ' ';
  int index = 0;

  [super textDidEndEditing:notification];

  if ([self isEditable])
  {
    // is there a valid string to display?
    file = [self stringValue];
    if ([file length] > 0)
    {
      c = [file characterAtIndex:([file length] - 1)];
      if (c == '\n') // check for white space at the end
      {
        // whitespace at the end... remove
        NSMutableString *newfile = [[NSMutableString alloc] init];
        c = [file characterAtIndex:index++];
        do
        {
          [newfile appendFormat:@"%c", c];
          c = [file characterAtIndex:index++];
        }
        while ((c != '\n') && (index < [file length]));

        [self setStringValue:newfile];
        file = newfile;
      }

      [[NSNotificationCenter defaultCenter]
       postNotificationName:@"inputFileEntered" object:self];
    }
  }

  // since we're leaving this box, show no text in this box as selected.
  // and deselect this box as the first responder
  [self setSelectedText:0];
  [[NSNotificationCenter defaultCenter]
   postNotificationName:@"setResponderToNil" object:self];
}

Where "setSelectedText" is a public function in the text field subclass:

- (void)setSelectedText:(int) length
{
  int start = 0;
  NSText *editor = [self.window fieldEditor:YES forObject:self];
  NSRange range = {start, length};
  [editor setSelectedRange:range];
}

And the "setResponderToNil" notification is a part of my NSView subclass:

- (void)setResponderToNil
{
  AppDelegate *delegate = (AppDelegate *)[NSApp delegate];
  [delegate.window makeFirstResponder:nil];
}
Was it helpful?

Solution

I think I found a way to do this. It may not be the most eloquent, but it seems to work with the type of behavior I want.

I added an mouse event listener to the app's main controller:

event_monitor_mousedown_ = [NSEvent addLocalMonitorForEventsMatchingMask:NSRightMouseDown
                                                               handler:^NSEvent *(NSEvent * event)
{
  NSResponder *resp = [[[NSApplication sharedApplication] keyWindow] firstResponder];

    if ([resp isKindOfClass:[NSTextView class]])
    {
      // set UI in proper state - remove focus from text field
      // even when touching a new window for the first time
      [[NSNotificationCenter defaultCenter]
       postNotificationName:@"setResponderToNil" object:self];
      [self setStopState];
    }

  return event;
}];

This event checks the current responder in the application on any mouseDown action. If it's a textView object (which is type of the object that would be the first responder when editing an NSTextField) it will send the notification to set the firstResponder to nil. This forces the textDidEndEditing notification. I want to play around with it some more to see if I'm getting the right expected behavior. I hope this helps someone out there!

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top