iOS 13 Set UISearchTextField placeholder color
Asked Answered
I

5

18

How do you set the placeholder color of iOS 13's UISearchTextField?

I tried the following with no success:

searchField.attributedPlaceholder = NSAttributedString(string: "Some placeholder", attributes: [NSAttributedString.Key.foregroundColor: UIColor.red])

Is this a bug in the current beta or do I miss something? I am using Xcode 11, beta 7.

Incurvate answered 6/9, 2019 at 10:30 Comment(7)
Did you find a solution to the issue? I am experiencing the same problem. I reckon it must be beta bug.Pyles
Have not found a solution yet... will write an update as soon as I have oneIncurvate
Ok, I found the solution. It was a timing issue. Make sure to set the attributedPlaceholder in viewDidAppear. Will wait for the public release of iOS 13 for an official answer.Incurvate
You're right. Unfortunately that means it is quite visible that color changes. A bummer. It works in viewDidLoad in iOS 12.Pyles
It seam to work also on viewDidLayoutSubviews, a got way to hide the color changesFob
Seems like a bug to me - calling it too soon leads to this kind of behavior in the iOS 13.1 (beta).Cordeliacordelie
Still happening in iOS 13.1.2Lafleur
A
12

I found the following code snippet worked:

DispatchQueue.main.asyncAfter(deadline: .now()+0.5) {
    // Stackoverflow said the above doesn't work on viewDidLoad style methods, so running it async after a 0.5s delay to workaround.  Works.
    searchField.attributedPlaceholder = NSAttributedString(string:"Search", attributes: attributesDictionary)
}
Aeonian answered 25/11, 2019 at 16:44 Comment(2)
Awesome! I'm finding I don't have to wait quite so long, I can do DispatchQueue.main.async { ... } so that it happens in the next event loop.Ilona
Jep. DispatchQueue.main.async {...} worked for me too. In my case it's needed since iOS 14.0...Ambivalence
T
8

You can try this with Xcode 11, iOS 13

 extension UISearchBar {

        func getTextField() -> UITextField? { return value(forKey: "searchField") as? UITextField }
        func set(textColor: UIColor) { if let textField = getTextField() { textField.textColor = textColor } }
        func setPlaceholder(textColor: UIColor) { getTextField()?.setPlaceholder(textColor: textColor) }
        func setClearButton(color: UIColor) { getTextField()?.setClearButton(color: color) }

        func setTextField(color: UIColor) {
            guard let textField = getTextField() else { return }
            switch searchBarStyle {
            case .minimal:
                textField.layer.backgroundColor = color.cgColor
                textField.layer.cornerRadius = 8
            case .prominent, .default: textField.backgroundColor = color
            @unknown default: break
            }
        }

        func setSearchImage(color: UIColor) {
            guard let imageView = getTextField()?.leftView as? UIImageView else { return }
            imageView.tintColor = color
            imageView.image = imageView.image?.withRenderingMode(.alwaysTemplate)
        }
    }

    private extension UITextField {

    private class Label: UILabel {
        private var _textColor = UIColor.lightGray
        override var textColor: UIColor! {
            set { super.textColor = _textColor }
            get { return _textColor }
        }

        init(label: UILabel, textColor: UIColor = .lightGray) {
            _textColor = textColor
            super.init(frame: label.frame)
            self.text = label.text
            self.font = label.font
        }

        required init?(coder: NSCoder) { super.init(coder: coder) }
    }


    private class ClearButtonImage {
        static private var _image: UIImage?
        static private var semaphore = DispatchSemaphore(value: 1)
        static func getImage(closure: @escaping (UIImage?)->()) {
            DispatchQueue.global(qos: .userInteractive).async {
                semaphore.wait()
                DispatchQueue.main.async {
                    if let image = _image { closure(image); semaphore.signal(); return }
                    guard let window = UIApplication.shared.windows.first else { semaphore.signal(); return }
                    let searchBar = UISearchBar(frame: CGRect(x: 0, y: -200, width: UIScreen.main.bounds.width, height: 44))
                    window.rootViewController?.view.addSubview(searchBar)
                    searchBar.text = ""
                    searchBar.layoutIfNeeded()
                    _image = searchBar.getTextField()?.getClearButton()?.image(for: .normal)
                    closure(_image)
                    searchBar.removeFromSuperview()
                    semaphore.signal()
                }
            }
        }
    }

    func setClearButton(color: UIColor) {
        ClearButtonImage.getImage { [weak self] image in
            guard   let image = image,
                let button = self?.getClearButton() else { return }
            button.imageView?.tintColor = color
            button.setImage(image.withRenderingMode(.alwaysTemplate), for: .normal)
        }
    }

    var placeholderLabel: UILabel? { return value(forKey: "placeholderLabel") as? UILabel }

    func setPlaceholder(textColor: UIColor) {
        guard let placeholderLabel = placeholderLabel else { return }
        let label = Label(label: placeholderLabel, textColor: textColor)
        placeholderLabel.removeFromSuperview() // To remove existing label. Otherwise it will overwrite it if called multiple times.
        setValue(label, forKey: "placeholderLabel")
    }

    func getClearButton() -> UIButton? { return value(forKey: "clearButton") as? UIButton }

}

Use:

    searchBarObj.placeholder = "placeholder text"
    searchBarObj.set(textColor: .blue)
    searchBarObj.setTextField(color: UIColor.gray)
    searchBarObj.setPlaceholder(textColor: .red)
    searchBarObj.setSearchImage(color: .black)
    searchBarObj.setClearButton(color: .red)
Tonie answered 14/10, 2019 at 11:5 Comment(2)
This answer uses setValue(_, forKey:), which is a private API the use of which can get one banned from the app store.Mcleod
Matt, it isn't private, its standard cocoa methodStriction
G
1

iOS 13

Use a custom search bar.

This also works when part of a UISearchController inside a UINavigationItem (with hidesSearchBarWhenScrolling = true).

We want to apply our changes immediately after UIAppearance proxies are being applied since those are the most likely root cause:

class MySearchBar : UISearchBar {
    // Appearance proxies are applied when a view is added to a view hierarchy, so apply your tweaks after that:
    override func didMoveToSuperview() {
        super.didMoveToSuperview() // important! - system colors will not apply correctly on ios 11-12 without this

        let placeholderColor = UIColor.white.withAlphaComponent(0.75)
        let placeholderAttributes = [NSAttributedString.Key.foregroundColor : placeholderColor]
        let attributedPlaceholder = NSAttributedString(string: "My custom placeholder", attributes: placeholderAttributes)
        self.searchTextField.attributedPlaceholder = attributedPlaceholder
        
        // Make the magnifying glass the same color
        (self.searchTextField.leftView as? UIImageView)?.tintColor = placeholderColor
    }
}

// Override `searchBar` as per the documentation
private class MySearchController : UISearchController {
    private lazy var customSearchBar = MySearchBar()
    override var searchBar: UISearchBar { customSearchBar }
}

Copy of my answer from here

Goeger answered 1/3, 2020 at 0:38 Comment(1)
This is the only clean answer, it's 2020 just use this and forget about iOS 9Jataka
M
0

This works for me XCode 11.1 for placeholder color, remove search icon:

  let textFieldInsideSearchBar = searchbar.value(forKey: "searchField") as? UITextField
    textFieldInsideSearchBar!.attributedPlaceholder = NSAttributedString(string: Constant.Text.search_currency.localized, attributes: [NSAttributedString.Key.foregroundColor: UIColor.white])
    textFieldInsideSearchBar?.textColor = UIColor.white
    textFieldInsideSearchBar?.leftViewMode = UITextField.ViewMode.never
    searchbar.setImage(UIImage(), for: .search, state: .normal)
    searchbar.setPositionAdjustment(UIOffset(horizontal: -20, vertical: 0), for: .search)
Monocycle answered 16/12, 2019 at 13:50 Comment(0)
B
0

UiSearch bar is not updating placeholder colur in viewDidLoad and viewWillAppear so write below code in viewDidAppear:

let textField = searchbar.value(forKey: "searchField") as? UITextField
let placeholderAttributes = [NSAttributedString.Key.foregroundColor : UIColor.white.withAlphaComponent(0.6)]
let attributedPlaceholder = NSAttributedString(string: "SearchTextPlaceholer".localized(), attributes: placeholderAttributes)
textField?.attributedPlaceholder = attributedPlaceholder
Burnell answered 8/6, 2020 at 8:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.