admin管理员组

文章数量:1122832

When I retrieve the accent color programmatically, it returns the accent color in the light mode. However, when I retrieve the color alone, it returns the correct color. How can I resolve this issue?

import SwiftUI

extension Color {
    // Function to get RGB components from Color
    private func getRGBA() -> (red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat) {
        let uiColor = UIColor(self)
        var red: CGFloat = 0
        var green: CGFloat = 0
        var blue: CGFloat = 0
        var alpha: CGFloat = 0
        uiColor.getRed(&red, green: &green, blue: &blue, alpha: &alpha)
        return (red, green, blue, alpha)
    }

    // Generate array of colors between two colors
    static func colorRange(from startColor: Color, to endColor: Color, count: Int) -> [Color] {
        guard count > 1 else { return [startColor] }

        let start = startColor.getRGBA()
        let end = endColor.getRGBA()

        let redDelta = end.red - start.red
        let greenDelta = end.green - start.green
        let blueDelta = end.blue - start.blue
        let alphaDelta = end.alpha - start.alpha

        return (0 ..< count).map { step in
            let progress = CGFloat(step) / CGFloat(count - 1)

            let red = start.red + redDelta * progress
            let green = start.green + greenDelta * progress
            let blue = start.blue + blueDelta * progress
            let alpha = start.alpha + alphaDelta * progress

            return Color(UIColor(red: red, green: green, blue: blue, alpha: alpha))
        }
    }
}

struct ColorRangeExamples: View {
    var body: some View {
        VStack(spacing: 30) {
            Color.accent.frame(height: 100).cornerRadius(10)
            
            // Example 1: 5 colors between accent and secondary
            let example1 = Color.colorRange(from: .accent, to: .secondary, count: 5)
            HStack(spacing: 0) {
                ForEach(0 ..< example1.count, id: \.self) { index in
                    example1[index]
                }
            }
            .frame(height: 50)
            .cornerRadius(10)
        }
        .padding()
    }
}

#Preview {
    ColorRangeExamples()
}

When I retrieve the accent color programmatically, it returns the accent color in the light mode. However, when I retrieve the color alone, it returns the correct color. How can I resolve this issue?

import SwiftUI

extension Color {
    // Function to get RGB components from Color
    private func getRGBA() -> (red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat) {
        let uiColor = UIColor(self)
        var red: CGFloat = 0
        var green: CGFloat = 0
        var blue: CGFloat = 0
        var alpha: CGFloat = 0
        uiColor.getRed(&red, green: &green, blue: &blue, alpha: &alpha)
        return (red, green, blue, alpha)
    }

    // Generate array of colors between two colors
    static func colorRange(from startColor: Color, to endColor: Color, count: Int) -> [Color] {
        guard count > 1 else { return [startColor] }

        let start = startColor.getRGBA()
        let end = endColor.getRGBA()

        let redDelta = end.red - start.red
        let greenDelta = end.green - start.green
        let blueDelta = end.blue - start.blue
        let alphaDelta = end.alpha - start.alpha

        return (0 ..< count).map { step in
            let progress = CGFloat(step) / CGFloat(count - 1)

            let red = start.red + redDelta * progress
            let green = start.green + greenDelta * progress
            let blue = start.blue + blueDelta * progress
            let alpha = start.alpha + alphaDelta * progress

            return Color(UIColor(red: red, green: green, blue: blue, alpha: alpha))
        }
    }
}

struct ColorRangeExamples: View {
    var body: some View {
        VStack(spacing: 30) {
            Color.accent.frame(height: 100).cornerRadius(10)
            
            // Example 1: 5 colors between accent and secondary
            let example1 = Color.colorRange(from: .accent, to: .secondary, count: 5)
            HStack(spacing: 0) {
                ForEach(0 ..< example1.count, id: \.self) { index in
                    example1[index]
                }
            }
            .frame(height: 50)
            .cornerRadius(10)
        }
        .padding()
    }
}

#Preview {
    ColorRangeExamples()
}

Share Improve this question asked yesterday Lê Công Minh KhôiLê Công Minh Khôi 491 silver badge6 bronze badges 0
Add a comment  | 

1 Answer 1

Reset to default 0

UIColor.getRGBA does not respect the current color scheme.

You should use resolve(in:) to get the RGBA values. This method takes an EnvironmentValues which includes the color scheme information. It gives you a Color.Resolved, from which you can directly get the RGBA values.

extension Color {
    func getRGBA(env: EnvironmentValues) -> (red: Float, green: Float, blue: Float, alpha: Float) {
        let resolved = resolve(in: env)
        return (resolved.red, resolved.green, resolved.blue, resolved.opacity)
    }
}

Similarly, colorRange needs to take such an EnvironmentValues parameter too.

static func colorRange(from startColor: Color, to endColor: Color, count: Int, env: EnvironmentValues) -> [Color.Resolved] {
    guard count > 1 else { return [startColor.resolve(in: env)] }

    let start = startColor.getRGBA(env: env)
    let end = endColor.getRGBA(env: env)

    let redDelta = end.red - start.red
    let greenDelta = end.green - start.green
    let blueDelta = end.blue - start.blue
    let alphaDelta = end.alpha - start.alpha

    return (0 ..< count).map { step in
        let progress = Float(step) / Float(count - 1)

        let red = start.red + redDelta * progress
        let green = start.green + greenDelta * progress
        let blue = start.blue + blueDelta * progress
        let alpha = start.alpha + alphaDelta * progress

        return Color.Resolved(red: red, green: green, blue: blue, opacity: alpha)
    }
}

In your view, use @Environment(\.self) to get the EnvironmentValues.

struct ColorRangeExamples: View {
    @Environment(\.self) var env
    
    var body: some View {
        VStack(spacing: 30) {
            Color.accent.frame(height: 100).cornerRadius(10)
            
            // Example 1: 5 colors between accent and secondary
            let example1 = Color.colorRange(from: .accent, to: .secondary, count: 5, env: env)
            HStack(spacing: 0) {
                ForEach(0 ..< example1.count, id: \.self) { index in
                    Color(example1[index])
                }
            }
            .frame(height: 50)
            .cornerRadius(10)
        }
        .padding()
    }
}

If you need to support earlier versions, you can use UIColor.resolvedColor(with:), which is similar to Color.resolve(in:), except it takes a UITraitCollection. You need to implement a UIViewRepresentable in order to get a UITraitCollection in SwiftUI.

extension Color {
    private func getRGBA(traits: UITraitCollection) -> (red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat) {
        let uiColor = UIColor(self).resolvedColor(with: traits)
        var red: CGFloat = 0
        var green: CGFloat = 0
        var blue: CGFloat = 0
        var alpha: CGFloat = 0
        uiColor.getRed(&red, green: &green, blue: &blue, alpha: &alpha)
        return (red, green, blue, alpha)
    }

    static func colorRange(from startColor: Color, to endColor: Color, count: Int, traits: UITraitCollection) -> [Color] {
        guard count > 1 else { return [startColor] }

        let start = startColor.getRGBA(traits: traits)
        let end = endColor.getRGBA(traits: traits)

        let redDelta = end.red - start.red
        let greenDelta = end.green - start.green
        let blueDelta = end.blue - start.blue
        let alphaDelta = end.alpha - start.alpha

        return (0 ..< count).map { step in
            let progress = CGFloat(step) / CGFloat(count - 1)

            let red = start.red + redDelta * progress
            let green = start.green + greenDelta * progress
            let blue = start.blue + blueDelta * progress
            let alpha = start.alpha + alphaDelta * progress

            return Color(UIColor(red: red, green: green, blue: blue, alpha: alpha))
        }
    }
}

struct TraitsReader<Content: View>: View {
    class TraitObserver: UIView {
        var traitsDidChange: ((UITraitCollection) -> Void)?

        // note that this method is deprecated in iOS 17.0
        override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
            DispatchQueue.main.async {
                self.traitsDidChange?(self.traitCollection)
            }
        }
    }
    
    struct TraitsAccessor: UIViewRepresentable {
        @Binding var traits: UITraitCollection
        
        func makeUIView(context: Context) -> TraitObserver {
            TraitObserver()
        }
        
        func updateUIView(_ uiView: TraitObserver, context: Context) {
            uiView.traitsDidChange = { traits = $0 }
        }
    }
    
    @ViewBuilder let content: (UITraitCollection) -> Content
    @State var traits: UITraitCollection = .init()
    
    var body: some View {
        content(traits).background {
            TraitsAccessor(traits: $traits)
        }
    }
}

struct ColorRangeExamples: View {
    var body: some View {
        TraitsReader { traits in
            VStack(spacing: 30) {
                Color.accent.frame(height: 100).cornerRadius(10)
                
                // Example 1: 5 colors between accent and secondary
                let example1 = Color.colorRange(from: .accent, to: .secondary, count: 5, traits: traits)
                HStack(spacing: 0) {
                    ForEach(0 ..< example1.count, id: \.self) { index in
                        example1[index]
                    }
                }
                .frame(height: 50)
                .cornerRadius(10)
            }
            .padding()
        }
    }
}

本文标签: swiftuiWrong dark mode color accentStack Overflow