import SwiftUI import UIKit struct OrientationAwareImage: View { let imagePath: String let contentMode: ContentMode @State private var uiImage: UIImage? @State private var isLoading = true @State private var hasFailed = false init(imagePath: String, contentMode: ContentMode = .fit) { self.imagePath = imagePath self.contentMode = contentMode } var body: some View { Group { if let uiImage = uiImage { Image(uiImage: uiImage) .resizable() .aspectRatio(contentMode: contentMode) } else if hasFailed { Image(systemName: "photo") .foregroundColor(.gray) } else { ProgressView() .progressViewStyle(CircularProgressViewStyle()) } } .onAppear { loadImageWithCorrectOrientation() } .onChange(of: imagePath) { _ in loadImageWithCorrectOrientation() } } private func loadImageWithCorrectOrientation() { Task.detached(priority: .userInitiated) { let correctedImage = await loadAndCorrectImage() await MainActor.run { self.uiImage = correctedImage self.isLoading = false self.hasFailed = correctedImage == nil } } } private func loadAndCorrectImage() async -> UIImage? { guard let data = ImageManager.shared.loadImageData(fromPath: imagePath) else { return nil } guard let originalImage = UIImage(data: data) else { return nil } return correctImageOrientation(originalImage) } /// Corrects the orientation of a UIImage based on its EXIF data private func correctImageOrientation(_ image: UIImage) -> UIImage { // If the image is already in the correct orientation, return as-is if image.imageOrientation == .up { return image } // Calculate the proper transformation matrix var transform = CGAffineTransform.identity switch image.imageOrientation { case .down, .downMirrored: transform = transform.translatedBy(x: image.size.width, y: image.size.height) transform = transform.rotated(by: .pi) case .left, .leftMirrored: transform = transform.translatedBy(x: image.size.width, y: 0) transform = transform.rotated(by: .pi / 2) case .right, .rightMirrored: transform = transform.translatedBy(x: 0, y: image.size.height) transform = transform.rotated(by: -.pi / 2) case .up, .upMirrored: break @unknown default: break } switch image.imageOrientation { case .upMirrored, .downMirrored: transform = transform.translatedBy(x: image.size.width, y: 0) transform = transform.scaledBy(x: -1, y: 1) case .leftMirrored, .rightMirrored: transform = transform.translatedBy(x: image.size.height, y: 0) transform = transform.scaledBy(x: -1, y: 1) case .up, .down, .left, .right: break @unknown default: break } // Create a new image context and apply the transformation guard let cgImage = image.cgImage else { return image } let context = CGContext( data: nil, width: Int(image.size.width), height: Int(image.size.height), bitsPerComponent: cgImage.bitsPerComponent, bytesPerRow: 0, space: cgImage.colorSpace ?? CGColorSpaceCreateDeviceRGB(), bitmapInfo: cgImage.bitmapInfo.rawValue ) guard let ctx = context else { return image } ctx.concatenate(transform) switch image.imageOrientation { case .left, .leftMirrored, .right, .rightMirrored: ctx.draw( cgImage, in: CGRect(x: 0, y: 0, width: image.size.height, height: image.size.width)) default: ctx.draw( cgImage, in: CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height)) } guard let newCGImage = ctx.makeImage() else { return image } return UIImage(cgImage: newCGImage) } } // MARK: - Convenience Extensions extension OrientationAwareImage { /// Creates an orientation-aware image with fill content mode static func fill(imagePath: String) -> OrientationAwareImage { OrientationAwareImage(imagePath: imagePath, contentMode: .fill) } /// Creates an orientation-aware image with fit content mode static func fit(imagePath: String) -> OrientationAwareImage { OrientationAwareImage(imagePath: imagePath, contentMode: .fit) } }