Search code examples
swiftmacosfontsnssearchfield

Style placeholder text NSSearchField and NSTextField?


I've been creating a MacOS app and am having trouble styling the font of an NSSearchField (named searchField). My code so far is as follows:

Declared at top of single main viewController class:

let normalTextStyle = NSFont(name: "PT Mono", size: 14.0)

let backgroundColour = NSColor(calibratedHue: 0.6,
                               saturation: 0.5,
                               brightness: 0.2,
                               alpha: 1.0)

let normalTextColour = NSColor(calibratedHue: 0.5,
                               saturation: 0.1,
                               brightness: 0.9,
                               alpha: 1.0)

Declared in viewDidLoad:

searchField.backgroundColor = backgroundColour
searchField.textColor = normalTextColour
searchField.font = normalTextStyle
searchField.centersPlaceholder = false
searchField.currentEditor()?.font = normalTextStyle
let attrStr = NSMutableAttributedString(string: "Search...",
                                        attributes: [NSForegroundColorAttributeName: normalTextColour])
searchField.placeholderAttributedString = attrStr

Generally this works except in one condition: when the search field has focus but no search term has been entered. In this case the placeholder text has the correct colour but the font seems to return to the default (Helvetica 12 point?). As soon as something is typed in or the field loses focus, then the correct font is used once more.

I have tried with no luck looking through the Apple docs for some kind of font or colour settings not currently being set. I have fiddled about with all the font setting I could find in the interface builder, including cocoa bindings and the normal settings in the inspector.

Do I need to set some value of the currentEditor? I am guessing not because the font is changed once text is entered.. I am stuck - can anyone help?

EDIT: I've now tried with an NSTextField and the results are the same. Does anyone have any ideas?


Solution

  • I eventually managed to find an answer. I created a new class TitleTextFormatter of type Formatter, which is 'is intended for subclassing. A custom formatter can restrict the input and enhance the display of data in novel ways'. All I needed to do was override certain default functions to get what I needed:

    import Cocoa
    
    class TitleTextFormatter: Formatter {
    
      override func string(for obj: Any?) -> String? {    
        /*
         * this function receives the object it is attached to.
         * in my case it only ever receives an NSConcreteAttributedString
         * and returns a plain string to be formatted by other functions
         */
    
        var result: String? = nil
        if let attrStr = obj as? NSAttributedString {
            result = attrStr.string
        }
        return result
      }
    
      override func getObjectValue( _ obj: AutoreleasingUnsafeMutablePointer<AnyObject?>?,
                                    for string: String,
                                    errorDescription error: AutoreleasingUnsafeMutablePointer<NSString?>?) -> Bool {
        /* 
         * this function in general is overridden to provide an object created 
         * from the input string. in this instance, all I want is an attributed string
         */
    
        let titleParagraphStyle = NSMutableParagraphStyle()
        titleParagraphStyle.alignment = .center
    
        let titleAttributes = [NSAttributedStringKey.foregroundColor: NSColor.mainText,
                               NSAttributedStringKey.font: NSFont.titleText,
                               NSAttributedStringKey.paragraphStyle: titleParagraphStyle]
    
        let titleAttrStr = NSMutableAttributedString(string: string,
                                                     attributes: titleAttributes)
    
        obj?.pointee = titleAttrStr
        return true
      }
    
      override func attributedString(for obj: Any,
                                   withDefaultAttributes attrs: [NSAttributedStringKey : Any]? = nil) -> NSAttributedString? {
        /* 
         * is overridden to show that an attributed string is created from the
         * formatted object. this happens to duplicate what the previous function 
         * does, only because the object I want to create from string is an 
         * attributed string
         */
    
        var titleAttrStr: NSMutableAttributedString?
    
        if let str = string(for: obj) {
            let titleParagraphStyle = NSMutableParagraphStyle()
            titleParagraphStyle.alignment = .center
    
            let titleAttributes = [NSAttributedStringKey.foregroundColor: NSColor.mainText,
                                   NSAttributedStringKey.font: NSFont.titleText,
                                   NSAttributedStringKey.paragraphStyle: titleParagraphStyle]
    
            titleAttrStr = NSMutableAttributedString(string: str,
                                                     attributes: titleAttributes)
        }
    
        return titleAttrStr
    
      }
    
    }   
    

    and then in viewDidLoad I added the following:

    let titleTextFormatter = TitleTextFormatter()
    titleTextField.formatter = titleTextFormatter