0

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

  • find the actual, real, border, get its real values
  • sadly just add a fake on top of that layer once found, perfectly replicating it

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

  • Bizarre: take my code above, instead of adding the fake layer, just change the color of the found layer. You'd think! I cannot for the life of me figure out where in the view cycle they set it. Any place I changed the color, "they" would change it back!! The only way I could make it stick was with a timer, for goodness sake - give it a go.

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.

5
  • Why not use borderStyle = .none instead of borderStyle = .roundedRect and give rounded border manually... Sorry, little confused what you want to do... OR simply you want to change color of textfield border? Commented Jul 1 at 11:58
  • cheers @FahimParkar sure, of course you could just turn it off, or indeed just build a custom control from scratch. But the question is what the question is, how can you literally change Apple's basis. If you read my supplementary answer it is explained in detail. Cheers
    – Fattie
    Commented Jul 1 at 12:22
  • This is why I don't use Apple behavior... i turn off style and use my own... tomorrow may be Apple will change radius so logic of cornerRadius won't work either.. can't trust Apple. I always prefer customized stuff... even UIButton with .custom and not default... Commented Jul 1 at 13:11
  • Cheers @FahimParkar ok - sounds fine. Very often on large projects you have to use and want to use exactly the Apple "defaults and style". Another issue is, imagine you're on a large project and there are dozens or even hundreds of uses of UITextField throughout, and numerous programmers/teams working on different parts of the app, modules etc. Say in one part the designers specify the border should be orange in some states; that team or person has to use the underlying, absolutely exact, Apple style but in the example make it orange. You can't just willy-nilly say "oh we'll do our own
    – Fattie
    Commented Jul 1 at 13:28
  • thing here". Note too the overwhelming problem with "metching" Apple is that Apple can and do change a LOT of stuff as the years go on. If you need "Apple's background color" you have to actually get that, you can't just say, oh it is #F0F3F2" (or whatever) and use that .. because anything can and will happen, there are various versions of iOS out there etc; much more so of the same issue is matching the use of Apple controls. Hence unfortunately "This is why I don't use Apple behavior" heh that is great if you own and are the sole boss of the programming company and client :)
    – Fattie
    Commented Jul 1 at 13:30

2 Answers 2

1
+250

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

7
  • You know what, unfortunately it seems if you do that you just get both (I edited in a pic to the question) - mystery!
    – Fattie
    Commented Jun 18 at 14:43
  • @Fattie - see my Edit for another approach.
    – DonMag
    Commented Jun 18 at 15:41
  • thanks, well, that takes away their rights to do whatever weird-ass stuff they're doing behind the scenes eh! :) You know what, if as in MyBorderedTextField you have border style .none, there's really no need to do the "takeover" of their layer; you're then just drawing a totally custom control (ie you'd just add the border, move the text, handle the sundry states etc or whatever else you want like any custom control I guess). There doesn't seem to be any way to actually change whatever the hell they are doing behind the scenes so that everything will match perfectly and you're ju
    – Fattie
    Commented Jun 19 at 11:40
  • st simply changing their color to another. Weird-ass UITextView, it's always been a bugbear!
    – Fattie
    Commented Jun 19 at 11:41
  • @Fattie - I would say yes, it's surprising UITextField does not have a corresponding .borderColor property for its .borderStyle property. However, lots of UIKit controls are meant to be used "as-is" ... and I've seen plenty of people who get themselves into trouble trying to make unsupported changes.
    – DonMag
    Commented Jun 19 at 12:44
0

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 :/ )

Not the answer you're looking for? Browse other questions tagged or ask your own question.