30

I've been trying search results for hours, but I can't get this figured out. Perhaps it isn't possible. I'm trying to change the tint color of the placeholder text and magnifying glass of a UISearchBar. I'm only targeting iOS 8.0+ if that matters. Here's my code and what it looks like now:

let searchBar = UISearchBar()
searchBar.placeholder = "Search"
searchBar.searchBarStyle = UISearchBarStyle.Minimal
searchBar.tintColor = UIColor.whiteColor()

a busy cat

I'd like for the search and magnifying glass to be white, or perhaps a dark green.

Kyle Bashour
  • 1,327
  • 2
  • 13
  • 20

12 Answers12

106

Details

  • Xcode Version 11.0 (11A420a), iOS 13, swift 5

Solution

import UIKit

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 = 6
        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 = "txt"
                    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)
        setValue(label, forKey: "placeholderLabel")
    }

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

Full Sample

import UIKit

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

        let searchBar = UISearchBar(frame: CGRect(x: 0, y: 20, width: UIScreen.main.bounds.width, height: 44))
        searchBar.searchBarStyle = .default
        view.addSubview(searchBar)

        searchBar.placeholder = "placeholder"
        searchBar.set(textColor: .brown)
        searchBar.setTextField(color: UIColor.green.withAlphaComponent(0.3))
        searchBar.setPlaceholder(textColor: .white)
        searchBar.setSearchImage(color: .white)
        searchBar.setClearButton(color: .red)
    }
}

Result

enter image description here enter image description here

Vasily Bodnarchuk
  • 24,482
  • 9
  • 132
  • 127
  • 2
    I've been trying to get this to work on iOS 11 when SearchBar is within the NavigationBar without success. Any ideas? – Renato Nov 07 '17 at 20:05
  • Hello Renato. I can't understand your problem. You can't add SearchBar to NavigationBar? – Vasily Bodnarchuk Nov 08 '17 at 06:00
  • Thank you so much! – Mikhail Sein Feb 28 '18 at 11:59
  • Magnifying glass and search placeholder not in centre as per your output. – Gautam Sareriya May 05 '18 at 09:55
  • 3
    This solution looks great, but for `UISearchController` you need to subclass both `UISearchController` and it's `UISearchBar`. Even then, the clear button coloring still doesn't work. Any ideas why? – Kevin_TA May 18 '18 at 17:55
  • This is a good solution but it can easily be broken if Apple decides to change the internal subview structure of UISearchBar in a future iOS release. I'm amazed they don't expose simple appearance customisations like this as a public API. – feb Jun 04 '18 at 11:32
  • @VasilyBodnarchuk Great answer. In swift 4 I think NSForegroundColorAttributeName has been changed to NSAttributedStringKey.foregroundColor. – Vineeth Joseph Jun 26 '18 at 11:53
  • 1
    Great Solution! One issue is `ClearButtonColor` is not working in iOS 12.0. – Chintan Shah Oct 26 '18 at 12:14
  • @VasilyBodnarchuk I think what @Renato meant was if you add the searchController straight to the navigationItem like this `navigationItem.searchController = searchController`, these methods don't work. I noticed however if you add the searchBar of the searchController to the titleView of the navigationItem like this, `navigationItem.titleView = searchController.searchBar`, it works. – Isuru Jul 29 '19 at 11:54
  • @Isuru It can be true. Because `searchController` has own `searchBar`. In this case I suggest to inherit `searchController` and updated its `searchBar`. – Vasily Bodnarchuk Jul 29 '19 at 12:10
  • Searchbar's placeholder color is not changing on iOS 13. – Soumen Sep 16 '19 at 11:50
33

If you have a custom image you could use, you can set the image and change the placeholder text color using something similar to the following:

[searchBar setImage:[UIImage imageNamed:@"SearchWhite"] forSearchBarIcon:UISearchBarIconSearch state:UIControlStateNormal];

UITextField *searchTextField = [searchBar valueForKey:@"_searchField"];    
if ([searchTextField respondsToSelector:@selector(setAttributedPlaceholder:)]) {
    UIColor *color = [UIColor purpleColor];
    [searchTextField setAttributedPlaceholder:[[NSAttributedString alloc] initWithString:@"Search" attributes:@{NSForegroundColorAttributeName: color}]];
}

In that example I used purpleColor, instead you can use the + (UIColor *)colorWithRed:(CGFloat)red green:(CGFloat)green blue:(CGFloat)blue alpha:(CGFloat)alpha method to create your custom dark green color.

EDIT: I just realized you were writing it in swift... duh. Quickly typed this out so I didn't leave the answer in just Obj-C.

    searchBar.setImage(UIImage(named: "SearchWhite"), forSearchBarIcon: UISearchBarIcon.Search, state: UIControlState.Normal);

    var searchTextField: UITextField? = searchBar.valueForKey("searchField") as? UITextField
    if searchTextField!.respondsToSelector(Selector("attributedPlaceholder")) {
        var color = UIColor.purpleColor()
        let attributeDict = [NSForegroundColorAttributeName: UIColor.purpleColor()]
        searchTextField!.attributedPlaceholder = NSAttributedString(string: "search", attributes: attributeDict)
    }

Swift 3.0

    var searchTextField: UITextField? = searchBar.value(forKey: "searchField") as? UITextField
    if searchTextField!.responds(to: #selector(getter: UITextField.attributedPlaceholder)) {
        let attributeDict = [NSForegroundColorAttributeName: UIColor.white]
        searchTextField!.attributedPlaceholder = NSAttributedString(string: "Search", attributes: attributeDict)
    }
O-mkar
  • 5,430
  • 8
  • 37
  • 61
c_rath
  • 3,618
  • 2
  • 22
  • 14
  • This is perfect, thank you _so much_. I hadn't seen `searchBar.valueForKey("searchField") as? UITextField` before, which is brilliant. Any chance you know how to change the clear button (the little round x) as well? – Kyle Bashour Mar 31 '15 at 21:45
  • I would imagine it would be the same way as the setImage but instead of using the UISearchBarIcon.Search, you would instead use .Clear??? I haven't actually tried that though. Outside of that I know the actual word "Cancel" is affected by the searchBar.tintColor... – c_rath Mar 31 '15 at 22:27
  • 10
    Accessing private properties or APIs is a sure way to get your app rejected. – Iulian Onofrei Aug 27 '15 at 15:06
  • don't use private APIs – netshark1000 Mar 01 '16 at 07:39
12

Swift 3: If you want to change the placeholder, clearbutton and magnifier glass

    let textFieldInsideSearchBar = searchBar.value(forKey: "searchField") as? UITextField
    textFieldInsideSearchBar?.textColor = UIColor.white

    let textFieldInsideSearchBarLabel = textFieldInsideSearchBar!.value(forKey: "placeholderLabel") as? UILabel
    textFieldInsideSearchBarLabel?.textColor = UIColor.white

    let clearButton = textFieldInsideSearchBar?.value(forKey: "clearButton") as! UIButton
    clearButton.setImage(clearButton.imageView?.image?.withRenderingMode(.alwaysTemplate), for: .normal)
    clearButton.tintColor = UIColor.white

    let glassIconView = textFieldInsideSearchBar?.leftView as? UIImageView

    glassIconView?.image = glassIconView?.image?.withRenderingMode(.alwaysTemplate)
    glassIconView?.tintColor = UIColor.white
phitsch
  • 823
  • 13
  • 12
  • self.searcBar.setTextFieldColor(color: .white) not working why ? still showing light lightGray color, but other colors are working but white not working any idea ? .... – Ansal Antony Jul 17 '17 at 11:40
3

You can change the color of the text without violating the private api rule:

UILabel.appearanceWhenContainedInInstancesOfClasses([UITextField.self]).textColor = UIColor.whiteColor()
netshark1000
  • 7,245
  • 9
  • 59
  • 116
2

I could not make it work properly with any of the above solutions.

I created the following UISearchBar category which works properly on iOS 8.4 and 10.3:

UISearchBar+PlaceholderColor.h

#import <UIKit/UIKit.h>

@interface UISearchBar (PlaceholderColor)

- (void)setPlaceholderColor:(UIColor *)placeholderColor;

@end

UISearchBar+PlaceholderColor.m

#import "UISearchBar+PlaceholderColor.h"

@implementation UISearchBar (PlaceholderColor)

- (void)setPlaceholderColor:(UIColor *)placeholderColor {
    UILabel *labelView = [self searchBarTextFieldLabelFromView:self];
    [labelView setTextColor:placeholderColor];
}

- (UILabel *)searchBarTextFieldLabelFromView:(UIView *)view {
    for (UIView *v in [view subviews]) {
        if ([v isKindOfClass:[UILabel class]]) {
            return (UILabel *)v;
        }

        UIView *labelView = [self searchBarTextFieldLabelFromView:v];
        if (labelView) {
            return (UILabel *)labelView;
        }
    }

    return nil;
}

@end

USAGE:

[mySearchBar setPlaceholderColor:[UIColor redColor]];

IMPORTANT NOTE:

Make sure that you call setPlaceholderColor: AFTER your UISearchBar has been added to the view and has created its view hierarchy.

If you open your search bar programmatically, call it AFTER your becomeFirstResponder call, as such:

[mySearchBar becomeFirstResponder];
[searchBar setPlaceholderColor:[UIColor redColor]];

Otherwise, if you are leveraging UISearchBarDelegate:

- (void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar {
    [searchBar setPlaceholderColor:[UIColor redColor]];
}
m_katsifarakis
  • 1,777
  • 1
  • 21
  • 27
2

I made a Swift 4.1 search bar extension:

import Foundation
import UIKit
extension UISearchBar{
    func setTextField(placeHolderColor:UIColor = .gray,placeHolder:String = "Search Something",textColor:UIColor = .white,backgroundColor:UIColor = .black,
                      placeHolderFont:UIFont = UIFont.systemFont(ofSize: 12.0),
                      textFont:UIFont =  UIFont.systemFont(ofSize: 12.0) ){
        for item in self.subviews{
            for mainView in (item as UIView).subviews{
                mainView.backgroundColor = backgroundColor
                if mainView is UITextField{
                    let textField = mainView as? UITextField
                    if let _textF = textField{
                        _textF.text = "success"
                        _textF.textColor = textColor
                        _textF.font      = textFont
                        _textF.attributedPlaceholder = NSMutableAttributedString.init(string: placeHolder, attributes: [NSAttributedStringKey.foregroundColor : placeHolderColor,
                                                                                                                               NSAttributedStringKey.font : placeHolderFont])
                    }
                }
            }
        }
    }
}

You can use this for your searchBar like this :

controller.searchBar.setTextField(placeHolderColor: .white,
 placeHolder: "Search A Pet",
 textColor: .white,
 backgroundColor: .green,
 placeHolderFont: UIFont.systemFont(ofSize: 14.0),
 textFont: UIFont.systemFont(ofSize: 14.0))
Undo
  • 25,519
  • 37
  • 106
  • 129
Abhimanyu Daspan
  • 1,095
  • 16
  • 20
2
extension UISearchBar {
    var textField: UITextField? { return value(forKey: "searchField") as? UITextField }
    var placeholderLabel: UILabel? { return textField?.value(forKey: "placeholderLabel") as? UILabel }
    var icon: UIImageView? { return textField?.leftView as? UIImageView }
    var iconColor: UIColor? {
        get {
            return icon?.tintColor
        }
        set {
            icon?.image = icon?.image?.withRenderingMode(.alwaysTemplate)
            icon?.tintColor = newValue
        }
    }
}
Geva
  • 820
  • 6
  • 14
1

I found a way to change the textfiled in search bar. It works in swift 3 and Xcode8.

Firstly, subclass the UISearchBar class.

class CustomSearchBar: UISearchBar {

UISearchBar include a view which has the important textfield you wanted. Following function get the index in subviews.

func indexOfSearchFieldInSubviews() -> Int! {
    var index: Int!
    let searchBarView = subviews[0]
    for (i, subview) in searchBarView.subviews.enumerated() {
        if subview.isKind(of: UITextField.self) {
            index = i
            break
        }
    }
    return index
}


override func draw(_ rect: CGRect) {
    // Find the index of the search field in the search bar subviews.
    if let index = indexOfSearchFieldInSubviews() {
        // Access the search field
        let searchField: UITextField = subviews[0].subviews[index] as! UITextField
        // Set its frame.
        searchField.frame = CGRect(x: 5, y: 5, width: frame.size.width - 10, height: frame.size.height - 10)
        // Set the font and text color of the search field.
        searchField.font = preferredFont
        searchField.textColor = preferredTextColor

        // Set the placeholder and its color
        let attributesDictionary = [NSForegroundColorAttributeName: preferredPlaceholderColor.cgColor]
        searchField.attributedPlaceholder = NSAttributedString(string: preferredPlaceholder, attributes: attributesDictionary)

        // Set the background color of the search field.
        searchField.backgroundColor = barTintColor
    }

    super.draw(rect)
}

There you go, enjoy for this.

Jerome
  • 2,114
  • 24
  • 24
  • Do you have the source code for this? I am having trouble understanding this. – ethanfox27 May 04 '17 at 01:51
  • Hi @ethanfox27, here is the source code, hope for help. https://github.com/JeromeTW/XZY-s-Blog/blob/master/ReadMe.txt https://github.com/JeromeTW/XZY-s-Blog/blob/master/CustomSearchBar.swift – Jerome May 04 '17 at 02:24
1

Small update to Vasily Bodnarchuk's great answer.

Swift 4+

NSForegroundColorAttributeName has been changed to NSAttributedStringKey.foregroundColor

Mike Carpenter
  • 410
  • 4
  • 12
0

For anybody just trying to change the text and having trouble seeing the updated placeholder, it worked for me by putting it here instead of viewDidLoad.

- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];
    self.searchBar.placeholder = @"My Custom Text";
}
skensell
  • 1,421
  • 12
  • 21
0

Just found this thread, sorry about the bump given the age. However it helped me in trying to fix my issue, and adding onto @m_katsifarakis answer, I added more to his existing category.

Adding additional color + font support for both placeholder and input text.

Only tested on iOS 13 as that's all I am supporting.

@interface UISearchBar (ColorandFont)
- (void)setPlaceholderColor:(UIColor *)placeholderColor setPlaceholderFont:(UIFont *)placeholderFont;
- (void)setTextColor:(UIColor *)textColor setTextFont:(UIFont *)textFont;
@end
@implementation UISearchBar (ColorandFont)

//Placeholder Color
- (void)setPlaceholderColor:(UIColor *)placeholderColor setPlaceholderFont:(UIFont *)placeholderFont{
    UILabel *labelView = [self placeholderText:self];
    [labelView setTextColor:placeholderColor];
    [labelView setFont:placeholderFont];
}
- (UILabel *)placeholderText:(UIView *)view {
    for (UIView *v in [view subviews]) {
        if ([v isKindOfClass:[UILabel class]]) {
            return (UILabel *)v;
        }

        UIView *labelView = [self placeholderText:v];
        if (labelView) {
            return (UILabel *)labelView;
        }
    }
    return nil;
}

//Text Color
- (void)setTextColor:(UIColor *)textColor setTextFont:(UIFont *)textFont{
    UITextField *textView = [self searchBarText:self];
    [textView setTextColor:textColor];
    [textView setFont:textFont];
}
- (UITextField *)searchBarText:(UIView *)view {
    for (UIView *v in [view subviews]) {
        if ([v isKindOfClass:[UITextField class]]) {
            return (UITextField *)v;
        }

        UIView *textView = [self searchBarText:v];
        if (textView) {
            return (UITextField *)textView;
        }
    }
    return nil;
}
@end

Usage (I used in viewDidLoad and Appear) given my current setup, results may vary:

[self.searchBar setPlaceholderColor:[UIColor lightTextColor] setPlaceholderFont:[UIFont italicSystemFontOfSize:13]];
[self.searchBar setTextColor:[UIColor whiteColor] setTextFont:[UIFont systemFontOfSize:16]];
ChrisOSX
  • 724
  • 2
  • 11
  • 28
-3

In Swift 4, assuming your search controller is set like this:

let searchController = UISearchController(searchResultsController: nil)

then set placeholder text as follows:

searchController.searchBar.placeholder = "Here is my custom text"
D. Rothschild
  • 659
  • 9
  • 14