16

I have an image that I'm hard coding the size, but realized it's not scaling for larger size categories. How can I set a preferred size and let it scale up to different sizes automatically?

This is what my code looks like:

HStack(alignment: .top, spacing: 4) {
    Text("Some text")
    Button(action: { showAlert = true }) {
        Image(systemName: "questionmark.circle.fill")
            .resizable()
            .frame(width: 12, height: 12)
            .foregroundColor(.secondary)
    }
}

I also have other scenario where it's not using a SF Symbol:

Button(action: action) {
    Label(
        title: {
            Text(title)
                .foregroundColor(Color(.label))
        },
        icon: {
            Image("twitter")
                .resizable()
                .frame(width: 24, height: 24)
        }
    )
}

This is how it looks in preview in different sizes, but the images are tiny in the larger scales. How do I handle this to achieve accessibility?

enter image description here

3 Answers 3

36

SwiftUI 2.0 provides ScaledMetric for this purpose.

Here is a demo of solution. Tested with Xcode 12.1 / iOS 14.1

Normal text: demo2

Largest text: demo

struct TestScaledImage: View {
    @ScaledMetric var scale: CGFloat = 1     // << here !!
    var body: some View {
        HStack(alignment: .top, spacing: 4) {
            Text("Some text")
            Button(action: {  }) {
                Image(systemName: "questionmark.circle.fill")
                    .resizable()
                    .frame(width: 12 * scale, height: 12 * scale)  // << here !!
                    .foregroundColor(.secondary)
            }
        }
    }
}
Sign up to request clarification or add additional context in comments.

3 Comments

Wow so elegant!! Exactly what I was hoping for!! Thank you SwiftUI team and @Asperi for pointing this out!! It even works in preview
Just to note in case it helps someone else.. in Xcode 12.3, I had to do: @ScaledMetric var imageSize: CGFloat = 12 instead of multiplying
If your text was set to a non-default size (say .font(.largeTitle)), then use @ScaledMetric(relativeTo:), e.g. @ScaledMetric(relativeTo: .largeTitle) var imageSize: CGFloat = _x_
0

Wrap your code in a GeometryReader

for example

HStack(alignment: .top, spacing: 4) {
    GeometryReader { geo in 
        Text("Some text")
        Button(action: { showAlert = true }) {
            Image(systemName: "questionmark.circle.fill")
                .resizable()
                .frame(width: geo.size.width / 9, height: geo.size.height / 9)
                .foregroundColor(.secondary)
        }
    }
}

Then just adjust to whatever suits you

A GeometryReader reads whatever the current devices width and height info is and allows you adjust that instead of hard coding.

Comments

0

✅ In addition to the accepted answer:

You should use the .font modifier to size the system images:

@ScaledMetric var size: CGFloat = 12

var body: some View {
    Image(systemName: "questionmark.circle.fill")                
        .font(.system(size: size)) // 👈 Using `.font` instead of `.resizable` to keep the quality
}

Using .resizable modifier turns the image into a static raster format with less quality, but using the .font modifier prevents it

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.