Set the placeholder string for NSTextView
Asked Answered
V

6

15

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.

Virtuosity answered 3/4, 2015 at 8:13 Comment(0)
D
12

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
Disgrace answered 3/4, 2015 at 10:23 Comment(0)
S
31

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.

Shannanshannen answered 26/3, 2017 at 12:17 Comment(6)
I've filed a radar asking Apple to expose this property openradar.me/33583675Fluorescent
This doesn't seem to work. I implemented the property in a subclass but it isn't displayed.Chuvash
@Shannanshannen Yes, it's uncertain whether it's in swift4 or in 10.13. That's not workingLincolnlincolnshire
I found you need to use ‘@objc’ to expose the variable in Swift 4Chuvash
This 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.Billbug
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?Alamo
D
12

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
Disgrace answered 3/4, 2015 at 10:23 Comment(0)
I
7

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))
    }
}
Ironwork answered 20/8, 2016 at 7:11 Comment(1)
This is super helpful, but any ideas as to how to make the placeholder text wrap over multiple lines?Labarbera
B
5

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))
    }
}
Bagman answered 27/11, 2018 at 13:3 Comment(2)
Maybe add these attributes: [NSAttributedString.Key.foregroundColor: NSColor.placeholderTextColor]Pigling
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)])Stopped
K
3

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")
Killarney answered 2/9, 2022 at 5:4 Comment(0)
L
1

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.

Lessielessing answered 16/3, 2022 at 12:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.