Zeist
Zeist

Reputation: 645

Set the placeholder string for NSTextView

Is there any way to set the placeholder string for NSTextView like that in NSTextField? I have checked the property but couldn't find it. I have searched some questions but there isn't a proper explanation.

Upvotes: 15

Views: 6475

Answers (6)

jnpdx
jnpdx

Reputation: 52312

One of the previous answers suggests subclassing NSTextView so that an @objc var placeholderAttributedString: NSAttributedString? property can be added to it (which normally isn't publicly exposed).

However, this subclass isn't necessary. Instead, because NSTextView conforms to NSObject, you can just use setValue to set this property, without having to subclass:

let attributes: [NSAttributedString.Key: Any] = 
  [.foregroundColor: NSColor.secondaryLabelColor]

textView.setValue(NSAttributedString(string: placeholder, attributes: attributes), 
                  forKey: "placeholderAttributedString")

Upvotes: 4

user187676
user187676

Reputation:

The best way to do it if you are using storyboards is to place an NSTextView and bind its value to a

@objc dynamic var myString: String?

property in your controller. In the binding inspector you then can set a Null Placeholder value and the text view will use that without you having to use any explicitly private API at all.

Upvotes: 1

Andrew Chersky
Andrew Chersky

Reputation: 61

Look for this. It's may be a better approach!

final class CustomTextView: NSTextView {

    private var placeholderAttributedString: NSAttributedString? = NSAttributedString(string: "Your placeholder string here")
    private var placeholderInsets = NSEdgeInsets(top: 0.0, left: 4.0, bottom: 0.0, right: 4.0)

    override func becomeFirstResponder() -> Bool {
        self.needsDisplay = true
        return super.becomeFirstResponder()
    }

    override func draw(_ dirtyRect: NSRect) {
        super.draw(dirtyRect)

        guard string.isEmpty else { return }
        placeholderAttributedString?.draw(in: dirtyRect.insetBy(placeholderInsets))
    }
}

extension NSRect {
    func insetBy(_ insets: NSEdgeInsets) -> NSRect {
        return insetBy(dx: insets.left + insets.right, dy: insets.top + insets.bottom)
        .applying(CGAffineTransform(translationX: insets.left - insets.right, y: insets.top - insets.bottom))
    }
}

Upvotes: 5

JanApotheker
JanApotheker

Reputation: 1906

Swift 4

As it turns out, there already seems to be a placeholderAttributedString property in NSTextView that isn't exposed publicly. Thus, you can simply implement it in your own subclass and get the default placeholder behaviour (similar to NSTextField).

class PlaceholderTextView: NSTextView {
     @objc var placeholderAttributedString: NSAttributedString?
}

And if this property will be made available in the future, you only need to use NSTextView instead of this subclass.

Upvotes: 32

Barath
Barath

Reputation: 1754

Swift 2.0

var placeHolderTitleString: NSAttributedString = NSAttributedString(string: "Place Holder Value", attributes: [NSForegroundColorAttributeName : NSColor.grayColor()])

override func becomeFirstResponder() -> Bool {
    self.needsDisplay = true
    return super.becomeFirstResponder()
}

override func drawRect(rect: NSRect) {
    super.drawRect(rect)

    if (self.string! == "") {
        placeHolderString.drawAtPoint(NSMakePoint(0, 0))
    }
}

Upvotes: 7

soldiershin
soldiershin

Reputation: 1620

I found this answer online. Philippe Mougin made this.

static NSAttributedString *placeHolderString;

@implementation TextViewWithPlaceHolder

+(void)initialize
{
  static BOOL initialized = NO;
  if (!initialized)
{
     NSColor *txtColor = [NSColor grayColor];
     NSDictionary *txtDict = [NSDictionary dictionaryWithObjectsAndKeys:txtColor, NSForegroundColorAttributeName, nil];
     placeHolderString = [[NSAttributedString alloc] initWithString:@"This is my placeholder text" attributes:txtDict];
 }
}

- (BOOL)becomeFirstResponder
{
  [self setNeedsDisplay:YES];
  return [super becomeFirstResponder];
}

- (void)drawRect:(NSRect)rect
{
  [super drawRect:rect];
 if ([[self string] isEqualToString:@""] && self != [[self window] firstResponder])
 [placeHolderString drawAtPoint:NSMakePoint(0,0)];
}

- (BOOL)resignFirstResponder
{
   [self setNeedsDisplay:YES];
   return [super resignFirstResponder];
}

@end

Upvotes: 12

Related Questions