Issue: Images from an ImagePicker (macOS) are displaying in landscape regardless of their actual orientation.
Desired Result: Images that are portrait should display as portrait
Steps to duplicate: Copy and paste the below code into a fresh project. Click the Select a photo button and select a picture that is portrait orientation. When it displays it will be rotated 90 degrees as landscape
Discussion: This seems to be related to the Image view object as it happens even within a PhotosPicker
Code: (import PhotosUI at top)
struct ContentView: View {
@State private var pickerItem: PhotosPickerItem?
@State private var selectedImage: Image?
var body: some View {
VStack {
PhotosPicker("Select a photo", selection: $pickerItem)
.onChange(of: pickerItem) {
Task {
selectedImage = try await pickerItem?.loadTransferable(type: Image.self)
}
}
if selectedImage != nil {
selectedImage!
.resizable()
.scaledToFit()
}
}
.frame(width: 300, height: 200)
.padding()
}
}
#Preview {
ContentView()
}
Edit:
As a test I created a new app with an Image property populated with a tall, portrait JPG. That Image view displays in the correct orientation without any additional code.
However, if that same image is selected using PhotosPicker (coupled with .loadTransferable), it looses its orientation upon display.
This would indicate the issue is not with Image view itself but with the Transferable Protocol (.loadTransferable) loosing orientation, along with Image only supports PNG file types and the picker returning HEIC images from the Photos library.
There are lots of pieces to the answer:
Image views only support PNG filetypes whereas the PhotosPicker returns HEIC images.
Populating an Image view via the Transfer protocol with Image.self looses orientation information ( per @matt answer ) so if .loadTransferable
is used with Image.self
, the orientation must be captured from the original image and the re-applied to the transferred image
Noting that Image views, if populated directly from a PNG, assets etc do work correctly without additional code - .loadTransferable
is the trouble.
@workingdogsupportUkraine answer here, led me to a more complete, macOS solution.
My solution was to create a structure that conforms to the Transferable Protocol, where data
is used which maintains the orientation. Instantiate an NSImage using that data, as this is macOS, and then creating the Image from the NSImage data, keeping the orientation. Here's the struct
struct ImageTransferable: Transferable {
let transferredImage: Image
enum TransferringError: Error {
case transferFailed
}
static var transferRepresentation: some TransferRepresentation {
DataRepresentation(importedContentType: .image) { data in
guard let nsimage = NSImage(data: data) else {
throw TransferringError.transferFailed
}
let image = Image(nsImage: nsimage)
return ImageTransferable(transferredImage: image)
}
}
}
note that I tried importedContentType: .heic
but it was inconsistent so using .image
seems better
And the implementation is
Task {
if let loaded = try await pickerItem?.loadTransferable(type: ImageTransferable.self) {
selectedImage = loaded.transferredImage
} else {
print("Image load failed")
}
}