56

I have an auto-generated clear button on my UITextfield, with the default blue tint color. I cannot change the tint color to white. I have tried modifying the storyboard and code without success, and I do not want to use a custom image.

How can I change the default clear button tint color without using a custom image?

clicked

Randomblue
  • 112,777
  • 145
  • 353
  • 547
Niels Robben
  • 1,637
  • 4
  • 17
  • 24
  • 2
    Looks like global tint isn't honored for the UITextField clear button (for whatever reason!), you can check out [this answer](http://stackoverflow.com/questions/18207893/how-to-show-xbuttonclear-button-always-visible-in-uisearchbar) and [this answer](http://stackoverflow.com/questions/10274210/uitextfield-clearbuttonmode-color) to see how others are doing it. Seems you have to create your own button. – radiovisual Jan 14 '15 at 14:27

25 Answers25

78

Here you go!

A TintTextField.

Using no custom image, or added buttons etc.

Image of UITextField with white tint color for cancel button

class TintTextField: UITextField {

     var tintedClearImage: UIImage?

     required init(coder aDecoder: NSCoder) {
       super.init(coder: aDecoder)
       self.setupTintColor()
     }

     override init(frame: CGRect) {
       super.init(frame: frame)
       self.setupTintColor()
     }

     func setupTintColor() {
       self.borderStyle = UITextField.BorderStyle.roundedRect
       self.layer.cornerRadius = 8.0
       self.layer.masksToBounds = true
       self.layer.borderColor = self.tintColor.cgColor
       self.layer.borderWidth = 1.5
       self.backgroundColor = .clear
       self.textColor = self.tintColor
     }

    override func layoutSubviews() {
        super.layoutSubviews()
        self.tintClearImage()
    }

    private func tintClearImage() {
        for view in subviews {
            if view is UIButton {
                let button = view as! UIButton
                if let image = button.image(for: .highlighted) {
                    if self.tintedClearImage == nil {
                        tintedClearImage = self.tintImage(image: image, color: self.tintColor)
                    }
                    button.setImage(self.tintedClearImage, for: .normal)
                    button.setImage(self.tintedClearImage, for: .highlighted)
                }
            }
        }
    }

    private func tintImage(image: UIImage, color: UIColor) -> UIImage {
        let size = image.size

        UIGraphicsBeginImageContextWithOptions(size, false, image.scale)
        let context = UIGraphicsGetCurrentContext()
        image.draw(at: .zero, blendMode: CGBlendMode.normal, alpha: 1.0)

        context?.setFillColor(color.cgColor)
        context?.setBlendMode(CGBlendMode.sourceIn)
        context?.setAlpha(1.0)

        let rect = CGRect(x: CGPoint.zero.x, y: CGPoint.zero.y, width: image.size.width, height: image.size.height)
        UIGraphicsGetCurrentContext()?.fill(rect)
        let tintedImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()

        return tintedImage ?? UIImage()
    }
 }
Joshua Wolff
  • 2,687
  • 1
  • 25
  • 42
Mikael Hellman
  • 2,664
  • 14
  • 22
  • 5
    Looks very promising. Can you provide an Objective-C version? – Randomblue May 29 '15 at 17:46
  • 3
    I can read Objective-C if I am in a good mood, but trust me you would not want to see me write Objective-C. Kind of new to iOS, and joined the train with Swift. :) – Mikael Hellman May 29 '15 at 19:18
  • Do not name custom text field class starting with `UI`. That prefix is reserved for Apple. – matt May 31 '15 at 14:28
  • This is very clever, and I think you should get the bounty - I love the use of `layoutSubviews` to solve the problem of when to access the clear button - but it has a slight inefficiency: you are setting the clear button image _every time the user edits_ (e.g. after every character is entered in the text field). – matt May 31 '15 at 14:30
  • You could cache the tinted images, so as not to recompute them every single time. – Randomblue May 31 '15 at 15:43
  • Added a simple cache mechanism. It is also limited in the sens that you would not get the component updated if you change the tintColor at a later stage... But I guess it's ok this stage. :) – Mikael Hellman May 31 '15 at 16:24
  • Thanks, VERY helpful! If anyone would like to use Mikael's clear button but without the border around the TintTextField, simply remove the following lines from setupTintColor().... 1. borderStyle = UITextBorderStyle.RoundedRect 2. layer.cornerRadius = 8.0 3. layer.masksToBounds = true 4. layer.borderColor = tintColor.CGColor 5. layer.borderWidth = 1.5 – Loic Verrall Jul 30 '15 at 09:20
  • Based on @MikaelHellman’s code I created an Objective-C category for `UITextField` that allows subclasses to easily share the text field’s `tintColor` with the clear button image -> https://gist.github.com/somethingkindawierd/a9f2cd3349685ab09757 – Jonathan Beebe Aug 06 '15 at 19:46
  • Just curious why you hardcoded a scale of 2 in your `UIGraphicsBeginImageContextWithOptions`. Perhaps it is best to use `image.scale` instead? – David Oct 16 '15 at 09:38
  • 1
    @David - Good catch. I copy pasted some code from another lab project where I wanted the scale to be hardcoded. I have updated the code here. – Mikael Hellman Oct 16 '15 at 10:20
  • 1
    Because the question asked for a way that did not use cusom images (that is prefered in my opinion). I do not know if there is a less "hacky" solution to achive this? – Mikael Hellman Jul 07 '16 at 09:38
  • 9
    No longer works in iOS 11. Button alpha is wrong with this solution – Raimundas Sakalauskas Nov 08 '17 at 16:43
  • Does not look good on iOS 11. I recommend checking my answer below and instead use my library LSCategories to set this with a single line of code :) – Leszek Szary Mar 27 '18 at 15:06
26

The reason you're having trouble doing this is that the clear button images are not tinted. They are just normal images.

The clear button is a button, internal to the UITextField. Like any button, it can have an image, and it does. In particular, it has two images: one for the Normal state, and one for the Highlighted state. The blue one to which the OP is objecting is the Highlighted image, and it can be captured by running this code at the time when the clear button is present:

    let tf = self.tf // the text view
    for sv in tf.subviews as! [UIView] {
        if sv is UIButton {
            let b = sv as! UIButton
            if let im = b.imageForState(.Highlighted) {
                // im is the blue x
            }
        }
    }

Once you capture it, you will find that it is a 14x14 double-resolution tiff image, and here it is:

enter image description here

In theory, you can change the image to a different color, and you can assign it as the text view's clear button's image for the highlighted state. But in practice this is not at all easy to do, because the button is not always present; you cannot refer to it when it is absent (it isn't just invisible; it is actually not part of the view hierarchy at all, so there's no way to access it).

Moreover, there is no UITextField API to customize the clear button.

Thus, the simplest solution is what is advised here: create a button with custom Normal and Highlighted images and supply it as the UITextField's rightView. You then set the clearButtonMode to Never (since you are using the right view instead) and the rightViewMode to whatever you like.

enter image description here

You will, of course, then have to detect a tap on this button and respond by clearing the text field's text; but this is easy to do, and is left as an exercise for the reader.

Community
  • 1
  • 1
matt
  • 515,959
  • 87
  • 875
  • 1,141
  • I don't suppose we could change the highlighted image's renderingMode to UIImageRenderingModeAlwaysTemplate? :) –  May 31 '15 at 06:38
  • @PetahChristian No effect. Try it. As I said at the start, these images are not being tinted. – matt May 31 '15 at 14:22
  • *nods.* I did see that, but didn't know if it was dependent on how the control rendered the clear button. Thanks for clearing that up! You'd think it would be a minor fix for Apple, but the original radar #14970472 from 2013 is still open. –  May 31 '15 at 17:27
  • The _highlighted_ state of the image seems to respond to `UITextField.appearance().tintColor`. The normal one is always light gray. – Nicolas Miari Feb 03 '17 at 03:48
  • Matt, do you perhaps have a transparent version of the clear button? So that I add it to my project and change it to a different color. – Cesare Jan 30 '18 at 21:36
24

For Swift 4, add this to a subclass of UITextField:

import UIKit

class CustomTextField: UITextField {

    override func layoutSubviews() {
        super.layoutSubviews()

        for view in subviews {
            if let button = view as? UIButton {
                button.setImage(button.image(for: .normal)?.withRenderingMode(.alwaysTemplate), for: .normal)
                button.tintColor = .white
            }
        }
    }
}
Justin Vallely
  • 5,932
  • 3
  • 30
  • 44
CW0007007
  • 5,681
  • 4
  • 26
  • 31
15

Basing on @Mikael Hellman response I've prepared similar implementation of UITextField subclass for Objective-C. The only difference is that I allow to have separate colors for Normal and Highlighted states.

.h file

#import <UIKit/UIKit.h>


@interface TextFieldTint : UITextField

-(void) setColorButtonClearHighlighted:(UIColor *)colorButtonClearHighlighted;
-(void) setColorButtonClearNormal:(UIColor *)colorButtonClearNormal;

@end

.m file

#import "TextFieldTint.h"

@interface TextFieldTint()

@property (nonatomic,strong) UIColor *colorButtonClearHighlighted;
@property (nonatomic,strong) UIColor *colorButtonClearNormal;

@property (nonatomic,strong) UIImage *imageButtonClearHighlighted;
@property (nonatomic,strong) UIImage *imageButtonClearNormal;


@end

@implementation TextFieldTint


-(void) layoutSubviews
{
    [super layoutSubviews];
    [self tintButtonClear];
}

-(void) setColorButtonClearHighlighted:(UIColor *)colorButtonClearHighlighted
{
    _colorButtonClearHighlighted = colorButtonClearHighlighted;
}

-(void) setColorButtonClearNormal:(UIColor *)colorButtonClearNormal
{
    _colorButtonClearNormal = colorButtonClearNormal;
}

-(UIButton *) buttonClear
{
    for(UIView *v in self.subviews)
    {
        if([v isKindOfClass:[UIButton class]])
        {
            UIButton *buttonClear = (UIButton *) v;
            return buttonClear;
        }
    }
    return nil;
}



-(void) tintButtonClear
{
    UIButton *buttonClear = [self buttonClear];

    if(self.colorButtonClearNormal && self.colorButtonClearHighlighted && buttonClear)
    {
        if(!self.imageButtonClearHighlighted)
        {
            UIImage *imageHighlighted = [buttonClear imageForState:UIControlStateHighlighted];
            self.imageButtonClearHighlighted = [[self class] imageWithImage:imageHighlighted
                                                                  tintColor:self.colorButtonClearHighlighted];
        }
        if(!self.imageButtonClearNormal)
        {
            UIImage *imageNormal = [buttonClear imageForState:UIControlStateNormal];
            self.imageButtonClearNormal = [[self class] imageWithImage:imageNormal
                                                             tintColor:self.colorButtonClearNormal];
        }

        if(self.imageButtonClearHighlighted && self.imageButtonClearNormal)
        {
            [buttonClear setImage:self.imageButtonClearHighlighted forState:UIControlStateHighlighted];
            [buttonClear setImage:self.imageButtonClearNormal forState:UIControlStateNormal];
        }
    }
}


+ (UIImage *) imageWithImage:(UIImage *)image tintColor:(UIColor *)tintColor
{
    UIGraphicsBeginImageContextWithOptions(image.size, NO, 0.0);
    CGContextRef context = UIGraphicsGetCurrentContext();

    CGRect rect = (CGRect){ CGPointZero, image.size };
    CGContextSetBlendMode(context, kCGBlendModeNormal);
    [image drawInRect:rect];

    CGContextSetBlendMode(context, kCGBlendModeSourceIn);
    [tintColor setFill];
    CGContextFillRect(context, rect);

    UIImage *imageTinted  = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return imageTinted;
}
@end
MP23
  • 1,763
  • 20
  • 25
15

In Swift you can write the extension and use on any textfield in your project.

extension UITextField {
    @objc func modifyClearButton(with image : UIImage) {
        let clearButton = UIButton(type: .custom)
        clearButton.setImage(image, for: .normal)
        clearButton.frame = CGRect(x: 0, y: 0, width: 15, height: 15)
        clearButton.contentMode = .scaleAspectFit
        clearButton.addTarget(self, action: #selector(UITextField.clear(_:)), for: .touchUpInside)
        rightView = clearButton
        rightViewMode = .whileEditing
    }

    @objc func clear(_ sender : AnyObject) {
    if delegate?.textFieldShouldClear?(self) == true {
        self.text = ""
        sendActions(for: .editingChanged)
    }
}
}
Community
  • 1
  • 1
Mohammad Sadiq
  • 5,070
  • 28
  • 29
  • This works great but it's a closer match to the original button if the frame's width is set to 27 with the supplied image as 15x15 (@1x) and the content mode set to UIViewContentModeCenter. – Leon Jan 02 '18 at 16:28
11

Swift 5 solution:

   if let clearButton = yourTextField.value(forKey: "_clearButton") as? UIButton {
       let templateImage = clearButton.imageView?.image?.withRenderingMode(.alwaysTemplate)
       clearButton.setImage(templateImage, for: .normal)
       clearButton.tintColor = .darkGray
   }
Veronika Babii
  • 324
  • 5
  • 18
7

The idea is get the clear button by key clearButton, then re-render the clear image with alwaysTemplate mode.

[Swift 4.2] Just made an extension for UITextField here:

extension UITextField {
    var clearButton: UIButton? {
        return value(forKey: "clearButton") as? UIButton
    }

    var clearButtonTintColor: UIColor? {
        get {
            return clearButton?.tintColor
        }
        set {
            let image =  clearButton?.imageView?.image?.withRenderingMode(.alwaysTemplate)
            clearButton?.setImage(image, for: .normal)
            clearButton?.tintColor = newValue
        }
    }
}

But the problem with this solution is the clear button's image is nil at the time you're calling to set tint color.

So for every one is using RxSwift to observe the image in clear button.

import RxSwift

extension UITextField {
    var clearButton: UIButton? {
        return value(forKey: "clearButton") as? UIButton
    }

    var clearButtonTintColor: UIColor? {
        get {
            return clearButton?.tintColor
        }
        set {
            _ = rx.observe(UIImage.self, "clearButton.imageView.image")
                .takeUntil(rx.deallocating)
                .subscribe(onNext: { [weak self] _ in
                    let image = self?.clearButton?.imageView?.image?.withRenderingMode(.alwaysTemplate)
                    self?.clearButton?.setImage(image, for: .normal)
                })
            clearButton?.tintColor = newValue
        }
    }
}
Tai Le
  • 8,530
  • 5
  • 41
  • 34
  • This is the way to go if you don't want a custom image. – heyfrank Nov 18 '19 at 15:35
  • for key "clearButton" works for ios 14, but it dont work for ios 12.4.. It return nil you can look my solution for your suggestion https://stackoverflow.com/questions/50383723/can-i-change-colour-image-uisearchbar-clear-button/68210154#68210154 – Ucdemir Jul 01 '21 at 13:22
5

You can use KVO to access the clear button and update it:

    UIButton *clearButton = [myTextField valueForKey:@"_clearButton"]
    if([clearButton respondsToSelector:@selector(setImage:forState:)]){

        //ensure that the app won't crash in the future if _clearButton reference changes to a different class instance
        [clearButton setImage:[UIImage imageNamed:@"MyImage.png"] forState:UIControlStateNormal];

    }

Note: This solution is not future proof - if Apple changes the implementation of of the clear button this will gracefully stop working.

Brody Robertson
  • 8,506
  • 2
  • 47
  • 42
  • **Absolutely terrible idea.** We used to do something like this on UIAlertViews to access the buttons. When apple changed their implementation, all in app purchases stopped working and we were left with weeks of "repair work". `This solution may not be future proof` isn't enough of a warning ;) – James Webster Nov 06 '15 at 09:52
  • 1
    @James Webster, understood that the implementation of UITextField may change but we are customizing a clear button - not exactly critical functionality. The worst thing that is going to happen is that the clear button doesn't get customized. – Brody Robertson Nov 06 '15 at 20:35
  • 3
    that's not true. The app could crash. If you have this text box on a login form, users suddenly can't use your app anymore – James Webster Nov 06 '15 at 21:46
  • 1
    @James Webster, how is this going to crash? valueForKey will return nil if the button does not exist and it is safe to message nil. Apple would have to replace _clearButton with a different class. I'll update my answer to use respondsToSelector to make it 100% safe – Brody Robertson Nov 08 '15 at 17:17
  • It works but I would say it is quite risky. valueForKey can crash if valueForUndefinedKey is not implemented. You can check that for example it crashes if you try to use it on non existing key on UIViewController object (NSUnknownKeyException). It seems that for UIButton it currently does not crash for non exisitng keys so probably UIButton implements valueForUndefinedKey however I would not assume that it will always implement that in future so in case Apple changes it then it might crash. – Leszek Szary Mar 25 '18 at 20:01
5

You can use your custom icon and it works in iOS 11,

searchBar.setImage(UIImage(named: "ic_clear"), for: .clear, state: .normal)
Kosrat D. Ahmad
  • 468
  • 8
  • 14
4

It could be even easier than the highest rated answer, available for iOS 7 and later.

@interface MyTextField

@end

@implementation MyTextField

- (void)layoutSubviews {
    [super layoutSubviews];

    for (UIView *subView in self.subviews) {
        if ([subView isKindOfClass:[UIButton class]]) {
            UIButton *button = (UIButton *)subView;
            [button setImage:[[button imageForState:UIControlStateNormal] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]
                    forState:UIControlStateNormal];
            button.tintColor = self.tintColor;
        }
    }
}

@end
CocoaBob
  • 533
  • 4
  • 9
4

Here is Swift 3 updated solution:

extension UITextField {
    func modifyClearButtonWithImage(image : UIImage) {
        let clearButton = UIButton(type: .custom)
        clearButton.setImage(image, for: .normal)
        clearButton.frame = CGRect(x: 0, y: 0, width: 15, height: 15)
        clearButton.contentMode = .scaleAspectFit
        clearButton.addTarget(self, action: #selector(self.clear(sender:)), for: .touchUpInside)
        self.rightView = clearButton
        self.rightViewMode = .whileEditing
    }

    func clear(sender : AnyObject) {
        self.text = ""
    }
}

Enjoy ;)

Bonnke
  • 896
  • 1
  • 12
  • 24
4

like @Brody Robertson answer, here is the Swift 5 version, it works for me:

let textField = UITextField()
if let button = textField.value(forKey: "clearButton") as? UIButton {
  button.tintColor = .white
  button.setImage(UIImage(named: "yourImage")?.withRenderingMode(.alwaysTemplate), for: .normal)
}

Note: You need to use your icon replace yourImage, or if your target is iOS 13.0 or above, you could replace the method UIImage(named:) with UIImage(systemName: "xmark.circle.fill"). This clear icon is ready for you by Apple in iOS 13.0 or above. I hope this will help! Good luck!

varrtix
  • 61
  • 4
3

I tried a lot of answers until found this solution based in @Mikael Hellman solution. This solution is using Swift 4.2.

The idea is the same:

Using no custom image, or added buttons etc.

And using a custom class that extends UITextField.

class TintClearTextField: UITextField {

    private var updatedClearImage = false

    override func layoutSubviews() {
        super.layoutSubviews()
        tintClearImage()
    }

    private func tintClearImage() {
        if updatedClearImage { return }

        if let button = self.value(forKey: "clearButton") as? UIButton,
            let image = button.image(for: .highlighted)?.withRenderingMode(.alwaysTemplate) {
            button.setImage(image, for: .normal)
            button.setImage(image, for: .highlighted)
            button.tintColor = .white

            updatedClearImage = true
        }
    }
}

You don't need the updatedClearImage, but have in mind that you'll be performing all the logics in every character addition.

I don't even need to set the tintColor to get the result I was looking for. Try commenting that line before setting to your color.

If it doesn't looks like you want, change the .white with your desired color, and that's all.

PS.: I have a field that is already populated in my initial screen, and for this only one the color change with tintColor happens milliseconds after showing the default item color, like a "glitch". I couldn't make it better, but since I'm not using the tintColor, that is ok for me.

Hope it helps :)

Endy Silveira
  • 211
  • 3
  • 13
2

If you use UIAppearance in your app you can set the tintColor for the clear button at runtime.

let textField = UITextField.appearance()
textField.tintColor = .green

At startup we call a class function in our AppDelegate that has a number of other controls that have their .appearance() configured in it.

Suppose your class to set the appearance on your app is called Beautyify you would create something like this:

@objc class Beautify: NSObject {
    class func applyAppearance() {
        let tableViewAppearance = UITableView.appearance()
        tableViewAppearance.tintColor = .blue

        let textField = UITextField.appearance()
        textField.tintColor = .green
    }
}

Then inside of AppDelegate didFinishLaunchingWithOptions just call it.

Beautify.applyAppearance()

It's a great way to configure the appearance of things in your application all at the same time.

Joshua Wolff
  • 2,687
  • 1
  • 25
  • 42
Rob Fahrni
  • 177
  • 1
  • 1
  • 10
2

This worked for me using objective-C. I pulled pieces from other threads on this topic and came up with this solution:

UIButton *btnClear = [self.textFieldUserID valueForKey:@"clearButton"];
[btnClear setImage:[UIImage imageNamed:@"facebookLoginButton"] forState:UIControlStateNormal];
2

In SWIFT 3 : this is working for me

    if let clearButton = self.textField.value(forKey: "_clearButton") as? UIButton {
        // Create a template copy of the original button image
        let templateImage =  clearButton.imageView?.image?.withRenderingMode(.alwaysTemplate)

        // Set the template image copy as the button image
        clearButton.setImage(templateImage, for: .normal)
        clearButton.setImage(templateImage, for: .highlighted)

        // Finally, set the image color
        clearButton.tintColor = .white
    }
Aqib Mumtaz
  • 4,936
  • 1
  • 36
  • 33
2

Swift 4, this works for me (change the tintColor to your own color):

var didSetupWhiteTintColorForClearTextFieldButton = false

private func setupTintColorForTextFieldClearButtonIfNeeded() {
    // Do it once only
    if didSetupWhiteTintColorForClearTextFieldButton { return }

    guard let button = yourTextField.value(forKey: "_clearButton") as? UIButton else { return }
    guard let icon = button.image(for: .normal)?.withRenderingMode(.alwaysTemplate) else { return }
    button.setImage(icon, for: .normal)
    button.tintColor = .white
    didSetupWhiteTintColorForClearTextFieldButton = true
}

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    setupTintColorForTextFieldClearButtonIfNeeded()
}

Need to call it in viewDidLayoutSubviews() so that you make sure it gets called eventually, because there're different clearButtonMode situations (always, whileEditing, etc.). I believe that these buttons are created lazily. So call it in viewDidLoad() mostly don't work.

aunnnn
  • 1,882
  • 2
  • 17
  • 23
2

Create this method.

func configureClearButtonColor() {

    guard let clearButton = textField.value(forKey: "_clearButton") as? UIButton else {
        return
    }
    let templateImage = clearButton.imageView?.image?.withRenderingMode(.alwaysTemplate)
    clearButton.setImage(templateImage, for: .normal)
    clearButton.setImage(templateImage, for: .highlighted)
    clearButton.tintColor = .white
}

And implement your UITextFieldDelegate, at the of textFieldDidEndEditing call the method. That change your image before create some text.

func textFieldDidEndEditing(_ textField: UITextField) {
    configureClearButtonColor()
}
YanSte
  • 10,661
  • 3
  • 57
  • 53
1

After going through all the answers and possibilities, I have found this simple and straight forward solution.

-(void)updateClearButtonColor:(UIColor *)color ofTextField:(UITextField *)textField {

    UIButton *btnClear = [textField valueForKey:@"_clearButton"];
    UIImage * img = [btnClear imageForState:UIControlStateNormal];

    if (img) {
        UIImage * renderingModeImage = [img imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
        [btnClear setImage:renderingModeImage forState:UIControlStateNormal];
        //-- Add states you want to update
        [btnClear setImage:renderingModeImage forState:UIControlStateSelected];
    }

    [btnClear setTintColor:color];
}


[self updateClearButtonColor:[UIColor whiteColor] ofTextField:self.textField];
GurPreet_Singh
  • 386
  • 2
  • 16
  • Doing `valueForKey:@"_clearButton"` is using a private API which may break in a future iOS release and might get your app rejected by Apple. – Felix Lapalme Feb 06 '18 at 22:11
1

Answer posted by matt above is correct. The clear button inside UITextField doesn't exist if not shown. One can try to access it right after UITextField performs its layoutSubviews and check for existence of the button. The easiest approach is to subclass a UITextField, override layoutSubviews and if the button is shown for the first time, store it's original image(s) for later use, and then during any subsequent show-ups apply a tint.

Below I'm showing you how to do this using extension because this way you are able to apply your custom tint to any UITextField, including those nested in ready classes like UISearchBar.

Have fun and give thumbs up if you like it :)

Swift 3.2

Here is the main extension:

import UIKit

extension UITextField {

    private struct UITextField_AssociatedKeys {
        static var clearButtonTint = "uitextfield_clearButtonTint"
        static var originalImage = "uitextfield_originalImage"
    }

    private var originalImage: UIImage? {
        get {
            if let cl = objc_getAssociatedObject(self, &UITextField_AssociatedKeys.originalImage) as? Wrapper<UIImage> {
                return cl.underlying
            }
            return nil
        }
        set {
            objc_setAssociatedObject(self, &UITextField_AssociatedKeys.originalImage, Wrapper<UIImage>(newValue), .OBJC_ASSOCIATION_RETAIN)
        }
    }

    var clearButtonTint: UIColor? {
        get {
            if let cl = objc_getAssociatedObject(self, &UITextField_AssociatedKeys.clearButtonTint) as? Wrapper<UIColor> {
                return cl.underlying
            }
            return nil
        }
        set {
            UITextField.runOnce
            objc_setAssociatedObject(self, &UITextField_AssociatedKeys.clearButtonTint, Wrapper<UIColor>(newValue), .OBJC_ASSOCIATION_RETAIN)
            applyClearButtonTint()
        }
    }

    private static let runOnce: Void = {
        Swizzle.for(UITextField.self, selector: #selector(UITextField.layoutSubviews), with: #selector(UITextField.uitextfield_layoutSubviews))
    }()

    private func applyClearButtonTint() {
        if let button = UIView.find(of: UIButton.self, in: self), let color = clearButtonTint {
            if originalImage == nil {
                originalImage = button.image(for: .normal)
            }
            button.setImage(originalImage?.tinted(with: color), for: .normal)
        }
    }

    func uitextfield_layoutSubviews() {
        uitextfield_layoutSubviews()
        applyClearButtonTint()
    }

}

Here are additional snippets used in the code above:

Nice wrapper for any stuff you would like to access object-wise:

class Wrapper<T> {
    var underlying: T?

    init(_ underlying: T?) {
        self.underlying = underlying
    }
}

A handful extension for finding nested subviews of any type:

extension UIView {

    static func find<T>(of type: T.Type, in view: UIView, includeSubviews: Bool = true) -> T? where T: UIView {
        if view.isKind(of: T.self) {
            return view as? T
        }
        for subview in view.subviews {
            if subview.isKind(of: T.self) {
                return subview as? T
            } else if includeSubviews, let control = find(of: type, in: subview) {
                return control
            }
        }
        return nil
    }

}

Extension for UIImage to apply a tint color

extension UIImage {

    func tinted(with color: UIColor) -> UIImage? {
        UIGraphicsBeginImageContextWithOptions(self.size, false, self.scale)
        color.set()
        self.withRenderingMode(.alwaysTemplate).draw(in: CGRect(origin: CGPoint(x: 0, y: 0), size: self.size))

        let result = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()

        return result
    }

}

...and finally Swizzling stuff:

class Swizzle {

    class func `for`(_ className: AnyClass, selector originalSelector: Selector, with newSelector: Selector) {
        let method: Method = class_getInstanceMethod(className, originalSelector)
        let swizzledMethod: Method = class_getInstanceMethod(className, newSelector)
        if (class_addMethod(className, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))) {
            class_replaceMethod(className, newSelector, method_getImplementation(method), method_getTypeEncoding(method))
        } else {
            method_exchangeImplementations(method, swizzledMethod)
        }
    }

}
1

Swift 4, clean and concise Subclass

import UIKit

class CustomTextField: UITextField {
    override func layoutSubviews() {
        super.layoutSubviews()
        for view in subviews where view is UIButton {
            (view as! UIButton).setImage(<MY_UIIMAGE>, for: .normal)
        }
    }
}
3vangelos
  • 501
  • 8
  • 15
1

Details

  • Xcode Version 10.1 (10B61)
  • Swift 4.2

Solution

import UIKit

extension UISearchBar {

    func getTextField() -> UITextField? { return value(forKey: "searchField") as? UITextField }

    func setClearButton(color: UIColor) {
        getTextField()?.setClearButton(color: color)
    }
}

extension UITextField {

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

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

Full sample

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        let textField = UITextField(frame: CGRect(x: 20, y: 20, width: 200, height: 44))
        view.addSubview(textField)
        textField.backgroundColor = .lightGray
        textField.clearButtonMode = .always
        textField.setClearButton(color: .red)

        let searchBar = UISearchBar(frame: CGRect(x: 20, y: 80, width: 200, height: 44))
        view.addSubview(searchBar)
        searchBar.backgroundColor = .lightGray
        searchBar.setClearButton(color: .red)
    }
}

Result

enter image description here

Vasily Bodnarchuk
  • 24,482
  • 9
  • 132
  • 127
1

Modification to @3vangelos solution, bypassing this for loop

for view in subviews where view is UIButton {
        (view as! UIButton).setImage(<MY_UIIMAGE>, for: .normal)
    }

My Modification:-

class CustomTextField:UITextField { 
override func layoutSubviews()
    {super.layoutSubviews()
    let clearButton = self.value(forKey: "clearButton") as? UIButton
    clearButton?.setImage(#imageLiteral(resourceName: "icons8-cancel.pdf"), for: .normal)
    clearButton?.tintColor = UIColor(<YOUR_COLOR>)
}

Same solution can be used in UISearchBar with some add on code:-

 override func viewWillAppear(_ animated: Bool)
{
    super.viewWillAppear(animated)
if let textField = agentsSearchBar.value(forKey: "searchField") as? UITextField
    {
let clearButton = textField.value(forKey: "clearButton") as? UIButton
clearButton?.setImage(#imageLiteral(resourceName: "icons8-cancel.pdf"), for: .normal)
clearButton?.tintColor = UIColor(<YOUR_COLOR>)
} }

image (icons8-cancel.pdf) can be downloaded at https://icons8.com/icon/set/clear-button/ios7# and added to your image assets with the following attributes enter image description here

Atka
  • 547
  • 4
  • 7
0

You can use my library LSCategories to do that with one line:

[textField lsSetClearButtonWithColor:[UIColor redColor] mode:UITextFieldViewModeAlways];

It does NOT use any private api, it does NOT search for original UIButton in UITextField subviews hierarchy and it does NOT require to subclass UITextField as some other answers here. Instead it uses rightView property to mimic system clear button therefore you do not need to worry that it will stop working in the future if Apple changes something. It works in Swift also.

Leszek Szary
  • 9,763
  • 4
  • 55
  • 62
0

You can change the color between white and grey for one view controller using:

override func viewDidLoad() {
    super.viewDidLoad()
    overrideUserInterfaceStyle = .light // .dark
}

To apply this change across your entire app, add this to your info.plist file:

<key>UIUserInterfaceStyle</key>
<string>Light</string>

Or

<key>UIUserInterfaceStyle</key>
<string>Dark</string>

Note: This will also change the keyboard and placeholder text color between dark and light.

Isaiah Turner
  • 2,574
  • 1
  • 22
  • 35