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
相关产品推荐
相关产品推荐

