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
|
1 Answer
Reset to default 0Try 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")
}
}
}
}
}
// 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