macos - Image from ImagePicker in landscape - Stack Overflow

时间: 2025-01-06 admin 业界

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.

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.

Share Improve this question edited yesterday Jay asked yesterday JayJay 35.6k19 gold badges58 silver badges85 bronze badges 2
  • does this SO post answer you question stackoverflow.com/questions/77714719/… – workingdog support Ukraine Commented yesterday
  • @workingdogsupportUkraine It mostly addresses the question with code - thank you. The big picture answer consists of a lot of pieces; Image view only supports PNG filetypes, .loadTransferable with Image.self looses orientation data so it can be used but the orientation would need to be captured from the image and then re-applied after transfer. I updated the question with a bit more info and added an answer which seems to be working - somewhat based on your answer, which I upvoted. – Jay Commented yesterday
Add a comment  | 

2 Answers 2

Reset to default 0

What gives a photo portrait orientation is the way the phone was held when the picture was taken. That information is written into the photo's metadata. It is up to you to extract that information and compensate when you make an Image from it.

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