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

Adding a func to a ForEach (on it's way to becoming red variable in Color for a Color Array)

Forums > SwiftUI

Working towards selecting two colors and the number of rectangles to create a color array. I'd like to create a func to make this reusable once it's useful. First I am just trying to make it so you can choose the number of rectangles dynamically like here

This code works but you can't change the number of Rectangles without doing it in the code:

import SwiftUI

struct RectsWSteps: View {
//    set the two parent colors red only for now
    let rMom: Double = 0.65
    let rDad: Double = 0.95
//    child colors green and blue are fixed for now
    let g: Double = 0.85
    let b: Double = 0.70

    let numberRects = 5
    let arrayOfPossibleRects = [Int](3...12)

    //    func lerp(rMom: Double, rDad: Double, numberRects: Int) -> Double {
    //      return rMom - (step * ((rMom - rDad) / Double (numberRects-1)))
    //    }

    var body: some View {

        let increment = (rMom - rDad) / Double (numberRects-1)

        VStack (spacing: 0) {
            ForEach(0..<numberRects, id: \.self) { step in
                ZStack {
                    Rectangle()
                        .foregroundColor(
                            .init(red: (rMom - (increment * Double(step))), green: g, blue: b))
//                    show Color values RGB
                    Text("r = \(rMom - (increment * Double(step))) g = \(g) b = \(b)")
                }
            }
        }
    }
}

Here's my effort to add the number of Rectangles picker. I'd really like to clean this up. I got cautions telling me to change var to let, but r, g & b ARE changing values so something is off.

I'm repeating the math, there are lets everywhere, but also I'm going to want the math that calculates the color array to be in a function. Isn't that the best way to make it reusable?- when I say reusable, I mean for all kind of purposes in other apps. In other code making a color array or blend from parent colors is called interpolation or in one case I know of lerp.

Anyway, open to all kinds of suggestions.


struct RectsWSteps: View {
    //   one day soon this will be the color picker :)
    // set value of red for both parents
    let rMom: Double = 0.65
    let rDad: Double = 0.95
    //set value of green for parents
    let gMom: Double = 0.85
    let gDad: Double = 0.85
      //set value of blue for parents
    let bMom: Double = 0.70
    let bDad: Double = 0.70

    //starting with 5 rectangles but this will be in picker for user to select

    @State private var numberRects = 5
    let arrayOfPossibleRects = [Int](0...12)

    var body: some View {

        VStack {
            Picker("Number of Rectangles", selection: $numberRects) {
                ForEach(0..<arrayOfPossibleRects.count) { index in
                    Text("\(index)")
                }
            }

            VStack (spacing: 0) {
                let incrementR = (rMom - rDad) / Double (numberRects-1)
                let incrementG = (gMom - gDad) / Double (numberRects-1)
                let incrementB = (bMom - bDad) / Double (numberRects-1)

                ForEach(0..<numberRects, id: \.self) { step in
                    let r = (rMom - (incrementR * Double(step)))
                    let g = (gMom - (incrementG * Double(step)))
                    let b = (bMom - (incrementB * Double(step)))

                    ZStack {
                        Rectangle()
                            .foregroundColor(.init(red: r, green: g, blue: b))
                        Text("r = \(r) g = \(g) b = \(b)")
                    }
                }
            }
        }
    }
}

3      

Try this:

import SwiftUI

extension Color {
    //break down the Color into its red, green and blue components
    //we ignore the alpha value
    var components: (red: CGFloat, green: CGFloat, blue: CGFloat) {
        var r: CGFloat = 0
        var g: CGFloat = 0
        var b: CGFloat = 0
        var a: CGFloat = 0

        guard UIColor(self).getRed(&r, green: &g, blue: &b, alpha: &a) else {
            return (0, 0, 0)
        }

        return (r, g, b)
    }

    //convenience properties for accessing the three components
    var r: CGFloat { components.red }
    var g: CGFloat { components.green }
    var b: CGFloat { components.blue }

    //interpolate Color values between ourself and an ending Color
    //with the given number of steps
    func interpolate(with endColor: Color, numSteps: Int) -> [Color] {
        let startComponents = components
        let endComponents = endColor.components
        let increments = (red: Double(startComponents.red - endComponents.red) / Double(numSteps - 1),
                          green: Double(startComponents.green - endComponents.green) / Double(numSteps - 1),
                          blue: Double(startComponents.blue - endComponents.blue) / Double(numSteps - 1))

        let colors: [Color] = (0...numSteps).map {
            let r = Double(startComponents.red) - (increments.red * Double($0))
            let g = Double(startComponents.green) - (increments.green * Double($0))
            let b = Double(startComponents.blue) - (increments.blue * Double($0))
            return Color(red: r, green: g, blue: b)
        }

        return colors
    }
}

struct RectsWSteps: View {
    //you can define these here or use @State properties
    //tied to a color picker
    let startColor = Color(red: 0.65, green: 0.85, blue: 0.70)
    let endColor = Color(red: 0.95, green: 0.85, blue: 0.70)

    //generate an array of Colors to use in ForEach
    var colorSteps: [Color] {
        startColor.interpolate(with: endColor, numSteps: numberRects)
    }

    //starting with 5 rectangles but this will be in picker for user to select
    @State private var numberRects = 5
    let arrayOfPossibleRects = [Int](0...12)

    var body: some View {

        VStack {
            Picker("Number of Rectangles", selection: $numberRects) {
                ForEach(arrayOfPossibleRects, id: \.self) { numRects in
                    Text("\(numRects)")
                }
            }

            VStack (spacing: 0) {
                ForEach(colorSteps, id: \.self) { color in
                    ZStack {
                        Rectangle()
                            .foregroundColor(color)
                        Text("r = \(color.r) g = \(color.g) b = \(color.b)")
                    }
                }
            }
        }
    }
}

3      

I'm sorry I haven't replied in 16 days. Your response floored me.

I need to try to summarize to see if I can learn.

You made a function called interpolate that could take in two colors & the number of steps and return a color array.

I'm trying to understand all it took for you to get there.

To start, you make an extension of Color so that you can access RGB in a way that SwiftUI will understand. This is all code I don't understand (neither how it works or why SwiftUI doesn't already provide access to RGB values-- will we always have to access UIColor in SwiftUI to get to RGB values?).

Even using CGFloat instead of Double, has me praying I don't actually have to get this to get this: what is guard why &.

extension Color {
    //break down the Color into its red, green and blue components
    //we ignore the alpha value
    var components: (red: CGFloat, green: CGFloat, blue: CGFloat) {
        var r: CGFloat = 0
        var g: CGFloat = 0
        var b: CGFloat = 0
        var a: CGFloat = 0

        guard UIColor(self).getRed(&r, green: &g, blue: &b, alpha: &a) else {
            return (0, 0, 0)
        }

        return (r, g, b)
    }

In the Color extension, we made that variable called components to isolate r,g, and b as variables. I'm not sure here if components.red could be components.r for example or dot x...

Any we use those components now to create our func called interpolate

What I haven't seen before and not sure why but you use 'with' and then make the startComponents a modifier.

 func interpolate(with endColor: Color, numSteps: Int) -> [Color] {
        let startComponents = components
        let endComponents = endColor.components
        ...

Thank you so much for creating a solution. It's a huge leap for me to "kind of" understand this!

3      

To start, you make an extension of Color so that you can access RGB in a way that SwiftUI will understand. This is all code I don't understand (neither how it works or why SwiftUI doesn't already provide access to RGB values-- will we always have to access UIColor in SwiftUI to get to RGB values?).

I did it as an extension more so that it can be called on an existing Color rather than having to pass in two Colors as you would if it was a free function.

So:

startColor.interpolate(with: endColor, numSteps: numberRects)

rather than something like:

interpolate(startColor: color1, endColor: color2, numSteps: numberRects)

It just reads better and is more "Swifty".

what is guard why &.

The guard keyword performs a check and exits if the check doesn't pass. Since the getRed method returns a Bool if it successfully retrieves the color components, we can use this in a guard check. If that check fails (because getRed returned false), we return from the method with a default tuple of red, green and blue values of 0.

In this particular case it doesn't really make much of a difference, but guard can often be used to exit early from a function and thereby avoid nested if statements.

As for the &, that signals that we are passing in the variables r, g, and b as inout parameters. Normally, parameters are passed as constants and they can't be changed within the body of a function; inout parameters let us change the value and that value therefore gets percolated back out to the caller. So, here we pass in the values 0, 0, and 0 and the get changed to whatever the actual red, green and blue values are.

What I haven't seen before and not sure why but you use 'with' and then make the startComponents a modifier.

with in this function declaration is an argument label. In Swift you can use argument labels to make your functions read nicer at the call site. You can have separate argument labels and argument names; the labels are used externally (i.e., when calling a function) and the names are used internally (i.e., within the function's body).

So using an argument label called with in the interpolate function lets us do this:

startColor.interpolate(with: endColor, numSteps: 5)

instead of this:

startColor.interpolate(endColor: endColor, numSteps: 5)

It just reads cleaner.

I'm not exactly sure what you mean by "and then make the startComponents a modifier", but we use the components computed property we created on the extension to Color and assign the results to startComponents. Since components is called without any preceding Color instance, it is understood to apply to whatever color we are calling it on. Then, we use endColor.components to get the components of our endColor instance.

4      

Thank you for the detailed explanation. I mistakenly used the term "modifier" incorrectly-- thinking of modifiers for views... guard I should have known. The & for inout parameters is new to me.

I'm still wroking through your code, but it works and next I'm going to try adding the color pickers. I really appreciate your help- and patience!!

BTW, you aren't ever on codementor are you?

3      

Hacking with Swift is sponsored by RevenueCat

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure your entire paywall view without any code changes or app updates.

Learn more here

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.