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?
Voici!
Un TintTextField.
Ne pas utiliser d'image personnalisée ou de boutons ajoutés, etc.
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
}
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:
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.
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.
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
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
}
}
}
}
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)
}
}
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.
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 ;)
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
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];
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
}
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.
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)
}
}
}
Vous pouvez utiliser votre icône personnalisée et cela fonctionne dans iOS 11,
searchBar.setImage(UIImage(named: "ic_clear"), for: .clear, state: .normal)
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)
}
}
}
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];
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()
}
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 }
}
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)
}
}
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.
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.
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 :)