iOS
Gradient Layer
Transparent Gradient
User Interface
Design Issue

iOS White to Transparent Gradient Layer is Gray

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

A white-to-transparent fade sounds simple, but CAGradientLayer often surprises people by rendering a gray band instead of a clean fade. The root cause is usually not the gradient engine itself. It is the color values being interpolated.

Why the Gradient Turns Gray

The most common mistake is using UIColor.clear.cgColor as the transparent end color:

swift
1let gradient = CAGradientLayer()
2gradient.colors = [
3    UIColor.white.cgColor,
4    UIColor.clear.cgColor
5]

UIColor.clear is transparent black, not transparent white. In RGBA terms, that means the gradient is blending from white 1, 1, 1, 1 to black 0, 0, 0, 0. During interpolation, Core Animation mixes both the RGB channels and the alpha channel. Midway through the fade, you do not get white with less opacity. You get a partially transparent gray.

That is why the gradient looks muddy even when the destination is supposed to be invisible.

The Correct Fix

Use the same RGB color at both stops and only change the alpha value:

swift
1import UIKit
2
3final class FadeViewController: UIViewController {
4    override func viewDidLoad() {
5        super.viewDidLoad()
6
7        let gradient = CAGradientLayer()
8        gradient.frame = view.bounds
9        gradient.colors = [
10            UIColor(white: 1.0, alpha: 1.0).cgColor,
11            UIColor(white: 1.0, alpha: 0.0).cgColor
12        ]
13        gradient.startPoint = CGPoint(x: 0.5, y: 0.0)
14        gradient.endPoint = CGPoint(x: 0.5, y: 1.0)
15
16        view.layer.addSublayer(gradient)
17        view.backgroundColor = .systemBlue
18    }
19}

Now the RGB values stay white across the gradient, and only opacity changes. Over a blue background, the top remains white and the fade stays visually clean.

Understanding Alpha Versus Color

A transparent color has two parts:

  1. The RGB channels.
  2. The alpha channel.

When you say “fade to transparent,” you usually mean “keep the same color, but lower alpha to zero.” If you instead change both color and alpha, the transition may darken, brighten, or shift hue.

This is especially visible with white because any movement away from white is easy to notice.

A useful mental model is:

  • White to transparent white: soft fade.
  • White to transparent black: gray band.

A Reusable Gradient Helper

If you create these fades often, wrap the behavior so the intent is explicit:

swift
1import UIKit
2
3extension CAGradientLayer {
4    static func verticalFade(from color: UIColor) -> CAGradientLayer {
5        let layer = CAGradientLayer()
6        layer.colors = [
7            color.withAlphaComponent(1).cgColor,
8            color.withAlphaComponent(0).cgColor
9        ]
10        layer.startPoint = CGPoint(x: 0.5, y: 0.0)
11        layer.endPoint = CGPoint(x: 0.5, y: 1.0)
12        return layer
13    }
14}
15
16let fadeLayer = CAGradientLayer.verticalFade(from: .white)

This removes the temptation to reach for .clear, which is the source of the bug.

Background Color Still Matters

Even with the correct gradient colors, the final look depends on what sits behind the gradient. A transparent endpoint reveals the content underneath. If the background is dark, your fade will appear to blend into that dark surface.

That is expected behavior. The key difference is that the transition should remain white as it fades, not drift toward gray halfway through.

You can test this quickly by placing the same gradient over different backgrounds:

swift
1override func viewDidLayoutSubviews() {
2    super.viewDidLayoutSubviews()
3
4    view.backgroundColor = .black
5}

The ending area will look darker because the background is black, but the gradient itself is still correct if the color stops are white with different alpha values.

Working with Images and Masks

Sometimes a gradient is used as an overlay on top of an image, and sometimes it is better used as a mask. If you want to gradually reveal or hide another layer, a mask can be more predictable than a visible gradient overlay.

swift
1let imageView = UIImageView(image: UIImage(named: "banner"))
2imageView.frame = view.bounds
3imageView.contentMode = .scaleAspectFill
4view.addSubview(imageView)
5
6let maskLayer = CAGradientLayer()
7maskLayer.frame = imageView.bounds
8maskLayer.colors = [
9    UIColor(white: 1.0, alpha: 1.0).cgColor,
10    UIColor(white: 1.0, alpha: 0.0).cgColor
11]
12maskLayer.startPoint = CGPoint(x: 0.5, y: 0.0)
13maskLayer.endPoint = CGPoint(x: 0.5, y: 1.0)
14
15imageView.layer.mask = maskLayer

In a mask, the color channels are less important than the alpha progression, so this pattern can be a better fit for fade effects.

Common Pitfalls

Using UIColor.clear as the transparent endpoint causes the gray fade. Use the same base color with alpha: 0 instead.

Forgetting to update the gradient frame leads to stretched or misplaced fades after layout changes. Set gradient.frame in viewDidLayoutSubviews() if the bounds can change.

Misreading the background can hide the real problem. A transparent endpoint always reveals the layer underneath, so test over a known background color first.

Adding multiple gradient sublayers during repeated layout passes can stack effects and make the fade look wrong. Reuse the same layer instead of inserting a new one each time.

Summary

  • 'UIColor.clear is transparent black, not transparent white.'
  • 'CAGradientLayer interpolates RGB and alpha together.'
  • For a clean white fade, keep RGB white at both stops and only lower alpha.
  • The background influences the final appearance, but it should not introduce a gray midpoint.
  • If the goal is reveal or hide behavior, a gradient mask may be cleaner than a visible overlay.

Course illustration
Course illustration

All Rights Reserved.