Question

I'm trying to handle a paste operation on a NSTextField.

I found a similar article but for NSTextView. I tried code similar to this by overriding NSTextField and putting:

- (BOOL)readSelectionFromPasteboard:(NSPasteboard *)pboard
{
    return [super readSelectionFromPasteboard: pboard];
}

But this method seems to never be called.

Any suggestions on how to detect a past on NSTextField?

Was it helpful?

Solution

You could use the NSTextFieldDelegate delegate method - (BOOL) control:(NSControl*) control textView:(NSTextView*) textView doCommandBySelector:(SEL) commandSelector and watch for the paste: selector.

OTHER TIPS

  1. Override the becomeFirstResponder method of your NSTextField

  2. Use object_setClass to override the class of the "field editor" (which is the NSTextView that handles text input for all NSTextField instances; see here)

#import <AppKit/AppKit.h>
#import <objc/runtime.h>

@interface MyTextField : NSTextField
@end

@implementation MyTextField

- (BOOL)becomeFirstResponder
{
  if ([super becomeFirstResponder]) {
    object_setClass(self.currentEditor, MyFieldEditor.class);
    return YES;
  }
  return NO;
}

@end
  1. Create your MyFieldEditor class and override its paste: method
@interface MyFieldEditor : NSTextView
@end

@implementation MyFieldEditor

- (void)paste:(id)sender
{
  // Get the pasted text.
  NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
  NSString *text = [pasteboard stringForType:NSPasteboardTypeString];
  NSLog(@"Pasted: %@", text);

  // Set the pasted text. (optional)  
  [pasteboard clearContents];
  [pasteboard setString:@"Hello world" forType:NSPasteboardTypeString];

  // Perform the paste action. (optional)
  [super paste:sender];
}

@end

All done! Now you can intercept every paste action.

Combining 2 answers found here I was able to find a workaround for me, but it was necessary to subclass NSTextField, NSTextFieldCell and NSTextView.

Swift 5

1.

Subclass NSTextView, this is where the actual paste is intercepted and can the content be replaced.

final class TextView: NSTextView {
    override func paste(_ sender: Any?) {
        var content = NSPasteboard.general.string(forType: .string) ?? ""

        // modify content

        NSPasteboard.general.clearContents()
        NSPasteboard.general.setString(content, forType: .string)
        super.paste(sender)
    }
}

2.

Subclass NSTextFieldCell and override fieldEditorFor with your own NSTextView. Important to set to true the property isFieldEditor of the NSTextView.

final class TextFieldCell: NSTextFieldCell {
    private let textView = TextView()
    
    required init(coder: NSCoder) {
        super.init(coder: coder)
    }

    override init(textCell: String) {
        super.init(textCell: textCell)
        textView.isFieldEditor = true
    }
    
    override func fieldEditor(for: NSView) -> NSTextView? {
        textView
    }
}

3.

Subclass NSTextField and assign to the static property cellClass your own NSTextFieldCell. This last step could be avoided if you simply assign your own NSTextFieldCell to all NSTextField, i.e. NSTextField.cellClass = yourCellClass

final class TextField: NSTextField {
    required init?(coder: NSCoder) {
        nil
    }

    init() {
        TextField.cellClass = TextFieldCell.self
        super.init(frame: .zero)
    }
}

Overriding NSTextFieldCell and putting.

 ///////////////////////////////////////////
 //BPastTextFieldCell.h
 //
 @interface BPastTextView : NSTextView <NSTextViewDelegate>
 @end

 @class BPastTextFieldCell ;
 @interface BPastTextFieldCell : NSTextFieldCell

 @end




  //////////////////////////////////////////
  //
  //BPastTextFieldCell.m 
  //

  #import "BPastTextFieldCell.h"

  @implementation BPastTextFieldCell

  - (NSTextView *)fieldEditorForView:(NSView *)controlView{
     BPastTextView *textView = [[BPastTextView alloc] init];
      return textView;
  }
  @end




  @implementation BPastTextView

  - (void)keyDown:(NSEvent *)theEvent {

      bool bHandled = false;
      if ([theEvent modifierFlags] & NSEventModifierFlagCommand)
      {
          NSResponder * responder = [[self window] firstResponder];
          if ((responder != nil) && [responder isKindOfClass[NSTextView class]])
          {
              NSTextView * textView = (NSTextView *)responder;
              NSRange range = [textView selectedRange];
              bool bHasSelectedTexts = (range.length > 0);
              unsigned short keyCode = [theEvent keyCode];

              if (keyCode == 6)  //command + Z
              {
                  if ([[textView undoManager] canUndo])
                  {
                      [[textView undoManager] undo];
                      bHandled = true;
                  }
              }
              else if (keyCode == 7 && bHasSelectedTexts) // command + X
              {
                  [textView cut:self];
                  bHandled = true;
              }
              else if (keyCode== 8 && bHasSelectedTexts)  // command + C
              {
                  [textView copy:self];
                  bHandled = true;
              }
              else if (keyCode == 9)   // command + V
              {
                  [textView paste:self];
                  bHandled = true;
              }
          }
      }
      if(bHandled)
          return;

      [super keyDown:theEvent];

  }

  @end

A nstextfield does not have copy and paste functions. Those are only found in nstextview. the catch is that when a textfield is edited it opens up a textview called a fieldeditor during the editing.

See my answer here:

NSTextField: exposing its Copy and Paste methods

Here is what i use to detect paste in UITextField:

// Set this class to be the delegate of the UITextField. Now when a user will paste a text in that textField, this delegate will be called.
-(BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {

    // Here we check if the replacement text is equal to the string we are currently holding in the paste board
    if ([string isEqualToString:[UIPasteboard generalPasteboard].string]) {

        // code to execute in case user is using paste

    } else {

        // code to execute other wise
    }

    return YES;
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top