Best way to convert [CGFloat] to [Double]?

cgFloatArray.map { Double($0) }

Is there some better way?

CGFloat appear to me like an alias of Double, is there some other better/faster way?

cgFloatArray.map(Double.init) is another way to do this.

1 Like

That’d apply only to 64bit machine. Even then, it’s not an alias as in typealias. You still need to convert it.

It’s possible that you can reinterpret the memory. Even if that works today, and I’m not even sure it does, it’d be very risky and fragile, so I wouldn’t recommend it.

What @suyashsrijan said is what I would also suggest. Better yet if you change either side to match, to use only CGFloat or only Double, and minimize the amount of conversion needed.

Conversion takes some resources, and Swift code sure looks like so (with .init call), which is by design.

2 Likes

Doesn't seem like possible in my case (I hope I'm wrong and there is a way?): I need to interpolate two SwiftUI.Color's rgba by calling:

UIColor(swiftUIColor).cgColor.components  // -> [CGFloat, CGFloat, CGFloat, CGFloat]

This is where I need to convert from [CGFloat] => [Double]

then I do my calculation in Double and make a new SwiftUI.Color with:

Color(red: green: blue: opacity:)

which takes Double

I don't know of another way to get rgba from SwiftUI.Color.

It’s not always possible of course. That’s why conversions still exist.

Maybe you can interpolate exclusively in UIColor and convert them to Color only as needed? You can also use getRed(_:green:blue:alpha:) to get rgba components, which is probably safer than getting raw components from CGColor, and then reinstantiate with init(red:green:blue:alpha:). Now you can do a roundtrip with UIColor and convert it to Color only on rendering.

PS

When dealing with color, you should keep in mind the color space that you’re working in. It’s probably fine if it comes from the same place. Even if you get it wrong, the worst that could happen is just getting the wrong color*. Though if you reuse the results in a cascading manner, the error may be magnified.

If you’re interpolating over time, maybe SwiftUI already handles that with animate, Or maybe you can use Gradient?

* Some color is know to crash the system, but that’s very rare.

1 Like

Why not just do the math in CGFloat? On a 64-bit machine, CGFloat is a 64-bit floating point number, equivalent to a Double, but, not boxed. On a 32-bit machine, it's a 32-bit floating point number, but, the range of your numbers is going to be between 0-255. With 6-6 ½ digits of precision, that leaves you 4-4 ½ digits for the fraction, which should be good enough for determining colors that can be displayed. Then, initialize the Double arguments from the CGFloats when you create your new Swift.Color, or, follow @lantua's advice and keep everything in UIColors until ready to display.

1 Like

Color.init still takes Double so we still need conversion if we go

Color -> UIColor -> CGColor ==> Color.

1 Like

This is what's I'm making:

// copied (mostly) from https://swiftui-lab.com/swiftui-animations-part3/
struct ColorAnimation: AnimatableModifier {
    var animatableData: Double
    let rgbaPair: [(Double, Double)]

    static private let garbage = [(0.0, 1.0), (0.0, 1.0), (0.0, 1.0), (0.0, 1.0)]

    init(_ flag: Bool, from: Color, to: Color) {
        animatableData = flag ? 0 : 1
        guard let cc1 = UIColor(from).cgColor.components else {
            rgbaPair = Self.garbage
            logger.error("ColorAnimation: UIColor(from).cgColor.components return nil!!")
            return
        }
        guard let cc2 = UIColor(to).cgColor.components else {
            rgbaPair = Self.garbage
            logger.error("ColorAnimation: UIColor(to).cgColor.components return nil!!")
            return
        }
        rgbaPair = Array(zip(cc1.map(Double.init), cc2.map(Double.init)))
    }

    func body(content: Content) -> some View {
        content
            .foregroundColor(mixedColor)
    }

    // This is a very basic implementation of a color interpolation
    // between two values.
    var mixedColor: Color {
        let rgba = rgbaPair.map { $0.0 + ($0.1 - $0.0) * animatableData }
        return Color(red: rgba[0], green: rgba[1], blue: rgba[2], opacity: rgba[3])
    }
}

It's used as one way of getting around Text().foregroundColor() not animated:

    Text("ABCD")
        .modifier(ColorAnimation(flag, from: .green, to: .red)

First, you don't need to include both the starting point and the ending point in the same struct. The point of Animatable is to put both endpoints in two instances, and interpolated between them.

struct ForegroundColor: AnimatableModifier {
  private var red, green, blue, alpha: Double

  init(color: Color) { ... }
  func body(content: Content) -> some View { ... }
    
  var animatableData: AnimatablePair<AnimatablePair<Double, Double>, AnimatablePair<Double, Double>> {
    get { .init(.init(red, green), .init(blue, alpha)) }
    set {
      red = newValue.first.first
      green = newValue.first.second
      blue = newValue.second.first
      alpha = newValue.second.second
    }
  }
}

So that you can use it like this:

Text("Test")
  .modifier(ForegroundColor(color: flag ? .green : .blue))

which is much more flexible.


Now, where am I? Yes, conversion. In this scenario, the UIColor vents out CGFloat, but the Color takes in Double, so conversion is required. You can use Double and perform conversion inside init. You can also use CGFloat and do the conversion inside body. Recall that body is called at every frame, so it's probably better to push the work toward init.


I did say before to be aware of the colour space because CGColor.components returns the colour components w.r.t. its colour space. If your colour is grayscale, it'd have only two components, crashing the system when you try to access components[2].

It's probably fine with Color. Though I'd do

init(color: Color) {
  var r: CGFloat = 0, g: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 0
  guard UIColor(color).getRed(&r, green: &g, blue: &b, alpha: &a) else {
    (red, green, blue, alpha) = (0, 0, 0, 1)
    logger.error(...)
    return
  }
  red = Double(r)
  green = Double(g)
  blue = Double(b)
  alpha = Double(a)
}
2 Likes

:bowing_man: Thank you! So much better to let the animation machinery do all the interpolating. You taught me a valuable lesson, again!

Oh yeah, also thanks for the lesson on color/color space! Without knowing the difference, when the crash comes, I would have no idea what went wrong!