UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

How to pick a single colour within a gradient based on a value

Forums > SwiftUI

I have a view that tracks progress and changes colour based on percent complete. Currently I am doing that by reducing the amount of green and increasing the amount of red as percent complete goes up. However in the middle this leads to some crappy brown colours that I would prefer are yellow.

I thought I could create a gradient from the colours I like with specific green, yellow and red in a diverging palette and then pick the correct hue from within the gradient based on the percent complete (0...1) value. But I am not able to find how to select a single colour from within a gradient currently, or some other colour ramp where you can pick from the blended colours. Any ideas? Maybe playing around within the start and end on the gradient?

2      

Since your gradient is an array of colors, I would suggest you find the right combination, and then trim the shape based on the completion percentage.

So you could have for example [.red, .orange, .yellow, .green] as your gradients. But the trim goes from 0 to 1 which then shows as much of the gradient as necessary.

trim is applied to shapes.

2      

I am not sure that would work though? I want the view to be a single uniform colour between red and green not a smaller part of the overall gradient. Is that what you meant? Basically I want a colour ramp that I can select single colours from based on a value. I get that currently by changing the rgb values but would prefer the values in the middle to not be a 0.5 g and 0.5r but some nicer shade from the blended array of colours input into the colour ramp. Not certain the gradient is the way to get this but it feels like it is doing some of the blending colours work I need.

edit: After reading the sparse documentation on Gradient I think I do want that, I just need to access a particular colour at a location rather than use it to create one of the gradient views.

2      

How are you showing the color? Do you intend to show the completion progress as a change of color? just that? something along the lines of a circle that starts as being red, and fades into the green via a percentage?

Or would you want a bar that is made up of the gradient. So when you only see 10% of the bar, you are seeing the red, at 50% you are seeing the red up to whatever is the halfway point, then at 100% you see the entire gradient?

Or are you looking for a mix? So you see 10% of the bar and it is entirely one color, as the bar grows wider, the color of the entire bar changes all the way to green at 100%?

2      

Here's a link: https://sarunw.com/posts/gradient-in-swiftui/

Has some stop examples

2      

Hi, I am trying to show the completion process through change of colour. So i have a bar that is coloured with a single flat colour that changes each second from green to red when its due. I have this already via manipulating RGB values but I want the middle colours to not be a linear journey through Red and Green values but instead the colour shown change in relation to its position in the gradient.

At no point do I want to show any gradients, I want single colours that change over time progressing through the gradient colours.

Is there a way to access a particular colour at a position in a Gradient rather than apply it to something? I have seen that link, its still just creating gradients not accessing a single colour.

2      

I'm not sure I really understand what you are trying to get. I mean, the whole point of a gradient is that there are no discrete colors, except at the beginning and end and at any explicit stops that may be defined in between. Everything else will be a blend of two colors.

But to get a color at any given percentage of a gradient, you could do this:

import SwiftUI

//modified slightly from this answer: https://stackoverflow.com/a/59996029/3505188
extension Array where Element: UIColor {
    func intermediate(percentage: CGFloat) -> UIColor {
        switch percentage {
        case 0: return first ?? .clear
        case 1: return last ?? .clear
        default:
            let approxIndex = percentage / (1 / CGFloat(count - 1))
            let firstIndex = Int(approxIndex.rounded(.down))
            let secondIndex = Int(approxIndex.rounded(.up))
            let fallbackIndex = Int(approxIndex.rounded())

            let firstColor = self[firstIndex]
            let secondColor = self[secondIndex]
            let fallbackColor = self[fallbackIndex]

            var (r1, g1, b1, a1): (CGFloat, CGFloat, CGFloat, CGFloat) = (0, 0, 0, 0)
            var (r2, g2, b2, a2): (CGFloat, CGFloat, CGFloat, CGFloat) = (0, 0, 0, 0)
            guard firstColor.getRed(&r1, green: &g1, blue: &b1, alpha: &a1) else { return fallbackColor }
            guard secondColor.getRed(&r2, green: &g2, blue: &b2, alpha: &a2) else { return fallbackColor }

            let intermediatePercentage = approxIndex - CGFloat(firstIndex)
            return UIColor(red: CGFloat(r1 + (r2 - r1) * intermediatePercentage),
                           green: CGFloat(g1 + (g2 - g1) * intermediatePercentage),
                           blue: CGFloat(b1 + (b2 - b1) * intermediatePercentage),
                           alpha: CGFloat(a1 + (a2 - a1) * intermediatePercentage))
        }
    }
}

struct IntermediateGradientView: View {
    @State private var percentage: CGFloat = 0.0

    let colors: [UIColor] = [.systemRed, .systemGreen]

    var body: some View {
        VStack {
            Rectangle()
                .fill(Color(colors.intermediate(percentage: $percentage.wrappedValue)))

            Slider(value: $percentage, in: 0...1)
        }
    }
}

2      

The question though is why gradient in that case? You are trying to access specific colors at specific times that relate to percentage completion?

So at 0% complete, it's red, at 100% complete it's green?

In any case my recommendation is to start with a linear gradient from Red to Green. Then open the Digital Color Meter (the Other folder in your app launch screen) and find the details for the colors you want.

The alternative is just put the color change inside a withAnimation. let it animate itself without having to control the numbers.

2      

Awesome RoosterBoy, thanks. A rendered gradient is indeed a blend of the colours but its fairly standard to have a colour ramp in data visualisation where you blend colours and pick a point from the ramp to represent the data point. So its entirely so I have a nicer fade from green to red than I do by a linear transition reducing green and increasing red with each percentage point. It looks like your code does that so great. I thought Gradient the class might allow this already since it seems thats what its doing anyway but using the dimensions of the shape you render it to to pick the colour for a point - isnt the class Gradient itself simply a theoretical blending between a number of colours that is then rendered later when you apply it to LinearGradient or whatever?

2      

BUILD THE ULTIMATE PORTFOLIO APP Most Swift tutorials help you solve one specific problem, but in my Ultimate Portfolio App series I show you how to get all the best practices into a single app: architecture, testing, performance, accessibility, localization, project organization, and so much more, all while building a SwiftUI app that works on iOS, macOS and watchOS.

Get it on Hacking with Swift+

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.