How to resize the image displayed when selecting an item in a Picker view

Is there a way to remove or resize the image from the tag in the Picker view?

Picker("", selection: $selectedCategory) {
    ForEach(categorySM.categories, id: \.self) { category in
        HStack {
            if let inputImage = UIImage(data: category.image ?? Data()) {
                Image(uiImage: inputImage)
                    .resizable()
                    .scaledToFit()
            }
            Text(category.name ?? "")
        }
        .tag(category as CategoryItem?)
    }
}
.font(.callout)
.pickerStyle(.menu)

As you can see in images 1 and 2 below, the image in the tag from the Beverages category is huge and covers almost the entire screen, it also covers the category name (Beverages). Is there a way to remove or resize the image when displaying it on the tag? Basically to make it look like image #3.

Image Link:
https://i.stack.imgur.com/4XpjI.jpg

Accepted Reply

@Claude31 - Thank you very much for your reply. For some reason resizing the UIImage directly solves the issue.

    if let inputImage = UIImage(data: category.image ?? Data()) {
        let resizeImage = inputImage.scalePreservingAspectRatio(targetSize: CGSize(width: 25, height: 25))
        Image(uiImage: resizeImage)
        .resizable()
        .scaledToFit()
    }

Code for resizing the UIImage

    extension UIImage {
        func scalePreservingAspectRatio(targetSize: CGSize) -> UIImage {
            // Determine the scale factor that preserves aspect ratio
            let widthRatio = targetSize.width / size.width
            let heightRatio = targetSize.height / size.height
            
            let scaleFactor = min(widthRatio, heightRatio)
            
            // Compute the new image size that preserves aspect ratio
            let scaledImageSize = CGSize(
                width: size.width * scaleFactor,
                height: size.height * scaleFactor
            )

            // Draw and return the resized UIImage
            let renderer = UIGraphicsImageRenderer(
                size: scaledImageSize
            )

            let scaledImage = renderer.image { _ in
                self.draw(in: CGRect(
                    origin: .zero,
                    size: scaledImageSize
                ))
            }
            return scaledImage
        }
    }

Replies

I could not yet make it work with .menu. Bizarrely, the selected image appears full size when selection is done. I've tried to set a frame on the picker, no avail.

I tried solution from here, to no avail: https://developer.apple.com/forums/thread/132023

So, I concluded that problem comes from .menu style.

You could try to change as described here with Menu: https://arkhyp.medium.com/swiftui-pickers-and-menu-build-a-modern-native-ios-application-90e82a4b521f

@Claude31 - Thank you very much for your reply. For some reason resizing the UIImage directly solves the issue.

    if let inputImage = UIImage(data: category.image ?? Data()) {
        let resizeImage = inputImage.scalePreservingAspectRatio(targetSize: CGSize(width: 25, height: 25))
        Image(uiImage: resizeImage)
        .resizable()
        .scaledToFit()
    }

Code for resizing the UIImage

    extension UIImage {
        func scalePreservingAspectRatio(targetSize: CGSize) -> UIImage {
            // Determine the scale factor that preserves aspect ratio
            let widthRatio = targetSize.width / size.width
            let heightRatio = targetSize.height / size.height
            
            let scaleFactor = min(widthRatio, heightRatio)
            
            // Compute the new image size that preserves aspect ratio
            let scaledImageSize = CGSize(
                width: size.width * scaleFactor,
                height: size.height * scaleFactor
            )

            // Draw and return the resized UIImage
            let renderer = UIGraphicsImageRenderer(
                size: scaledImageSize
            )

            let scaledImage = renderer.image { _ in
                self.draw(in: CGRect(
                    origin: .zero,
                    size: scaledImageSize
                ))
            }
            return scaledImage
        }
    }

Thanks for feedback. When we know, that's logical. The image is now at the new size. That's equivalent to calling directly an image of the correct size.

When Picker has to display the selection, it does not apply the modifiers but uses directly the image…

I'll keep a copy of your finding. Good continuation.

For anyone looking for a macOS (App Kit) solution.

NSImage extension

import Foundation
import AppKit

extension NSImage {
    func scalePreservingAspectRatio(targetSize: NSSize) -> NSImage {
        let widthRatio = targetSize.width / size.width
        let heightRatio = targetSize.height / size.height
        
        let scaleFactor = min(widthRatio, heightRatio)
        
        let scaledImageSize = NSSize(
            width: size.width * scaleFactor,
            height: size.height * scaleFactor
        )
        
        let newImage = NSImage(size: scaledImageSize)
        newImage.lockFocus()
        self.draw(in: NSRect(origin: .zero, size: scaledImageSize), from: NSRect(origin: .zero, size: self.size), operation: .copy, fraction: 1.0)
        newImage.unlockFocus()
        
        return newImage
    }
}

ResizedImage View

import SwiftUI

/// Image resized to a specific size
struct ResizedImage: View {
    let resourceName: String
    let width: Int
    let height: Int
    
    init(_ resourceName: String, width: Int, height: Int) {
        self.resourceName = resourceName
        self.width = width
        self.height = height
    }
    
    var body: some View {
        if let image = NSImage(named: resourceName) {
            let imageResized = image.scalePreservingAspectRatio(targetSize: NSSize(width: 20, height: 20))
            Image(nsImage: imageResized)
        } else {
            // Default questionmark if image is not found
            Image(systemName: "questionmark.square")
        }
    }
}