Austin Roach
Austin Roach

Reputation: 21

Displaying an image of a ruler with accurate dimensions across multiple iOS devices using SwiftUI

I am searching for a way to define the size of an image in terms of inches or millimeters instead of points or pixels. I want to display an image of a ruler on a white background. Regardless of the device (i.e. iPad air, iPad pro, iPhone se, iPhone 11, iPhone 11 pro, etc.) I want the image on the screen to fill a consistent dimension when measured with a physical ruler.

I have seen solutions that center on creating separate images for each device, but I'd be really delighted to discover a single method that I can use to scale the size of a single image across all iOS devices.

If any of my language is erroneous or confusing, please feel free to point it out/ask for clarification. I'm a green dev hungry for critique.

Upvotes: 2

Views: 996

Answers (2)

ohho
ohho

Reputation: 51921

There is a package for device PPI detection: Clafou/DevicePpi

let ppi: Double = {
    switch Ppi.get() {
    case .success(let ppi):
        return ppi
    case .unknown(let bestGuessPpi, let error):
        // A bestGuessPpi value is provided but may be incorrect
        // Treat as a non-fatal error -- e.g. log to your backend and/or display a message
        return bestGuessPpi
    }
}()

Upvotes: 0

vacawama
vacawama

Reputation: 154583

This in general is harder than it ought to be. Apple doesn't want you designing interfaces for specific devices, but for this application you have to know the specific device you are running on so that you can look up the points per inch for that device. When I say look up, you'll need to provide your own table (dictionary) that maps device name to points per inch.

First thing first. How do you get the device name?

You can get that by calling:

UIDevice.current.name

To find what the various devices return, run this:

struct ContentView: View {

    var body: some View {
        HStack {
            Text(UIDevice.current.name)
        }
    }
}

on the various device simulators to discover what they return. Some of the more recent ones are "iPhone 11 Pro Max", "iPhone 11 Pro", "iPhone 11", "iPad Pro (12.9-inch) (4th generation)".

In SwiftUI, when you specify frame sizes, you are working in points, not pixels. So you need to know the points per inch for all of the devices.

This table contains a great deal of information on the many devices and their resolutions, and point dimensions. With some work you can derive points per inch for the various devices. In most cases, the points per inch is just the pixels per inch divided by the image asset multiplier (@2x or @3x). The exception is the 5.5" iPhones (6S+, 7+, 7S+, 8+) which don't quite have as high a resolution as they should, so there is an extra 1.15 scale factor in there.

Once you have the points per inch, I would recommend creating a dictionary mapping device name to points per inch.

let pointsPerInch: [String : CGFloat] = [
    "iPhone 8": 163,                               // 326 ppi / 2
    "iPhone 8 Plus": 153.716,                      // 401 ppi * 1.15 / 3
    "iPhone 11": 163,                              // 326 ppi / 2
    "iPhone 11 Pro": 152.66,                       // 458 ppi / 3
    "iPhone 11 Pro Max": 152.66,                   // 458 ppi / 3
    "iPad Pro (12.9-inch) (4th generation)": 132   // 264 ppi / 2
]

Finally, you'd have some images of a ruler. You'll want to provide @2x and @3x versions of those images in your image assets. I'd suggest using 326 ppi for the @2x images and 458 ppi for the @3x images. iOS will automatically load the @2x or @3x version that is appropriate for your device.

In your app, you'd look up the device name and then use your dictionary to find the points per inch. Then you'd multiply points per inch by the size of the image you will display (for example, a 12 inch ruler would be points per inch * 12). This would give you the points dimensions to use for the frame size of your image.

Upvotes: 1

Related Questions