Change search field's icon

867 views Asked by At

I try to implement search behavior like in Xcode: if you enter something in search field, icon changes color.

I delegate both searchFieldDidStartSearching and searchFieldDidEndSearching to controller and change the image. The problem is icon's image changes only when window lose it's focus.

enter image description here

class ViewController: NSViewController {
    @IBOutlet weak var searchField: NSSearchField!

    func searchFieldDidStartSearching(_ sender: NSSearchField) {
        print("\(#function)")

        (searchField.cell as! NSSearchFieldCell).searchButtonCell?.image = NSImage.init(named: "NSActionTemplate")
    }

    func searchFieldDidEndSearching(_ sender: NSSearchField) {
        print("\(#function)")

        (searchField.cell as! NSSearchFieldCell).searchButtonCell?.image = NSImage.init(named: "NSHomeTemplate")
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
    }

    override var representedObject: Any? {
        didSet {
        // Update the view, if already loaded.
        }
    }
}

Thanks in advance for any ideas/suggestions.

5

There are 5 answers

0
LevB On BEST ANSWER

Solve problem by subclassing NSSearchFieldCell and assign this class to field's cell.

3
Geart Otten On

As you can see when you click inside the view it's still focussed on the search text field(as you can still type in it after you clicked underneath it). Since the change image is on when it loses focus, you should check if you clicked outside of the text field.

0
jqgsninimo On

Although I don't know the reason, it works:

NSApp.mainWindow?.resignMain()
NSApp.mainWindow?.becomeMain()

Here is the whole code:

class MyViewController: NSViewController {
    private lazy var searchField: NSSearchField = {
        let searchField = NSSearchField(string: "")

        if let searchButtonCell = searchField.searchButtonCell {
            searchButtonCell.setButtonType(.toggle)
            let filterImage = #imageLiteral(resourceName: "filter")
            searchButtonCell.image = filterImage.tinted(with: .systemGray)
            searchButtonCell.alternateImage = filterImage.tinted(with: .systemBlue)
        }
        searchField.focusRingType = .none
        searchField.bezelStyle = .roundedBezel
        searchField.delegate = self

        return searchField
    }()

    ...
}

extension MyViewController: NSSearchFieldDelegate {
    func searchFieldDidStartSearching(_ sender: NSSearchField) {
        sender.searchable = true
    }

    func searchFieldDidEndSearching(_ sender: NSSearchField) {
        sender.searchable = false
    }
}

extension NSSearchField {
    var searchButtonCell: NSButtonCell? {
        (self.cell as? NSSearchFieldCell)?.searchButtonCell
    }

    var searchable: Bool {
        get {
            self.searchButtonCell?.state == .on
        }

        set {
            self.searchButtonCell?.state = newValue ? .on : .off
            self.refreshSearchIcon()
        }
    }

    private func refreshSearchIcon() {
        NSApp.mainWindow?.resignMain()
        NSApp.mainWindow?.becomeMain()
    }
}

extension NSImage {
    func tinted(with color: NSColor) -> NSImage? {
        guard let image = self.copy() as? NSImage else { return nil }
        image.lockFocus()
        color.set()
        NSRect(origin: NSZeroPoint, size: self.size).fill(using: .sourceAtop)
        image.unlockFocus()
        image.isTemplate = false
        return image
    }
}
0
onetrueshady On

I was having the same issue. A simple override fixed this issue for me

extension NSSearchField{
    open override func draw(_ dirtyRect: NSRect) {
        super.draw(dirtyRect)
    }
}
0
Fengson On

You don't even need to subclass NSSearchFieldCell. When you create your NSSearchField from code, you can do something like this:

if let searchFieldCell = searchField.cell as? NSSearchFieldCell {
    let image = NSImage(named: "YourImageName")
    searchFieldCell.searchButtonCell?.image = image
    searchFieldCell.searchButtonCell?.alternateImage = image // Optionally
}

If you're using storyboards, you can do the same in didSet of your @IBOutlet.