Set the placeholder string for NSTextView - macos

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.

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.

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

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))
}
}

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))
}
}

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")

Related

How to draw NSPopupButtonCell highlighted image

I have NSPopupButton inside NSToolbar that has borderder = false which is having some color burn blend when highlighted. If I use bordered = true the image is drawn with nice dark overlay.
I am trying to achieve to draw highlighted state the same way as it is in bordered=true
PS: NSButtonCell with bordered = false works out of the box.
I can achieve the behaviour by having bordered = true and overriding drawBezel and do nothing there but I want to know
Things I tried:
highlightsBy
interiorBackgroundStyle
setCellAttribute
-
class ToolbarPopUpButtonCell : NSPopUpButtonCell {
override var isHighlighted: Bool {
get { return true }
set { super.isHighlighted = newValue }
}
override func drawImage(withFrame cellFrame: NSRect, in controlView: NSView) {
super.drawImage(withFrame: cellFrame, in: controlView)
}
//used in case bordered = true so we do nothing
override func drawBezel(withFrame frame: NSRect, in controlView: NSView) {
}
//doesn't work
override var interiorBackgroundStyle: NSView.BackgroundStyle
{
return .raised
}
}
class ToolbarPopUpButton: NSPopUpButton {
override func awakeFromNib() {
cell?.setCellAttribute(.cellLightsByBackground, to: 1)
}
override var intrinsicContentSize: NSSize {
return NSMakeSize(32 + 5, 32)
}
}
Notice the image on right which works for bordered = false (NSButtonCell)
The only hack I found is to draw a subsitute cell. Still seeking a better solution.
- (void)drawImageWithFrame:(NSRect)cellFrame inView:(NSView *)controlView
{
NSImage *image = [self image];
NSButtonCell *swapCell = [[NSButtonCell alloc] initImageCell:image];
[swapCell setHighlighted:[self isHighlighted]];
[swapCell drawImage:image withFrame:cellFrame inView:controlView];
}

How can I set the text color of an NSButton via Interface Builder?

There are several questions about how to set the text color programmatically. That's all fine, but there's got to be a way to do it via Interface Builder also.
The "Show Fonts" box works for changing the size of the button text, but Xcode ignores any color changes made using the widget there, and the Attributes Inspector for NSButton doesn't have a color picker...
I've no idea why this is missing still from NSButton. But here is the replacement class in Swift 4:
import Cocoa
class TextButton: NSButton {
#IBInspectable open var textColor: NSColor = NSColor.black
#IBInspectable open var textSize: CGFloat = 10
public override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
}
public required init?(coder: NSCoder) {
super.init(coder: coder)
}
override func awakeFromNib() {
let titleParagraphStyle = NSMutableParagraphStyle()
titleParagraphStyle.alignment = alignment
let attributes: [NSAttributedStringKey : Any] = [.foregroundColor: textColor, .font: NSFont.systemFont(ofSize: textSize), .paragraphStyle: titleParagraphStyle]
self.attributedTitle = NSMutableAttributedString(string: self.title, attributes: attributes)
}
}
Try this solution,i hope so you will get :)
NSFont *txtFont = button.font;
NSMutableParagraphStyle *style = [[NSMutableParagraphStyle alloc] init];
[style setAlignment:button.alignment];
NSDictionary *attrsDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
[NSColor whiteColor], NSForegroundColorAttributeName, style, NSParagraphStyleAttributeName, txtFont, NSFontAttributeName, nil];
NSAttributedString *attrString = [[NSAttributedString alloc]
initWithString:button.title attributes:attrsDictionary];
[button setAttributedTitle:attrString];
You can also add this extension to your code if you like the 'Throw an extension in and look if it sticks' approach.
extension NSButton {
#IBInspectable open var textColor: NSColor? {
get {
return self.attributedTitle.attribute(.foregroundColor, at: 0, effectiveRange: nil) as? NSColor
}
set {
var attributes = self.attributedTitle.attributes(at: 0, effectiveRange: nil)
attributes[.foregroundColor] = newValue ?? NSColor.black
self.attributedTitle = NSMutableAttributedString(string: self.title,
attributes: attributes)
}
}
}
Edit: Misread question. Below is how you'd change the text of a button on an iOS app.
Just to clarify, this isn't working for you?
added button
click on it and go to Attributes Inspector
change color in "Text Color" field

NSAttributedString highlight/background color shows between lines (ugly)

I'm trying to nicely display paragraphs of highlighted in a NSTextView. Right now, I'm doing this by creating a NSAttributedString with a background color. Here's some simplified code:
NSDictionary *attributes = #{NSBackgroundColorAttributeName:NSColor.greenColor};
NSAttributedString *attrString = [[NSAttributedString alloc] initWithString:#"Here is a single line of text with single spacing" attributes:attributes];
[textView.textStorage setAttributedString:attrString];
This approach basically works, in that it produces highlighted text.
Unfortunately, when multiple lines exist, the highlight covers the vertical space between the lines in addition to the lines themselves, resulting in ugliness.
Does anyone know of a way to do this kind of highlighting in Cocoa? The picture below is basically what I'm looking for (ignore the shadow on the white boxes):
I'd be willing to use CoreText, html, or whatever is necessary to make things look nicer.
You will need to subclass NSLayoutManager and override:
- (void)fillBackgroundRectArray:(const CGRect *)rectArray
count:(NSUInteger)rectCount
forCharacterRange:(NSRange)charRange
color:(UIColor *)color;
This is the primitive method for drawing background color rectangles.
Try this:-
-(IBAction)chooseOnlylines:(id)sender
{
NSString *allTheText =[tv string];
NSArray *lines = [allTheText componentsSeparatedByString:#"\n"];
NSString *str=[[NSString alloc]init];
NSMutableAttributedString *attr;
BOOL isNext=YES;
[tv setString:#""];
for (str in lines)
{
attr=[[NSMutableAttributedString alloc]initWithString:str];
if ([str length] > 0)
{
NSRange range=NSMakeRange(0, [str length]);
[attr addAttribute:NSBackgroundColorAttributeName value:[NSColor greenColor] range:range];
[tv .textStorage appendAttributedString:attr];
isNext=YES;
}
else
{
NSString *str=#"\n";
NSAttributedString *attr=[[NSAttributedString alloc]initWithString:str];
[tv .textStorage appendAttributedString:attr];
isNext=NO;
}
if (isNext==YES)
{
NSString *str=#"\n";
NSAttributedString *attr=[[NSAttributedString alloc]initWithString:str];
[tv .textStorage appendAttributedString:attr];
}
}
}
The paragraph needs to be highlighted when user taps on it. this is how I implemented it and don't confuse with the highlight color, it is a custom NSAttributedString key I created for this purpose.
extension NSAttributedString.Key {
public static let highlightColor = NSAttributedString.Key.init("highlightColor")
}
class ReaderLayoutManager: NSLayoutManager {
// MARK: - Draw Background
override func drawBackground(forGlyphRange glyphsToShow: NSRange, at origin: CGPoint) {
super.drawBackground(forGlyphRange: glyphsToShow, at: origin)
self.enumerateLineFragments(forGlyphRange: glyphsToShow) { (_, usedRect, _, range, _) in
guard let highlightColor = self.currentHighlightColor(range: range) else { return }
guard let context = UIGraphicsGetCurrentContext() else { return }
var lineRect = usedRect
lineRect.origin.y += 10
lineRect.size.height -= 2
context.saveGState()
let path = UIBezierPath(roundedRect: lineRect, cornerRadius: 2)
highlightColor.setFill()
path.fill()
context.restoreGState()
}
}
private func currentHighlightColor(range: NSRange) -> UIColor? {
guard let textStorage = textStorage else { return nil }
guard let highlightColor = textStorage.attributes(at: range.location, effectiveRange: nil)[.highlightColor] as? UIColor else { return nil }
return highlightColor
}
}
when user clicks on it, I set the highlight color for the range and reset the TextView.
attributedString.addAttributes([.highlightColor: theme.textUnderlineColor], range: range)

NSPopUpButton arrow color

Is there a way to customize the color of a NSPopUpButton arrow? I've looked around but I've not found an answer yet
I really dont think there is an "easy" way to do this. If you look at the API description, it even states that it doesnt respond to the setImage routine. I have done quite a bit of work sub-classing button objects, etc... and I think this is where you would have to go in order to do what you are asking.
Like too many of these controls, I did it by subclassing NSPopupButton(Cell) and then doing all my own drawing in drawRect...I cheated a little though, and used an image do the actual triangle rather than trying to do it via primitives.
- (void)drawRect:(NSRect)dirtyRect
{
//...Insert button draw code here...
//Admittedly the above statement includes more work than we probably want to do.
//Assumes triangleIcon is a cached NSImage...I also make assumptions about location
CGFloat iconSize = 6.0;
CGFloat iconYLoc = (dirtyRect.size.height - iconSize) / 2.0;
CGFloat iconXLoc = (dirtyRect.size.width - (iconSize + 8));
CGRect triRect = {iconXLoc, iconYLoc, iconSize, iconSize};
[triangleIcon drawInRect:triRect];
}
i did this and its worked for me.
(void)drawImageWithFrame:(NSRect)cellFrame inView:(NSView *)controlView
{
NSPopUpButton *temp = (NSPopUpButton*)controlView;
NSString *strtile = temp.title;
AppDelegate *appdel = (AppDelegate*)[NSApplication sharedApplication].delegate;
NSFont *font = [NSFont systemFontOfSize:13.5];
NSSize size = NSMakeSize(40, 10);// string size
CGRect rect = controlView.frame;
rect = CGRectMake((size.width + temp.frame.size.width)/2, rect.origin.y, 8, 17);
[self drawImage:[NSImage imageNamed:#"icon_downArrow_white.png"] withFrame:rect inView:self.
}
I have changed arrow color by using "False Color" filter without using any image. So far it is the easiest way to change cocoa control to me.
class RLPopUpButton: NSPopUpButton {
init() {
super.init(frame: NSZeroRect, pullsDown: false)
addFilter()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
addFilter()
}
func addFilter() {
let colorFilter = CIFilter(name: "CIFalseColor")!
colorFilter.setDefaults()
colorFilter.setValue(CIColor(cgColor: NSColor.black.cgColor), forKey: "inputColor0")
colorFilter.setValue(CIColor(cgColor: NSColor.white.cgColor), forKey: "inputColor1")
// colorFilter.setValue(CIColor(cgColor: NSColor.yellow.cgColor), forKey: "inputColor0")
// colorFilter.setValue(CIColor(cgColor: NSColor.property.cgColor), forKey: "inputColor1")
self.contentFilters = [colorFilter]
}
}
Swift 5
In interface builder, remove default arrow setting.
Then, apply this subclass for cell, which will add an NSImageView to the right side of the NSPopUpButton.
This way you have complete control over what you set as your custom button and how you position it.
import Cocoa
#IBDesignable class NSPopUpButtonCellBase: NSPopUpButtonCell {
let textColor = NSColor(named: "white")!
let leftPadding: CGFloat = 16
let rightPadding: CGFloat = 30
override func awakeFromNib() {
super.awakeFromNib()
let imageView = NSImageView()
imageView.image = NSImage(named: "ic_chevron_down")!
controlView!.addSubview(imageView)
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.widthAnchor.constraint(equalToConstant: CGFloat(20)).isActive = true
imageView.heightAnchor.constraint(equalToConstant: CGFloat(20)).isActive = true
imageView.trailingAnchor.constraint(equalTo: controlView!.trailingAnchor).isActive = true
imageView.centerYAnchor.constraint(equalTo: controlView!.centerYAnchor).isActive = true
}
// overriding this removes the white container
override func drawBezel(withFrame frame: NSRect, in controlView: NSView) {
}
// overriding this allows us to modify paddings to text
override func titleRect(forBounds cellFrame: NSRect) -> NSRect {
// this gets rect, which has title's height, not the whole control's height
// also, it's origin.y is such that it centers title
let processedTitleFrame = super.titleRect(forBounds: cellFrame)
let paddedFrame = NSRect(
x: cellFrame.origin.x + leftPadding,
y: processedTitleFrame.origin.y,
width: cellFrame.size.width - leftPadding - rightPadding,
height: processedTitleFrame.size.height
)
return paddedFrame
}
// overriding this allows us to style text
override func drawTitle(_ title: NSAttributedString, withFrame frame: NSRect, in controlView: NSView) -> NSRect {
let attributedTitle = NSMutableAttributedString.init(attributedString: title)
let range = NSMakeRange(0, attributedTitle.length)
attributedTitle.addAttributes([NSAttributedString.Key.foregroundColor : textColor], range: range)
return super.drawTitle(attributedTitle, withFrame: frame, in: controlView)
}
}

How to fill the right bottom corner of NSScrollView?

I want to change its color, but I'm not sure wether to subclass NSScrollView or NSClipView. Or if the corner can be inserted as a regular NSView.
(source: flickr.com)
I don't need code. Just a hint at how to do it.
Already answered elsewhere on stackoverflow by mekentosj. The class to subclass is NSScrollView.
#interface MyScrollView : NSScrollView {
}
#end
#implementation MyScrollView
- (void)drawRect:(NSRect)rect{
[super drawRect: rect];
if([self hasVerticalScroller] && [self hasHorizontalScroller]){
NSRect vframe = [[self verticalScroller]frame];
NSRect hframe = [[self horizontalScroller]frame];
NSRect corner;
corner.origin.x = NSMaxX(hframe);
corner.origin.y = NSMinY(hframe);
corner.size.width = NSWidth(vframe);
corner.size.height = NSHeight(hframe);
// your custom drawing in the corner rect here
[[NSColor redColor] set];
NSRectFill(corner);
}
}
#end
Kind of odd but just subclassing NSScrollView and overriding draw with super.drawRect() made my NSScrollView (not) fill in that corner with white. I tested it 2x to make sure, since it doesn't make much sense.
import Cocoa
class ThemedScrollView: NSScrollView {
override func drawRect(dirtyRect: NSRect) {
super.drawRect(dirtyRect)
}
}
Here's a Swift variation of the original answer as well:
import Cocoa
class ThemedScrollView: NSScrollView {
override func drawRect(dirtyRect: NSRect) {
super.drawRect(dirtyRect)
if hasVerticalScroller && hasHorizontalScroller {
guard verticalScroller != nil && horizontalScroller != nil else { return }
let vFrame = verticalScroller!.frame
let hFrame = horizontalScroller!.frame
let square = NSRect(origin: CGPoint(x: hFrame.maxX, y: vFrame.maxY), size: CGSize(width: vFrame.width, height: hFrame.height))
let path = NSBezierPath(rect: square)
let fillColor = NSColor.redColor()
fillColor.set()
path.fill()
}
}
}
Updated Swift 5.2 version of Austin's answer
class Themed: NSScrollView {
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
if hasVerticalScroller && hasHorizontalScroller {
guard verticalScroller != nil && horizontalScroller != nil else { return }
let vFrame = verticalScroller!.frame
let hFrame = horizontalScroller!.frame
let square = NSRect(origin: CGPoint(x: hFrame.maxX, y: vFrame.maxY), size: CGSize(width: vFrame.width, height: hFrame.height))
let path = NSBezierPath(rect: square)
let fillColor = NSColor.red
fillColor.set()
path.fill()
}
}
}

Resources