it-swarm.com.ru

Как изменить цвет оттенка кнопки очистки на UITextField

У меня есть автоматически сгенерированная кнопка очистки в моем поле UIText, с синим оттенком по умолчанию. Я не могу изменить цвет оттенка на белый. Я попытался изменить раскадровку и код безуспешно, и я не хочу использовать пользовательское изображение.

Как изменить цвет оттенка кнопки очистки по умолчанию без использования пользовательского изображения?

clicked

47
Niels Robben

Ну вот! 

TintTextField.

Не использовать пользовательское изображение или добавленные кнопки и т.д.

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

Причина, по которой у вас возникают проблемы, заключается в том, что изображения кнопок очистки не окрашены. Это просто нормальные изображения.

Кнопка очистки - это кнопка, встроенная в UITextField. Как и любая кнопка, она может иметь изображение, и оно есть. В частности, у него есть two изображения: одно для нормального состояния и одно для выделенного состояния. Синим цветом, на который возражает OP, является выделенное изображение, и его можно получить, запустив этот код в то время, когда присутствует кнопка очистки:

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

Как только вы сделаете снимок, вы обнаружите, что это изображение в формате TIFF с двойным разрешением 14x14, и вот оно:

enter image description here

Теоретически вы можете изменить изображение на другой цвет и назначить его в качестве изображения кнопки очистки текстового представления для выделенного состояния. Но на практике это совсем не легко сделать, потому что кнопка не всегда присутствует; вы не можете ссылаться на него, когда он отсутствует (он не просто невидим; на самом деле он вообще не является частью иерархии представлений, поэтому нет доступа к нему).

Кроме того, нет UITextField API для настройки кнопки очистки.

Таким образом, самое простое решение - это то, что рекомендуется здесь : создайте кнопку с пользовательскими обычными и выделенными изображениями и предоставьте ее как rightView UITextField. Затем вы устанавливаете для clearButtonMode значение Никогда (поскольку вместо этого вы используете правильный вид), а для rightViewMode - все, что вам нравится.

enter image description here

Затем вам, конечно, придется обнаружить нажатие на эту кнопку и ответить, очистив текст текстового поля; но это легко сделать, и оставлено в качестве упражнения для читателя.

22
matt

Основываясь на ответе @Mikael Hellman, я подготовил аналогичную реализацию подкласса UITextField для Objective-C. Разница лишь в том, что я позволяю иметь отдельные цвета для нормального и выделенного состояния.

.h файл

#import <UIKit/UIKit.h>


@interface TextFieldTint : UITextField

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

@end

.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

Для Swift 4 добавьте это в подкласс 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

В Swift вы можете написать расширение и использовать его в любом текстовом поле вашего проекта. 

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

Вы можете использовать KVO для доступа к кнопке очистки и ее обновления:

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

    }

Примечание: это решение не является будущим - если Apple изменит реализацию кнопки очистки, это изящно перестанет работать.

5
Brody Robertson

Вот обновленное решение 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 = ""
    }
}

Наслаждаться ;)

3
Bonnke

Это может быть даже проще, чем самый высокий рейтинг, доступный для iOS 7 и более поздних версий.

@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

Если вы используете UIAppearance в вашем приложении, вы можете установить tintColor для кнопки очистки во время выполнения.

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

При запуске мы вызываем функцию класса в нашем AppDelegate, который имеет ряд других элементов управления, для которых настроена их .appearance()

Предположим, ваш класс для установки внешнего вида в вашем приложении называется Beautyify, вы бы создали что-то вроде этого:

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

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

Тогда внутри AppDelegate didFinishLaunchingWithOptions просто позвоните.

Beautify.applyAppearance()

Это отличный способ настроить внешний вид вещей в вашем приложении одновременно.

2
Rob Fahrni

Это сработало для меня, используя цель-C. Я вытащил кусочки из других тем на эту тему и придумал это решение:

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

В Swift 3: это работает для меня

    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, это работает для меня (измените tintColor на свой собственный цвет):

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

Нужно вызвать его в viewDidLayoutSubviews(), чтобы убедиться, что он будет вызван в конечном итоге, потому что существуют разные ситуации clearButtonMode (always, whileEditing и т.д.). Я считаю, что эти кнопки создаются лениво. Так что назовите это в viewDidLoad() в основном не работает.

1
aunnnn

Swift 4, чистый и лаконичный подкласс

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

Вы можете использовать свой собственный значок, и он работает в iOS 11, 

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

Ответ от Matt выше является правильным. Кнопка очистки внутри UITextField не существует, если не показана. Можно попытаться получить к нему доступ сразу после того, как UITextField выполнит его layoutSubviews и проверит наличие кнопки . Самый простой подход - создать подкласс UITextField, переопределить layoutSubviews и, если кнопка отображается впервые, сохранить ее исходное изображение ( s) для последующего использования, а затем во время любых последующих посещений наносите оттенок.

Ниже я покажу вам, как сделать это, используя расширение, потому что таким образом вы сможете применить свой собственный оттенок к любому UITextField, включая вложенные в готовые классы, такие как UISearchBar.

Веселитесь и будьте готовы, если вам это нравится :)

Swift 3.2

Вот основное расширение:

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

}

Вот дополнительные фрагменты, используемые в приведенном выше коде:

Хорошая обертка для любого материала, к которому вы хотели бы получить доступ к объектам:

class Wrapper<T> {
    var underlying: T?

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

Небольшое расширение для поиска вложенных подпредставлений любого типа:

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
    }

}

Расширение для UIImage для применения оттенка

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
    }

}

... и, наконец, Swizzling вещи:

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

Изучив все ответы и возможности, я нашел это простое и понятное решение.

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

Создайте этот метод.

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
}

И реализуйте ваш UITextFieldDelegate при вызове метода textFieldDidEndEditing. Это изменить ваше изображение, прежде чем создавать текст.

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

Подробности

  • Версия Xcode 10.1 (10B61)
  • Swift 4.2

Решение

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

Результат

 enter image description here

0
Vasily Bodnarchuk

Вы можете использовать мою библиотеку LSCategories , чтобы сделать это одной строкой:

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

Он НЕ использует какой-либо частный API, он НЕ ищет оригинальный UIButton в иерархии подпредставлений UITextField и НЕ требует подкласса UITextField, как некоторые другие ответы здесь. Вместо этого он использует свойство rightView для имитации кнопки очистки системы, поэтому вам не нужно беспокоиться о том, что она перестанет работать в будущем, если Apple что-то изменит. Это работает и в Swift.

0
Leszek Szary

Я пробовал много ответов, пока не нашел это решение, основанное на решении @Mikael Hellman. Это решение использует Swift 4.2 .

Идея та же:

Не использовать пользовательское изображение или добавленные кнопки и т.д.

И используя собственный класс, который расширяет 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
        }
    }
}

Вам не нужна updatedClearImage, но имейте в виду, что вы будете выполнять все логические операции при каждом добавлении символов.

Мне даже не нужно устанавливать tintColor, чтобы получить результат, который я искал. Попробуйте прокомментировать эту строку, прежде чем установить свой цвет.

Если он не выглядит так, как вы хотите, измените .white на нужный вам цвет, и все.

PS: у меня есть поле, которое уже заполнено на моем начальном экране, и для этого только одно изменение цвета с tintColor происходит через миллисекунды после отображения цвета элемента по умолчанию, например, «сбой». Я не мог сделать это лучше, но так как я не использую tintColor, это нормально для меня.

Надеюсь, поможет :)

0
Endy Silveira