最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

swift - Rotate a photo gallery image updating only metadata - Stack Overflow

programmeradmin0浏览0评论

I've been straggling for a couple of days, asking gpt, gemini and claude plus google without success. I'm writing a small iOS app to manage images, and one thing I want is a "one click rotate" left and right with dedicated UI buttons (with the final goal to update EXIF orientation).

my latest code:

    static func rotateImage(image: UIImage, by angle: CGFloat) -> UIImage? {
        guard let cgImage = image.cgImage else { return nil }

        let radians = angle * .pi / 180.0
        let rotatedSize = CGRect(origin: .zero, size: image.size)
            .applying(CGAffineTransform(rotationAngle: radians))
            .size

        UIGraphicsBeginImageContextWithOptions(rotatedSize, false, image.scale)
        guard let context = UIGraphicsGetCurrentContext() else { return nil }

        context.translateBy(x: rotatedSize.width / 2, y: rotatedSize.height / 2)
        context.rotate(by: radians)
        context.draw(cgImage, in: CGRect(x: -image.size.width / 2, y: -image.size.height / 2, width: image.size.width, height: image.size.height))

        let rotatedImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()

        return rotatedImage
    }

the calling function reports:

[03:00:15] ✅ Image rotated and saved at file:///private/var/mobile/Containers/Data/Application/BFCD9A75-9300-419F-9C7E-373E74E66C12/tmp/rotated_image.heic
[03:00:15] ❌ Error applying rotation: The operation couldn’t be completed. (PHPhotosErrorDomain error 3303.)

I tried to investigate this error 3303 without success.

the closest I came (with different code) was creating a rotated copy in the gallery which appeared as the latest image, which is not my goal.

I really want to simply rotate the direction of the image in EXIF. NOT editing the actual photo. I know iOS doesn't directly let me edit EXIF, but the iOS Photos app "tricks me" into thinking I've edited the photo (even if I can revert to original). I'd like my app to be able to do the same.

I'm very much out of my depth and currently I'm importing everything under the sun:

import SwiftUI
import Photos
import ImageIO
import MobileCoreServices
import UniformTypeIdentifiers
import CoreGraphics
 

and finally this is the wrapper in case this might be the reason:

    private func rotateImage(left: Bool) {
    let asset = imageAssets[selectedImageIndex]  // Get the currently selected image

    let options = PHContentEditingInputRequestOptions()
    options.isNetworkAccessAllowed = true

    asset.requestContentEditingInput(with: options) { editingInput, _ in
        guard let editingInput = editingInput, let fullSizeImageURL = editingInput.fullSizeImageURL else {
            Logger.log("❌ Error: Could not retrieve full-size image URL")
            return
        }

        // Load original image
        guard let imageSource = CGImageSourceCreateWithURL(fullSizeImageURL as CFURL, nil),
              let cgImage = CGImageSourceCreateImageAtIndex(imageSource, 0, nil) else {
            Logger.log("❌ Error: Could not create CGImage from image source")
            return
        }

        let originalImage = UIImage(cgImage: cgImage)
        let rotationAngle = left ? -90.0 : 90.0

        // ✅ Rotate image properly using Core Graphics
        guard let rotatedImage = ImageUtilities.rotateImage(image: originalImage, by: CGFloat(rotationAngle)) else {
            Logger.log("❌ Error rotating image")
            return
        }

        // ✅ Prepare output URL
        let fileExtension = fullSizeImageURL.pathExtension.lowercased()
        let tempURL = FileManager.default.temporaryDirectory.appendingPathComponent("rotated_image.\(fileExtension)")

        // ✅ Save rotated image data
        do {
            if fileExtension == "heic" {
                try rotatedImage.heicData(compressionQuality: 1.0)?.write(to: tempURL)
            } else {
                try rotatedImage.jpegData(compressionQuality: 1.0)?.write(to: tempURL)
            }
            Logger.log("✅ Image rotated and saved at \(tempURL)")
        } catch {
            Logger.log("❌ Error saving rotated image: \(error.localizedDescription)")
            return
        }

        // ✅ Create an empty PHAdjustmentData object
        let adjustmentData = PHAdjustmentData(
            formatIdentifier: "com.example.photo-rotation",
            formatVersion: "1.0",
            data: Data()  // Empty data (mimics iOS Photos behavior)
        )

        // ✅ Apply the rotation as an edit to the existing asset
        let output = PHContentEditingOutput(contentEditingInput: editingInput)
        output.adjustmentData = adjustmentData
        try? FileManager.default.removeItem(at: output.renderedContentURL) // Clear previous edits
        try? FileManager.default.copyItem(at: tempURL, to: output.renderedContentURL)

        PHPhotoLibrary.shared().performChanges({
            let request = PHAssetChangeRequest(for: asset)
            request.contentEditingOutput = output
        }) { success, error in
            DispatchQueue.main.async {
                if success {
                    Logger.log("✅ Image rotation applied")
                    self.refreshCurrentImage()
                } else {
                    Logger.log("❌ Error applying rotation: \(error?.localizedDescription ?? "Unknown error")")
                }
                try? FileManager.default.removeItem(at: tempURL) // Clean up temp file
            }
        }
    }
}

I've been straggling for a couple of days, asking gpt, gemini and claude plus google without success. I'm writing a small iOS app to manage images, and one thing I want is a "one click rotate" left and right with dedicated UI buttons (with the final goal to update EXIF orientation).

my latest code:

    static func rotateImage(image: UIImage, by angle: CGFloat) -> UIImage? {
        guard let cgImage = image.cgImage else { return nil }

        let radians = angle * .pi / 180.0
        let rotatedSize = CGRect(origin: .zero, size: image.size)
            .applying(CGAffineTransform(rotationAngle: radians))
            .size

        UIGraphicsBeginImageContextWithOptions(rotatedSize, false, image.scale)
        guard let context = UIGraphicsGetCurrentContext() else { return nil }

        context.translateBy(x: rotatedSize.width / 2, y: rotatedSize.height / 2)
        context.rotate(by: radians)
        context.draw(cgImage, in: CGRect(x: -image.size.width / 2, y: -image.size.height / 2, width: image.size.width, height: image.size.height))

        let rotatedImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()

        return rotatedImage
    }

the calling function reports:

[03:00:15] ✅ Image rotated and saved at file:///private/var/mobile/Containers/Data/Application/BFCD9A75-9300-419F-9C7E-373E74E66C12/tmp/rotated_image.heic
[03:00:15] ❌ Error applying rotation: The operation couldn’t be completed. (PHPhotosErrorDomain error 3303.)

I tried to investigate this error 3303 without success.

the closest I came (with different code) was creating a rotated copy in the gallery which appeared as the latest image, which is not my goal.

I really want to simply rotate the direction of the image in EXIF. NOT editing the actual photo. I know iOS doesn't directly let me edit EXIF, but the iOS Photos app "tricks me" into thinking I've edited the photo (even if I can revert to original). I'd like my app to be able to do the same.

I'm very much out of my depth and currently I'm importing everything under the sun:

import SwiftUI
import Photos
import ImageIO
import MobileCoreServices
import UniformTypeIdentifiers
import CoreGraphics
 

and finally this is the wrapper in case this might be the reason:

    private func rotateImage(left: Bool) {
    let asset = imageAssets[selectedImageIndex]  // Get the currently selected image

    let options = PHContentEditingInputRequestOptions()
    options.isNetworkAccessAllowed = true

    asset.requestContentEditingInput(with: options) { editingInput, _ in
        guard let editingInput = editingInput, let fullSizeImageURL = editingInput.fullSizeImageURL else {
            Logger.log("❌ Error: Could not retrieve full-size image URL")
            return
        }

        // Load original image
        guard let imageSource = CGImageSourceCreateWithURL(fullSizeImageURL as CFURL, nil),
              let cgImage = CGImageSourceCreateImageAtIndex(imageSource, 0, nil) else {
            Logger.log("❌ Error: Could not create CGImage from image source")
            return
        }

        let originalImage = UIImage(cgImage: cgImage)
        let rotationAngle = left ? -90.0 : 90.0

        // ✅ Rotate image properly using Core Graphics
        guard let rotatedImage = ImageUtilities.rotateImage(image: originalImage, by: CGFloat(rotationAngle)) else {
            Logger.log("❌ Error rotating image")
            return
        }

        // ✅ Prepare output URL
        let fileExtension = fullSizeImageURL.pathExtension.lowercased()
        let tempURL = FileManager.default.temporaryDirectory.appendingPathComponent("rotated_image.\(fileExtension)")

        // ✅ Save rotated image data
        do {
            if fileExtension == "heic" {
                try rotatedImage.heicData(compressionQuality: 1.0)?.write(to: tempURL)
            } else {
                try rotatedImage.jpegData(compressionQuality: 1.0)?.write(to: tempURL)
            }
            Logger.log("✅ Image rotated and saved at \(tempURL)")
        } catch {
            Logger.log("❌ Error saving rotated image: \(error.localizedDescription)")
            return
        }

        // ✅ Create an empty PHAdjustmentData object
        let adjustmentData = PHAdjustmentData(
            formatIdentifier: "com.example.photo-rotation",
            formatVersion: "1.0",
            data: Data()  // Empty data (mimics iOS Photos behavior)
        )

        // ✅ Apply the rotation as an edit to the existing asset
        let output = PHContentEditingOutput(contentEditingInput: editingInput)
        output.adjustmentData = adjustmentData
        try? FileManager.default.removeItem(at: output.renderedContentURL) // Clear previous edits
        try? FileManager.default.copyItem(at: tempURL, to: output.renderedContentURL)

        PHPhotoLibrary.shared().performChanges({
            let request = PHAssetChangeRequest(for: asset)
            request.contentEditingOutput = output
        }) { success, error in
            DispatchQueue.main.async {
                if success {
                    Logger.log("✅ Image rotation applied")
                    self.refreshCurrentImage()
                } else {
                    Logger.log("❌ Error applying rotation: \(error?.localizedDescription ?? "Unknown error")")
                }
                try? FileManager.default.removeItem(at: tempURL) // Clean up temp file
            }
        }
    }
}
Share Improve this question edited Feb 8 at 22:40 flower42 asked Feb 7 at 11:48 flower42flower42 1111 bronze badge 1
  • I tried also a version of the code which only tries to edit metadata (which is actually what I want) but it also fails (PHPhotosErrorDomain error 3300.) // Create adjustment data with custom metadata request.contentEditingOutput?.adjustmentData = PHAdjustmentData( formatIdentifier: "com.example.photo-rotation", // Unique identifier formatVersion: "1.0", data: try! JSONSerialization.data(withJSONObject: customMetadata, options: []) ) – flower42 Commented Feb 7 at 14:58
Add a comment  | 

1 Answer 1

Reset to default 0

Try this simple approach using a PhotosPicker to pick the photo you want, then use a rotationEffect to rotate the photo displayed by tapping a left or right button. To achieve your goals: ... one thing I want is a "one click rotate" left and right with dedicated UI buttons... and ...NOT editing the actual photo.

struct ContentView: View {
    @State private var photoItem: PhotosPickerItem?
    @State private var photo: Image?
    @State private var angle: Double = 0
    
    var body: some View {
        VStack (spacing: 55) {
            PhotosPicker("Select photo", selection: $photoItem, matching: .images)
            
            if let theImage = photo {
                theImage
                    .resizable()
                    .scaledToFit()
                    .frame(width: 300, height: 300)
                    .rotationEffect(.degrees(angle))
            } else {
                Image(systemName: "photo")
                    .resizable()
                    .scaledToFit()
                    .frame(width: 300, height: 300)
                    .rotationEffect(.degrees(angle))
            }
            
            HStack{
                Button("left") { angle -= 90 }
                Button("right") { angle += 90 }
            }.buttonStyle(.bordered)
        }
        .onChange(of: photoItem) {
            Task {
                if let loaded = try? await photoItem?.loadTransferable(type: Image.self) {
                    photo = loaded
                } else {
                    print("Failed")
                }
            }
        }
    }

}
发布评论

评论列表(0)

  1. 暂无评论