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.
6 Answers
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.

- 1,746
- 1
- 17
- 23
-
5I've filed a radar asking Apple to expose this property http://www.openradar.me/33583675 – Keith Smiley Jul 28 '17 at 04:16
-
This doesn't seem to work. I implemented the property in a subclass but it isn't displayed. – Austin Oct 28 '17 at 17:49
-
@JanApotheker Yes, it's uncertain whether it's in swift4 or in 10.13. That's not working – Simon Oct 29 '17 at 10:36
-
3I found you need to use ‘@objc’ to expose the variable in Swift 4 – Austin Oct 29 '17 at 12:17
-
1This displays the text, but it is presented with _some_ vertical offset when compared to setting .textStorage?.setAttributedString() or binding text etc. Values of .baselineOffset and paragraphStyle.paragraphSpacingBefore seem to be ignored by whatever renders this string, so this is not a shippable solution for me. I ended up floating an additional non-selectable text view above the other. – Giles Oct 09 '18 at 16:25
-
A variation on that solution worked for me (instead of creating a subclass, call `theTextView.perform(Selector(("setPlaceholderAttributedString:")), with: someAttributedString)`) but the `foregroundColor` key of the attributed string is being ignored; the placeholder is getting rendered in black whatever I do. Have others had more success in setting the placeholder color? – Cykelero Apr 27 '23 at 06:04
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

- 22,385
- 6
- 55
- 76

- 1,612
- 1
- 18
- 29
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))
}
}

- 1,656
- 19
- 19
-
This is super helpful, but any ideas as to how to make the placeholder text wrap over multiple lines? – Steven Hovater Mar 12 '17 at 02:44
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))
}
}

- 61
- 1
- 3
-
1Maybe add these attributes: `[NSAttributedString.Key.foregroundColor: NSColor.placeholderTextColor]` – Daniel Nov 20 '19 at 12:51
-
I added the following, that can be expanded to properties: `private var placeholderAttributedString: NSAttributedString? = NSAttributedString(string: "Placeholder string", attributes: [NSAttributedString.Key.foregroundColor: NSColor.placeholderTextColor, NSAttributedString.Key.font: NSFont.systemFont(ofSize: 20, weight: .light)])` – ervinbosenbacher Sep 15 '21 at 06:18
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.
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")

- 45,847
- 6
- 64
- 94