Fattie
Fattie

Reputation: 12272

in iOS17 UIKit is there a way to change/affect the border color of an iOS17 UITextField (other than the mangle shown here)?

This is about iOS 17 only. The only way I could hack around and achieve something like this was

so

///True™ iOS look of the border, and you can change the color
class FixedUITextField: UITextField {
    
    var completed: Bool = true {
        didSet {
            fakeLayer.borderColor = completed ? rememberDefaultColor : UIColor.red.cgColor
        }
    }
    
    private var rememberDefaultColor: CGColor? = UIColor.gray.cgColor
    
    private lazy var fakeLayer: CALayer = {
        let v = RepairedCALayer()
        for found in layer.sublayers ?? [] {
            if found.borderWidth > 0 {
                layer.insertSublayer(v, above: found)
                v.backgroundColor = found.backgroundColor
                v.borderColor = found.borderColor
                v.borderWidth = found.borderWidth
                v.cornerRadius = found.cornerRadius
                v.cornerCurve = found.cornerCurve
                print("found and covered")
                rememberDefaultColor = found.borderColor
                return v
            }
        }
        // defaults ICO disaster
        v.backgroundColor = UIColor.systemBackground.cgColor
        v.borderColor = UIColor.blue.cgColor
        v.borderWidth = 1.0 / UIScreen.main.scale
        v.cornerRadius = 4.0
        v.cornerCurve = .circular
        layer.addSublayer(v)
        return v
    }()
    
    override func layoutSubviews() {
        super.layoutSubviews()
        fakeLayer.frame = bounds
    }
}

This is about iOS 17 only. Am I missing something really obvious?

This is about iOS 17 only. Even more issues on this time waste ...

Am I missing something really obvious? - is there a way to change the border color of a UITextField as is?


It looks like if you set the border style on a UITextField these days,

override func common() {
    super.common()
    borderStyle = .roundedRect
    layer.borderWidth = 1.0 / UIScreen.main.scale
    layer.cornerCurve = .circular
    layer.cornerRadius = 10

It just adds that to the standard border.

enter image description here

seen here

enter image description here

(tap for large)

If there was a way to turn off the mystery layer (or draw ?), that would be a work around, but I just cannot find a way to turn it off. (If you hide it, "they" turn it on again after a few frames! I don't know how to examine the source code to see what's going on.)

NB, from storyboard ...

If you're investigating this recall that is it begins from storyboard they set borderStyle = .roundedRect (it's .none if you just instantiate a text field), adding further confusion.

Upvotes: 0

Views: 275

Answers (2)

Fattie
Fattie

Reputation: 12272

Answer is: "it cannot be done"; here's a workaround

In iOS17 UIKit is there really ANY way at all to changethe border color of an iOS17 UITextField?

No.

You have two and a half possibilities:

  1. Use the mangle I give in the question, which will exactly copy their border and cover it with an exact match, which you can color as wished. (Do note though that if UIKit changes any quality of it thickness, color, whatever, it during the running of an app {perhaps for an animation, state change or whatever reason} you won't know about that and the look will change.)

  2. Very simply eliminate "their" border and use your own. The only trick to doing so is that you have to set the text rect (which is essentially setting the padding visually):


///Essentially, you can't CHANGE THE COLOR of the border in a UITextField.
///Here, we simply turn it off and recreate it strictly BY EYE.
///Don't expect this to match UITextField in any serious project.
class ManualBorderUIITextField: UIITextField {
    
    override func common() {
        super.common()
        borderStyle = .none
        layer.borderWidth = 2.0 / UIScreen.main.scale
        // In 2024 iOS it's 1 pixel there, I like 2
        layer.cornerCurve = .circular
        layer.cornerRadius = 4
        layer.borderColor = UIColor.green.cgColor
        // Choice is yours. In most real projects you'd be changing it per your use/states etc
    }
    
    ///Note that very confusingly UITextField will do the intrinsic size for you backwards from this, you don't have to.
    override func textRect(forBounds bounds: CGRect) -> CGRect {
        return bounds.insetBy(dx: 7.0, dy: 8.0)
        // When you kill the border style this padding becomes zero so you have to do it manually
        // In 2024 iOS it's possibly ~7/~6 there but hard to say exactly
    }
}

2.5. I guess if you are incredibly anal you could do "1" when you launch a text field, memorize all the values, and then use that in a "2" approach. (Again though, you wouldn't know about any changes "they" might make in theory during the run; you're very simply building a custom control like any other custom control :/ )

Upvotes: 0

DonMag
DonMag

Reputation: 77672

Set the text field's .borderStyle to .roundedRect, then set the desired border properties on the text field's .layer itself:

class RedTextFieldVC: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let tf = UITextField()
        tf.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(tf)
        
        let g = view.safeAreaLayoutGuide
        NSLayoutConstraint.activate([
            tf.widthAnchor.constraint(equalToConstant: 240.0),
            tf.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
            tf.centerXAnchor.constraint(equalTo: g.centerXAnchor),
        ])
        
        tf.borderStyle = .roundedRect
        
        tf.layer.borderColor = UIColor.red.cgColor
        tf.layer.borderWidth = 1.0 / UIScreen.main.scale
        tf.layer.cornerRadius = 4.0
        tf.layer.cornerCurve = .circular
        
        tf.text = "Hello Red Border"
        
    }
    
}

Result:

enter image description here


Edit

Maybe give this a try?

class MyBorderedTextField: UITextField {
    
    override class var layerClass: AnyClass { CAShapeLayer.self }
    private var shapeLayer: CAShapeLayer { layer as! CAShapeLayer }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    private func commonInit() {
        shapeLayer.fillColor = UIColor.clear.cgColor
        shapeLayer.strokeColor = UIColor.red.cgColor
        shapeLayer.lineWidth = 1.0 / UIScreen.main.scale
    }
    override func layoutSubviews() {
        super.layoutSubviews()
        // tweak cornerRadius to suit
        shapeLayer.path = UIBezierPath(roundedRect: bounds, cornerRadius: 6.0).cgPath
    }
    override func textRect(forBounds bounds: CGRect) -> CGRect {
        // tweak the 7,6 insets to suit
        return bounds.insetBy(dx: 7.0, dy: 6.0)
    }
    
}

class RedTextFieldVC: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let tf = UITextField()
        tf.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(tf)
        
        let tf2 = MyBorderedTextField()
        tf2.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(tf2)
        
        let g = view.safeAreaLayoutGuide
        NSLayoutConstraint.activate([
            tf.widthAnchor.constraint(equalToConstant: 240.0),
            tf.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
            tf.centerXAnchor.constraint(equalTo: g.centerXAnchor),

            tf2.widthAnchor.constraint(equalToConstant: 240.0),
            tf2.topAnchor.constraint(equalTo: tf.bottomAnchor, constant: 8.0),
            tf2.centerXAnchor.constraint(equalTo: g.centerXAnchor),
        ])
        
        tf.borderStyle = .roundedRect
        
        tf.text = "Hello Plain Border"
        tf2.text = "Hello Red Border"

    }
    
}

Output:

enter image description here

Upvotes: 1

Related Questions