Search code examples
objective-ccocoainterface-builder

Implement Delegate Method on NSTextField


I am attempting to implement a delegate method on NSTextField as described in this article from Apple. My goal is for the NSTextField to accept carriage returns and tabs. I have read elsewhere (including the linked article) that NSTextView is a better choice. However, I am working within a multiplatform framework that lacks support for NSTextView, and NSTextField will do the job if I can get it to accept carriage returns.

Based on the article, here is my code:

@interface MyTextFieldSubclass : NSTextField
{}
- (BOOL)control:(NSControl*)control textView:(NSTextView*)textView doCommandBySelector:(SEL)commandSelector;
@end

@implementation MyTextFieldSubclass
- (BOOL)control:(NSControl*)control textView:(NSTextView*)textView doCommandBySelector:(SEL)commandSelector
{
   BOOL result = NO;

   if (commandSelector == @selector(insertNewline:))
   {
      // new line action:
      // always insert a line-break character and don’t cause the receiver to end editing
      [textView insertNewlineIgnoringFieldEditor:self];
      result = YES;
   }
   else if (commandSelector == @selector(insertTab:))
   {
      // tab action:
      // always insert a tab character and don’t cause the receiver to end editing
      [textView insertTabIgnoringFieldEditor:self];
      result = YES;
   }

   return result;
}
@end

Additionally, in the Identity Inspector of the text field, I have changed the class name from the default NSTextField to my class name. However, when I run my program, the delegate method never gets called. Is there something else I have to do to set this up in Interface Builder?


Solution

  • There are a few parts of the documentation you linked which is pertinent that I think may have been neglected.

    I've copied a few of the lines below:

    Should you decide to keep using NSTextField, allowing the tab key and/or allowing enter and return keys for line-breaks can be achieved by implementing the following delegate method:

    • (BOOL)control:(NSControl*)control textView:(NSTextView*)textView doCommandBySelector:(SEL)commandSelector;

    Note: When implementing this delegate method in your own object you should set your object up as the "delegate" for this NSTextField.

    I've bolded a few of the callouts which I think might have been missed.

    This method is within the NSControlTextEditingDelegate protocol within NSControl.h. As such it should be implemented by a class which implements the NSControlTextEditingDelegate (i.e. NSTextFieldDelegate)

    One common way of doing this is to have the ViewController "holding" the NSTextField be the NSTextFieldDelegate.

    Here's a very simple example using the sample code from Apple you linked:

    ViewController.h

    #import <Cocoa/Cocoa.h>
    
    @interface ViewController : NSViewController <NSTextFieldDelegate>
    
    
    @end
    

    ViewController.m

    #import "ViewController.h"
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
    
        // Do any additional setup after loading the view.
    }
    
    
    - (void)setRepresentedObject:(id)representedObject {
        [super setRepresentedObject:representedObject];
    
        // Update the view, if already loaded.
    }
    
    - (BOOL)control:(NSControl *)control textView:(NSTextView *)textView doCommandBySelector:(SEL)commandSelector {
        BOOL result = NO;
         
            if (commandSelector == @selector(insertNewline:))
            {
                // new line action:
                // always insert a line-break character and don’t cause the receiver to end editing
                [textView insertNewlineIgnoringFieldEditor:self];
                result = YES;
            }
            else if (commandSelector == @selector(insertTab:))
            {
                // tab action:
                // always insert a tab character and don’t cause the receiver to end editing
                [textView insertTabIgnoringFieldEditor:self];
                result = YES;
            }
         
        return result;
    }
    
    
    @end
    

    Then set your NSTextField's delegate to the ViewController

    Set_Text_Field_Delegate

    No need to add a custom subclass.

    Alternatively you could probably make the custom text field subclass its own delegate. Something along these lines:

    #import "MyTextFieldSubclass.h"
    
    @interface MyTextFieldSubclass() <NSTextFieldDelegate>
    @end
    
    @implementation MyTextFieldSubclass
    
    - (instancetype)init {
        self = [super init];
        if (self) {
            self.delegate = self;
        }
        return self;
    }
    
    - (instancetype)initWithCoder:(NSCoder *)coder {
        self = [super initWithCoder:coder];
        if (self) {
            self.delegate = self;
        }
        return self;
    }
    
    - (instancetype)initWithFrame:(NSRect)frameRect {
        self = [super initWithFrame:frameRect];
        if (self) {
            self.delegate = self;
        }
        return self;
    }
    
    - (void)drawRect:(NSRect)dirtyRect {
        [super drawRect:dirtyRect];
        
        // Drawing code here.
    }
    
    - (BOOL)control:(NSControl *)control textView:(NSTextView *)textView doCommandBySelector:(SEL)commandSelector {
        BOOL result = NO;
    
           if (commandSelector == @selector(insertNewline:))
           {
              // new line action:
              // always insert a line-break character and don’t cause the receiver to end editing
              [textView insertNewlineIgnoringFieldEditor:self];
              result = YES;
           }
           else if (commandSelector == @selector(insertTab:))
           {
              // tab action:
              // always insert a tab character and don’t cause the receiver to end editing
              [textView insertTabIgnoringFieldEditor:self];
              result = YES;
           }
    
           return result;
    }
    
    @end