You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI中如何根据背景图片动态设置文本颜色?

SwiftUI中如何根据背景图片动态设置文本颜色?

这个问题我之前做类似书籍封面视图时也踩过坑!浅色背景上用白字真的完全看不清,咱们可以通过把SwiftUI的Image转成UIImage,计算背景图的平均亮度,再根据亮度决定文本用黑还是白。下面给你一步步的实现方案:

第一步:实现计算图片平均亮度的工具函数

首先写一个函数,传入UIImage就能算出它的平均亮度。这里用的是ITU-R BT.709的亮度公式,比简单平均RGB更符合人眼对亮度的感知,结果会更准确:

func calculateAverageBrightness(image: UIImage) -> CGFloat {
    guard let cgImage = image.cgImage else { return 0.5 }
    let width = cgImage.width
    let height = cgImage.height
    let bytesPerPixel = 4
    let bytesPerRow = bytesPerPixel * width
    let bitsPerComponent = 8
    let totalBytes = width * height * bytesPerPixel
    
    var pixelData = [UInt8](repeating: 0, count: totalBytes)
    let colorSpace = CGColorSpaceCreateDeviceRGB()
    let context = CGContext(data: &pixelData,
                            width: width,
                            height: height,
                            bitsPerComponent: bitsPerComponent,
                            bytesPerRow: bytesPerRow,
                            space: colorSpace,
                            bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue)
    
    context?.draw(cgImage, in: CGRect(x: 0, y: 0, width: width, height: height))
    
    var totalBrightness: CGFloat = 0
    for y in 0..<height {
        for x in 0..<width {
            let index = (y * width + x) * bytesPerPixel
            let red = CGFloat(pixelData[index]) / 255.0
            let green = CGFloat(pixelData[index + 1]) / 255.0
            let blue = CGFloat(pixelData[index + 2]) / 255.0
            // 人眼对绿色最敏感,所以权重最高
            let brightness = (0.2126 * red) + (0.7152 * green) + (0.0722 * blue)
            totalBrightness += brightness
        }
    }
    return totalBrightness / CGFloat(width * height)
}

第二步:给SwiftUI Image加扩展,转成UIImage

因为上面的函数需要UIImage,但AsyncImage返回的是SwiftUI的Image,所以咱们加个扩展把它转成UIImage:

extension Image {
    func asUIImage() -> UIImage {
        let controller = UIHostingController(rootView: self)
        controller.view.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
        return controller.view.asImage()
    }
}

extension UIView {
    func asImage() -> UIImage {
        let renderer = UIGraphicsImageRenderer(bounds: bounds)
        return renderer.image { rendererContext in
            layer.render(in: rendererContext.cgContext)
        }
    }
}

第三步:在视图中动态计算文本颜色

咱们用@State变量存储文本颜色,当AsyncImage加载成功后,计算图片亮度,再更新文本颜色。亮度大于0.5说明背景偏亮,用黑色文本;反之用白色:

struct BookCoverView: View {
    let coverURL: String
    let title: String
    let author: String?
    let geometry: GeometryProxy
    
    @State private var textColor: Color = .white
    
    var body: some View {
        VStack {
            // 封面图部分
            AsyncImage(url: URL(string: coverURL)) { phase in
                switch phase {
                case .failure:
                    Image(systemName: "photo")
                        .font(.largeTitle)
                case .success(let image):
                    image.resizable()
                        .onAppear {
                            // 转成UIImage并计算亮度
                            let uiImage = image.asUIImage()
                            let brightness = calculateAverageBrightness(image: uiImage)
                            textColor = brightness > 0.5 ? .black : .white
                        }
                default:
                    ProgressView()
                }
            }
            .scaledToFit()
            .frame(height: 300)
            .cornerRadius(10)
            .padding()
            
            // 文本部分 - 使用动态计算的textColor
            VStack {
                Text(title)
                    .font(.title)
                    .foregroundStyle(textColor)
                if let author = author {
                    Text(author)
                        .font(.subheadline)
                        .foregroundStyle(textColor)
                }
            }
            .padding()
        }
        .padding(.top, geometry.safeAreaInsets.top)
        .frame(maxWidth: .infinity)
        .background(
            AsyncImage(url: URL(string: coverURL)) { phase in
                switch phase {
                case .failure:
                    Color.gray
                case .success(let image):
                    image.resizable()
                default:
                    Color.gray
                }
            }
            .blur(radius: 30, opaque: true)
        )
    }
}

额外优化建议

  • 如果你担心重复加载图片,可以把AsyncImage的结果缓存到@State变量里,比如用@State private var cachedImage: UIImage?,这样封面图和背景图都用同一个缓存的图片,不用重复下载。
  • 要是觉得计算整个图片的亮度太耗时,可以缩小图片尺寸后再计算,比如先把UIImage缩成100x100的小图,这样计算速度会快很多,结果也不会差太多。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.07 07:34:36