Search code examples
iosswiftui

Why do images affect location of placement in navigation bar?


During experimenting with adding an image to the navigation bar I've noticed that an image isn't placed in the same spot as a system image or text. I have a ToolbarItem that contains a VStack with an image and text. The ToolbarItem has an alignment of topBarLeading so it is placed to the left. The VStack has an alignment of center so the image and the text are aligned at the center with each other. When I use a SF Symbol everything is in the location I would expect. When I change the symbol for an image that is resized and scaled to fit everything is offset further to the right than I expect. What causes this behavior and how can I get the image and text to the left?

This is how the toolbar item looks with an SF Symbol.

ToolbarItem(placement: .topBarLeading) {
    VStack(alignment: .center) {
        Image(systemName: "magnifyingglass")
            .resizable()
            .scaledToFit()

        Text("Test text")
    }
}

enter image description here

And this is how the toolbar item looks with an image. The image is taken from the magnifying glass page of Wikipedia.

ToolbarItem(placement: .topBarLeading) {
    VStack(alignment: .center) {
        Image(.magGlassRequest)
            .resizable()
            .scaledToFit()

        Text("Test text")
    }
}

enter image description here


Solution

  • It seems that the layout is based on the ideal size of the image, this being its natural dimensions.

    The image you are using has dimensions 456×373. An iPhone screen in portrait orientation has a width of 393. So since the image is wider than the screen, it ends up being centered.

    In landscape orientation, the width of an iPhone screen inside the safe area insets is 734. You then find, the image is no longer centered on the screen. However, it is still centered within a space of width 456, added to which is a leading offset of about 17 points (to give a gap to the left side).

    The following body content can be used to demonstrate this:

    VStack(alignment: .leading) {
        HStack(spacing: 0) {
            Color.orange
                .frame(width: 17)
            Color.yellow
                .frame(maxWidth: 456)
                .overlay { Divider() }
        }
        .frame(height: 100)
        Text("hello")
            .frame(maxWidth: .infinity)
        Spacer()
    }
    

    Screenshot

    To fix the alignment, try applying a different ideal size to constrain the image.

    • You can specify either width or height (or both).
    • As long as the ideal width of the image is not greater than the ideal width of the text, the horizontal alignment will be determined by the text.
    Image(.magGlassRequest)
        .resizable()
        .scaledToFit()
        .frame(idealHeight: 40) // 👈 HERE
    

    The text will then be left-aligned on the screen, with a gap to the leading edge of about 17 points (as above):

    Screenshot