macosswiftui

Image from ImagePicker in landscape


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.


Solution

  • 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")
        }
    }