HStack with SF Symbols Image not aligned centered
Asked Answered
A

2

9

I have this simple SwiftUI code. I want all symbols to be aligned centered, just like the cloud symbol.

struct ContentView : View {
var body: some View {
    HStack(alignment: .center, spacing: 10.0) {
        Image(systemName: "cloud.sun")
        Image(systemName: "cloud")
        Image(systemName: "cloud.bolt")
        Text("Text")
        }.font(.title)
    }
}

But as you can see below, the first and the last symbol are not centered. Am I missing something, or is this a bug?

Centered HStack

Cheers!

Activity answered 13/6, 2019 at 10:3 Comment(5)
Increase the height of the HStack and check.Seleneselenious
With .frame(height: 32) ? Makes no difference. :/Activity
Check all the image and find out the maximum height. Setting the maximum height to the HStack should help I guess.Seleneselenious
Still no luck. Why are the symbols of different height anyway? Isn't the idea behind the symbols that they resize perfectly depending on the font you use?Activity
Can confirm you get the same behaviour with Xcode Version 11.0 beta 2 (11M337n) - released 17th June.Disparity
S
8

This is what it's going on.

enter image description here

The Image views are not resizing.

It looks like they're not aware of their intrinsic content size, or maybe it reports the wrong value.

To fix it:

struct ContentView : View {
    var body: some View {
        HStack(alignment: .center, spacing: 10.0) {
            Image(systemName: "cloud.sun")
                .resizable()
                .aspectRatio(contentMode: .fit)
                .background(Color.red)
            Image(systemName: "cloud")
                .resizable()
                .aspectRatio(contentMode: .fit)
                .background(Color.yellow)
            Image(systemName: "cloud.bolt")
                .resizable()
                .aspectRatio(contentMode: .fit)
                .background(Color.pink)
            Text("Text").background(Color.green)
        }
        .frame(width: 250, height: 50)
        .background(Color.gray)
        .font(.title)

    }
}

...make the Images resizable, and also make sure the aspect ratio is set to .fit, or they will stretch.

Set also frame on the HStack or it will expand to fill the whole screen.

@MartinR suggested an even better solution - creating the images via UIImage - see his comment below.

struct ContentView : View {

    var body: some View {
        HStack {
            Image(uiImage: UIImage(systemName: "cloud.sun")!)
                .background(Color.red)
            Image(uiImage: UIImage(systemName: "cloud")!)
                .background(Color.yellow)
            Image(uiImage: UIImage(systemName: "cloud.bolt")!)
                .background(Color.pink)
            Text("Text").background(Color.green)
        }
        .background(Color.gray)
        .font(.title)

    }

}

Output:

enter image description here

Shovel answered 13/6, 2019 at 12:39 Comment(12)
I thought the SF symbols had a “natural baseline offset” for proper alignment with text. Therefore I would have expected something like HStack(alignment: .lastTextBaseline) to work – but it doesn't.Zara
@MartinR if feels like a bug to be honest - it should work exactly as you saidShovel
If you go via UIImage, e.g. Image(uiImage: UIImage(systemName: "cloud")!) then the alignment seems to work, but the icons are scaled differently.Zara
@MartinR I believe it still looks better than my solution - also, with yours there's no need to set a frame on the view, as it won't stretch to fill the superview.Shovel
I agree that it feels like a bug - setting a mental note to test HStack(alignment:) in future betas.Maness
Thanks. I filed a bug with the new Feedback-Assistent. Jay. :)Activity
I filed a bug: FB7158085: Alignment of Image SF Symbols is RandomGrippe
You'll need to use a templated image if you want to change the color. Image(uiImage: UIImage(systemName:"star.fill")!.withRenderingMode(.alwaysTemplate)) .foregroundColor(.white)Grippe
@MatteoPacini this doesnt work with multline text tho, if i do this in the new line where the text ends there is huge right padding then the image to the rightWoofer
This seems to be fixed in iOS 14, but remains a problem in iOS 13.Ensepulcher
@PaulSolt Any updates on the bug you filed? This doesn't seem to be solved on iOS 13.5.1 yet, but it is solved in the iOS 14 simulator of Xcode beta 3. Is there any good workaround? I've found trying to get the exact same size using the Image(uiImage:) hack to be near-impossible.Ensepulcher
Please duplicate the bug report for iOS 13. I have no updates: Recent Similar Reports: None Resolution: OpenGrippe
W
0

I came across the same problem as you: SF Symbols are not reporting the correct content size on iOS 13. (Though, it is fixed on iOS 14 and above.)

The problem with using UIImage as proposed in Matteo Pacinis solution is the poor compatibility with the SwiftUI dynamics: foreground color and font size (and dynamic type!) are not simply taken from the current SwiftUI context but have to be duplicated into the UIImage configuration (using UIImage(systemName:, withConfiguration:)) wich is often not practical. (see hackingwithswift.com for how to use this).

Looking at the problem above I propose the following solution:

HStack(alignment: .center, spacing: 10.0) {
    Image(systemName: "cloud.sun")
        .hidden()
        .overlay(
            Image(systemName: "cloud.sun")
                .resizable()
                .aspectRatio(contentMode: .fill)
                .background(Color.red)
        )
    Image(systemName: "cloud")
        .hidden()
        .overlay(
            Image(systemName: "cloud")
                .resizable()
                .aspectRatio(contentMode: .fill)
                .background(Color.yellow)
        )
    Image(systemName: "cloud.bolt")
        .hidden()
        .overlay(
            Image(systemName: "cloud.bolt")
                .resizable()
                .aspectRatio(contentMode: .fill)
                .background(Color.pink)
        )
    Text("Text")
        .background(Color.green)
}
.background(Color.gray)
.font(.title)

It looks like a lot of code duplication but it has the advantage, that the symbols scale according to your font, respect the foregroundColor modifier and align according to your desired alignment

Output:

enter image description here

But there is still some issue with this approach: the image still has not the correct intrinsic size, the symbols are simply drawn "nicely centered". This means that the height of the HStack still depends of the height of the Text element. If you simply want to draw the symbols in .largeTitle font and the text in .body font, the result would look like below possibly causing overlapping with neighbouring views:

SF symbols aligned in HStack but drawing over the frame

I will still investigate further to find a solution ensuring the correct view size, as this really annoys me.

Walkerwalkietalkie answered 23/9, 2021 at 12:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.