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

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()
        }
    }
}

显示效果对比

AsyncImage与BackPortAsyncImage显示效果对比

解决方案

方法1:自定义图片处理器,读取EXIF方向并旋转图片

原生AsyncImage的默认实现可能忽略了EXIF方向,我们可以通过自定义图片转换逻辑,手动读取图片的EXIF方向信息并修正:

  1. 添加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
    }
}
  1. 添加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)
        }
    }
}
  1. 修改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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 23:55:25