1

I created a little project (Github link) demonstrating how to add a line under a UITextField to answer this SO question.

The OP was trying to add the underline using a CALayer, so that is how I originally implemented the solution. For that version, I created a custom subclass of UITextField called "UnderlinedTextField". That custom subclass creates and maintains a CALayer with a background color.

I then noted that it would be easier to simply add a 1-point UIView at the bottom of the text field. I updated my demo project to illustrate that approach as well.

Both approaches add a 1-point blue line under the UITextField. The view-based approach does everything in the Storyboard, and there is no code needed. Since it uses AutoLayout constraints to pin the underline view to the bottom of the text field, the constraints keep the view at the right location even if the text field moves.

However, I noticed that the layer-based underline and the view-based underline look different here is a screenshot:

UnderlinedTextField screenshot

The layer-based underline looks heavier. In looking at it, the color used for anti-aliasing on the layer-based underline is darker than the color used in anti-aliasing the underline for the view-based underline.

Why is that, and what would I change to make them look the same?

(Below is the code for the UnderlinedTextField class, in case you don't want to go look at the Github repo)

/// This is a custom subclass of UITextField that adds a 1-point colored underline under the text field using a CALayer.
/// It implments the `layoutSubviews()` method to reposition the underline layer if the text field is moved or resized.
class UnderlinedTextField: UITextField {

    /// Change this color to change the color used for the underline
    public var underlineColor = UIColor.blue {
        didSet {
            underlineLayer.backgroundColor = underlineColor.cgColor
        }
    }

    private let underlineLayer = CALayer()

    /// Size the underline layer and position it as a one point line under the text field.
    func setupUnderlineLayer() {
        var frame = self.bounds
        frame.origin.y = frame.size.height - 1
        frame.size.height = 1

        underlineLayer.frame = frame
        underlineLayer.backgroundColor = underlineColor.cgColor
    }

    required init?(coder: NSCoder) {
        super.init(coder: coder)
        // In `init?(coder:)` Add our underlineLayer as a sublayer of the view's main layer
        self.layer.addSublayer(underlineLayer)
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
        // in `init(frame:)` Add our underlineLayer as a sublayer of the view's main layer
        self.layer.addSublayer(underlineLayer)
    }

    // Any time we are asked to update our subviews,
    // adjust the size and placement of the underline layer too
    override func layoutSubviews() {
        super.layoutSubviews()
        setupUnderlineLayer()
    }
}

Edit:

I changed my project based on Matt's answer (and pushed the changes) to make the Storyboard use the device color profile. That makes the underlines' color values very close, but still not exactly the same.

If I inspect the lines in the simulator using the "Digital Color meter" app the top and bottom lines are subtly different. However, if I save the screen to disk in the simulator with control S (which saves it at full size) and open the resulting image in PS, the lines show as un-aliased pure sRGB blue. I guess there's some weirdness with mapping the simulator screen to the Mac screen that causes the 2 lines to be aliased slightly differently. enter image description here

(You'll have to open the picture in PS or another app that lets you view pixel color to detect the difference in the two lines. After changing the storyboard's color profile to device RGB (sRGB) I can't see the difference any more.)

Duncan C
  • 128,072
  • 22
  • 173
  • 272
  • 1
    The varying use of color spaces within Xcode and assets you introduce (i.e. Adobe's definition of color spaces versus Apple's definition) can be a headache, which is why I always extend `UIColor` and define my own colors programmatically with `UIColor(hue:saturation:brightness:alpha:)` which is also available in Storyboard (create custom color with HSB Sliders) and they will always be in sync. For any Adobe users reading this, sRGB is the color space you want to use for Xcode assets. – trndjc Dec 19 '21 at 16:57
  • 1
    @liquid Very nice point. I've given a different working technique in my answer, but yours is better as it eliminates the whole issue. – matt Dec 19 '21 at 17:07
  • @liquid, sometimes I find the HSB color space useful for describing colors, and other times I want to use RGB. I had encountered the need to switch to sRGB when picking colors in PS before, so I should have thought to check the color profile in the Storyboard. It's maddening that the different parts of the IDE use different color profiles. – Duncan C Dec 19 '21 at 22:41

1 Answers1

4

You have in fact given them different colors.

  • The layer's color is set in code and is UIColor.blue.cgColor.

  • The view's color is created in the storyboard and is

      R:0.02 G:0.2 B:1 A:1
    

    as revealed by examining it in the view debugger.

Why? Because you have failed to take into account the storyboard color's color space. It is Generic RGB, whereas the color you create in code is Device RGB, which is sRGB.

Another way to see this is to do some logging. In the running app, the two colors are

<CGColor 0x60000296aa60> 
[<CGColorSpace 0x60000296ab20> 
    (kCGColorSpaceICCBased; kCGColorSpaceModelRGB; sRGB IEC61966-2.1; extended range)] 
( 0 0 1 1 )>

and

<CGColor 0x600002976940> 
[<CGColorSpace 0x60000296ab20> 
    (kCGColorSpaceICCBased; kCGColorSpaceModelRGB; sRGB IEC61966-2.1; extended range)] 
( 0.01675 0.198341 1 1 )>

As you can see, the second color has been converted to sRGB color space, which is not the same as the Generic RGB you started with, and thus we get a different color after the conversion.

and what would I change to make them look the same

If you set the storyboard color's colorspace to Device RGB and then set the sliders to give a simple blue, you'll end up with the same colors in the running app.

matt
  • 515,959
  • 87
  • 875
  • 1,141
  • And see the canonical https://stackoverflow.com/questions/10039641/ios-color-on-xcode-simulator-is-different-from-the-color-on-device – matt Dec 19 '21 at 16:47
  • Of course. I should have thought to check the color spaces. (It's annoying that Storyboards default to Generic RGB.) Thanks Matt. (voted.) – Duncan C Dec 19 '21 at 17:59
  • I edited my project to set the Storyboard color to the device RGB (sRGB) profile, and that helps, but the layer-based and view-based colors are still slightly different. If I make the lines multiple points thick then the center parts match exactly, but the colors in the anti-aliasing are subtly different. It's no longer enough to be visible, but it's still curious. Also, for some reason the anti-aliasing on a horizontal "line" (one point content) is not top-to-bottom symmetrical, and the layer anti-aliasing is flipped vertically from the view anti-aliasing. – Duncan C Dec 20 '21 at 15:48
  • Ok, it looks like the differences are an artifact of the simulator's rendering onto the Mac screen. If I inspect the two lines using the "Digital Color Meter" pixel peeping utility, the top and bottom lines give slightly different colors (depending on the scaling I have selected in the simulator.) If I instead save a full-sized image to disk and open that in PS, I find both lines as pure, 0/0/255 blue lines with no anti-aliasing. – Duncan C Dec 20 '21 at 16:04
  • Are you saying you get the right colors if you run this on a device???? I didn't test that but maybe I should have. – matt Dec 20 '21 at 16:16