SwiftUI AsyncImage无法识别图片EXIF方向,如何修正显示?
SwiftUI AsyncImage 无法正确识别图片EXIF方向的问题
问题概述
使用SwiftUI的AsyncImage从Parse Server下载JPG图片时,图片的EXIF方向值为6(需逆时针旋转90度),但AsyncImage加载后丢失了该方向信息,导致图片显示旋转异常。替换为BackPortAsyncImage则能正常显示正确方向。需要解决如何让AsyncImage检测并自动修正图片方向的问题。
代码示例
初始简化代码
AsyncImage(url: photoURL) { image in image .resizable() .scaledToFill() .frame(width: 200, height: 200, alignment: .leading) } placeholder: { ProgressView() }
实际场景代码
主视图(带列表和ScrollView布局)
struct TimeLineView: View { // SwiftUI视图模型 @StateObject var viewModel = PFTour.query(matchesKeyInQuery(key: "routeID", queryKey: "uniqueID", query: PFRoute.query("state" == "Colorado"))) .order([.descending("date")]) .include("route") .include("creator") .include("photos") .viewModel var body: some View { Group { if let error = viewModel.error { Text(error.description) } else { List(viewModel.results, id: \.id) { tour in ParseTourImageOnlyScrollView(tour: tour) } .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center) } Spacer() } .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center) .edgesIgnoringSafeArea(.all) .onAppear(perform: { viewModel.find() }) } }
单元格ScrollView视图
struct ParseTourImageOnlyScrollView: View { let tour: PFTour var body: some View { VStack { Divider() ScrollView(.horizontal) { HStack(spacing: 5.0) { if let photos = tour.photos { ForEach(photos, id: \.self) { parsePhoto in PhotoOnlyView(photoFile: parsePhoto.photo!) } } } } Divider() Spacer() } } }
图片对比视图(AsyncImage vs BackPortAsyncImage)
struct PhotoOnlyView: View { let photoFile: ParseFile var body: some View { if #available(iOS 15.0, *) { VStack{ SwiftUI.AsyncImage(url: photoFile.url) { image in image .resizable() .scaledToFill() } placeholder: { ProgressView() } .frame(width: UIScreen.main.bounds.size.width - 150, height: UIScreen.main.bounds.size.width - 150, alignment: .leading) BackportAsyncImage(url: photoFile.url) { image in image .resizable() .scaledToFill() } placeholder: { ProgressView() } .frame(width: UIScreen.main.bounds.size.width - 150, height: UIScreen.main.bounds.size.width - 150, alignment: .leading) } } else { ProgressView() } } }
显示效果对比

解决方案
方法1:自定义图片处理器,读取EXIF方向并旋转图片
原生AsyncImage的默认实现可能忽略了EXIF方向,我们可以通过自定义图片转换逻辑,手动读取图片的EXIF方向信息并修正:
- 添加UIImage扩展,用于修正方向:
extension UIImage { func fixOrientation() -> UIImage { guard imageOrientation != .up else { return self } var transform = CGAffineTransform.identity switch imageOrientation { case .down, .downMirrored: transform = transform.translatedBy(x: size.width, y: size.height) transform = transform.rotated(by: .pi) case .left, .leftMirrored: transform = transform.translatedBy(x: size.width, y: 0) transform = transform.rotated(by: .pi / 2) case .right, .rightMirrored: transform = transform.translatedBy(x: 0, y: size.height) transform = transform.rotated(by: -.pi / 2) case .up, .upMirrored: break @unknown default: break } switch imageOrientation { case .upMirrored, .downMirrored: transform = transform.translatedBy(x: size.width, y: 0) transform = transform.scaledBy(x: -1, y: 1) case .leftMirrored, .rightMirrored: transform = transform.translatedBy(x: size.height, y: 0) transform = transform.scaledBy(x: -1, y: 1) case .up, .down, .left, .right: break @unknown default: break } if let cgImage = self.cgImage, let colorSpace = cgImage.colorSpace { let context = CGContext(data: nil, width: Int(size.width), height: Int(size.height), bitsPerComponent: cgImage.bitsPerComponent, bytesPerRow: 0, space: colorSpace, bitmapInfo: cgImage.bitmapInfo.rawValue) context?.concatenate(transform) switch imageOrientation { case .left, .leftMirrored, .right, .rightMirrored: context?.draw(cgImage, in: CGRect(x: 0, y: 0, width: size.height, height: size.width)) default: context?.draw(cgImage, in: CGRect(x: 0, y: 0, width: size.width, height: size.height)) } if let correctedCGImage = context?.makeImage() { return UIImage(cgImage: correctedCGImage) } } return self } }
- 添加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) let renderer = UIGraphicsImageRenderer(size: controller.view.bounds.size) return renderer.image { ctx in controller.view.drawHierarchy(in: controller.view.bounds, afterScreenUpdates: true) } } }
- 修改AsyncImage的使用逻辑,加入方向修正:
SwiftUI.AsyncImage(url: photoFile.url) { phase in switch phase { case .success(let image): // 转换为UIImage并修正方向后再转回Image if let uiImage = image.asUIImage() { Image(uiImage: uiImage.fixOrientation()) .resizable() .scaledToFill() } else { image .resizable() .scaledToFill() } case .failure(_): Image(systemName: "photo.badge.exclamationmark") .resizable() .scaledToFit() case .empty: ProgressView() @unknown default: ProgressView() } } .frame(width: UIScreen.main.bounds.size.width - 150, height: UIScreen.main.bounds.size.width - 150, alignment: .leading)
方法说明
BackPortAsyncImage内部已处理EXIF方向修正,而原生AsyncImage在嵌套ScrollView+List这类复杂布局中,可能因视图复用或解码优化忽略了EXIF信息,通过手动修正图片方向可解决该问题。
内容的提问来源于stack exchange,提问作者David
相关产品推荐
相关产品推荐

