web-dev-qa-db-fra.com

Comment changer la couleur de teinte du bouton d'effacement sur un UITextField

J'ai un bouton clair généré automatiquement sur mon UITextfield, avec la couleur de teinte bleue par défaut. Je ne peux pas changer la couleur de teinte en blanc. J'ai essayé de modifier le storyboard et le code sans succès et je ne souhaite pas utiliser d'image personnalisée.

Comment modifier la couleur de teinte par défaut du bouton clair sans utiliser d'image personnalisée?

clicked

47
Niels Robben

Voici! 

Un TintTextField.

Ne pas utiliser d'image personnalisée ou de boutons ajoutés, etc.

enter image description here

class TintTextField: UITextField {

  var tintedClearImage: UIImage?

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

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

  func setupTintColor() {
    clearButtonMode = UITextFieldViewMode.WhileEditing
    borderStyle = UITextBorderStyle.RoundedRect
    layer.cornerRadius = 8.0
    layer.masksToBounds = true
    layer.borderColor = tintColor.CGColor
    layer.borderWidth = 1.5
    backgroundColor = UIColor.clearColor()
    textColor = tintColor
  }

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

  private func tintClearImage() {
    for view in subviews as! [UIView] {
      if view is UIButton {
        let button = view as! UIButton
        if let uiImage = button.imageForState(.Highlighted) {
          if tintedClearImage == nil {
            tintedClearImage = tintImage(uiImage, tintColor)
          }
          button.setImage(tintedClearImage, forState: .Normal)
          button.setImage(tintedClearImage, forState: .Highlighted)
        }
      }
    }
  }
}


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

  UIGraphicsBeginImageContextWithOptions(size, false, image.scale)
  let context = UIGraphicsGetCurrentContext()
  image.drawAtPoint(CGPointZero, blendMode: CGBlendMode.Normal, alpha: 1.0)

  CGContextSetFillColorWithColor(context, color.CGColor)
  CGContextSetBlendMode(context, CGBlendMode.SourceIn)
  CGContextSetAlpha(context, 1.0)

  let rect = CGRectMake(
    CGPointZero.x,
    CGPointZero.y,
    image.size.width,
    image.size.height)
  CGContextFillRect(UIGraphicsGetCurrentContext(), rect)
  let tintedImage = UIGraphicsGetImageFromCurrentImageContext()
  UIGraphicsEndImageContext()

  return tintedImage
}
69
Mikael Hellman

La raison pour laquelle vous avez du mal à faire ceci est que les images des boutons clairs ne sont pas teintées. Ce ne sont que des images normales.

Le bouton Clear est un bouton interne à UITextField. Comme n'importe quel bouton, il peut avoir une image, et c'est le cas. En particulier, il a deux images: une pour l'état Normal et une pour l'état Surligné. La bleue à laquelle l'OP s'oppose est l'image surlignée, et elle peut être capturée en exécutant ce code au moment où le bouton d'effacement est présent:

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

Une fois que vous l'avez capturée, vous constaterez qu'il s'agit d'une image tiff 14x14 à double résolution, et la voici:

enter image description here

En théorie, vous pouvez changer la couleur de l'image et l'affecter à l'image du bouton en clair de la vue Texte pour l'état en surbrillance. Mais dans la pratique, cela n’est pas facile du tout, car le bouton n’est pas toujours présent; vous ne pouvez pas vous y référer quand il est absent (ce n'est pas simplement invisible; en fait, il ne fait pas partie de la hiérarchie des vues, il n'y a donc aucun moyen d'y accéder).

De plus, il n'y a pas d'API UITextField pour personnaliser le bouton d'effacement.

Ainsi, la solution la plus simple est ce qui est conseillé ici : créer un bouton avec des images personnalisées Normal et Surligné et le fournir sous la forme rightView de UITextField. Vous définissez ensuite la variable clearButtonMode sur Jamais (puisque vous utilisez plutôt la vue de droite) et la variable rightViewMode sur celle que vous préférez.

enter image description here

Bien sûr, vous devrez alors détecter un appui sur ce bouton et répondre en effaçant le texte du champ de texte; mais ceci est facile à faire et reste comme un exercice pour le lecteur.

22
matt

En me basant sur la réponse @Mikael Hellman, j'ai préparé une implémentation similaire de la sous-classe UITextField pour Objective-C. La seule différence est que je permet d'avoir des couleurs séparées pour les états Normal et En surbrillance.

fichier .h

#import <UIKit/UIKit.h>


@interface TextFieldTint : UITextField

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

@end

fichier .m

#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
15
MP23

Pour Swift 4, ajoutez ceci à une sous-classe de 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
            }
        }
    }
}
10
CW0007007

Dans Swift, vous pouvez écrire l'extension et l'utiliser sur n'importe quel champ de texte de votre projet. 

extension UITextField {
    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
    }

    func clear(_ sender : AnyObject) {
        self.text = ""
        sendActions(for: .editingChanged)
    }
}
10
Mohammad Sadiq

Vous pouvez utiliser KVO pour accéder au bouton d'effacement et le mettre à jour:

    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];

    }

Remarque: cette solution n'est pas à l'épreuve du temps. Si Apple modifie la mise en œuvre du bouton vide, elle ne fonctionnera plus normalement.

5
Brody Robertson

Voici la solution mise à jour Swift 3:

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 = ""
    }
}

Prendre plaisir ;)

3
Bonnke

Cela pourrait même être plus facile que la réponse la mieux notée, disponible pour iOS 7 et versions ultérieures.

@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
3
CocoaBob

Cela a fonctionné pour moi en utilisant object-C. J'ai tiré des morceaux d'autres discussions sur ce sujet et suis venu avec cette solution:

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

Dans Swift 3: ça marche pour moi

    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
    }
1
Aqib Mumtaz

Swift 4, cela fonctionne pour moi (changez la tintColor à votre propre couleur):

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

Vous devez l’appeler dans viewDidLayoutSubviews() pour vous assurer qu’il sera appelé ultérieurement, car il existe différentes situations clearButtonMode (always, whileEditing, etc.) Je crois que ces boutons sont créés paresseusement. Alors appelez-le dans viewDidLoad() la plupart du temps ne fonctionne pas.

1
aunnnn

Swift 4, sous-classe propre et concise

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)
        }
    }
}
1
3vangelos

Vous pouvez utiliser votre icône personnalisée et cela fonctionne dans iOS 11, 

searchBar.setImage(UIImage(named: "ic_clear"), for: .clear, state: .normal)
1
Kosrat D. Ahmad

La réponse postée par Matt ci-dessus est correcte. Le bouton d'effacement à l'intérieur de UITextField n'existe pas s'il n'est pas affiché. On peut essayer d'y accéder juste après que UITextField effectue ses layoutSubviews et vérifie l'existence du bouton . L'approche la plus simple consiste à sous-classer une UITextField, outrepasser layoutSubviews et, si le bouton est affiché pour la première fois, stocker son image d'origine ( s) pour une utilisation ultérieure, puis, lors de présentations ultérieures, appliquez une teinte.

Ci-dessous, je vous montre comment faire cela en utilisant une extension, car vous pouvez ainsi appliquer votre teinte personnalisée à n'importe quel UITextField, y compris ceux imbriqués dans des classes prêtes, comme UISearchBar.

Amusez-vous et donnez un coup de pouce si vous l'aimez :)

Swift 3.2

Voici l'extension principale:

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

}

Voici des extraits supplémentaires utilisés dans le code ci-dessus:

Belle enveloppe pour tout ce que vous souhaitez accéder à l'objet-sage:

class Wrapper<T> {
    var underlying: T?

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

Une poignée d'extensions pour trouver des sous-vues imbriquées de tout 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 pour UIImage pour appliquer une couleur de teinte

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
    }

}

... et enfin 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
MonsterSale

Après avoir parcouru toutes les réponses et possibilités, j'ai trouvé cette solution simple et directe.

-(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];
1
GurPreet_Singh

Créez cette méthode.

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
}

Et implémentez votre UITextFieldDelegate , à partir de textFieldDidEndEditing appelez la méthode. Cela change votre image avant de créer du texte.

func textFieldDidEndEditing(_ textField: UITextField) {
    configureClearButtonColor()
}
0
YannickSteph

Détails

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

Échantillon complet

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

Résultat

 enter image description here

0
Vasily Bodnarchuk

Vous pouvez utiliser ma bibliothèque LSCategories pour le faire avec une seule ligne:

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

Il n'utilise PAS les API privées, ne recherche PAS l'UIButton d'origine dans la hiérarchie de sous-vues UITextField et ne requiert PAS de sous-classe UITextField comme d'autres réponses ici. Au lieu de cela, il utilise la propriété rightView pour imiter le bouton d'effacement du système. Par conséquent, vous n'avez pas à craindre qu'il ne fonctionne plus si Apple modifie quelque chose. Cela fonctionne à Swift également.

0
Leszek Szary

J'ai essayé beaucoup de réponses jusqu'à ce que je trouve cette solution basée sur la solution @Mikael Hellman. Cette solution utilise Swift 4.2 .

L'idée est la même:

Ne pas utiliser d'image personnalisée ou de boutons ajoutés, etc.

Et en utilisant une classe personnalisée qui étend 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
        }
    }
}

Vous n'avez pas besoin de la variable updatedClearImage, mais gardez à l'esprit que vous exécuterez toutes les logiques dans chaque ajout de personnage.

Je n'ai même pas besoin de régler la tintColor pour obtenir le résultat que je cherchais. Essayez de commenter cette ligne avant de définir votre couleur.

Si cela ne vous ressemble pas, changez le .white avec la couleur de votre choix, et c'est tout.

PS .: J'ai un champ qui est déjà rempli dans mon écran initial, et pour celui-ci seulement, le changement de couleur avec tintColor se produit millisecondes après l'affichage de la couleur par défaut de l'élément, comme un "problème". Je ne pouvais pas faire mieux, mais comme je n'utilise pas la tintColor, ça me convient.

J'espère que ça aide :)

0
Endy Silveira