Why do symbol images in UIKit and SwiftUI have different size?

Hi!

I'm trying to port a feature from UIKit to SwiftUI.

Pixel-perfection isn't a hard requirement, but it makes it easier to image-diff the two implementations to find places that need adjustment.

I noticed that when using symbol images, even if the visual size of the opaque part of the symbol is the same, the frame (bounds? margin? extents? padding? Trying to find a word here for the transparent box around the symbol that's UI-framework-agnostic.) is different, which affects apparent whitespace when the symbol is placed in a view.

I created a minimal example in a SwiftUI preview:

struct MyCoolView_Previews: PreviewProvider {
  struct UIImageViewWrapper: UIViewRepresentable {
    var uiImage: UIImage

    func makeUIView(context: Context) -> UIImageView {
      let uiImageView = UIImageView()
      uiImageView.preferredSymbolConfiguration = UIImage.SymbolConfiguration(
        font: UIFont.systemFont(ofSize: 64)
      )
      uiImageView.tintColor = UIColor.black
      return uiImageView
    }

    func updateUIView(_ uiView: UIImageView, context: Context) {
      uiView.image = uiImage
    }
  }

  static var previews: some View {
    VStack {
      UIImageViewWrapper(uiImage: UIImage(systemName: "exclamationmark.triangle.fill")!)
        .background(Color.pink)
        .fixedSize()

      Image(systemName: "exclamationmark.triangle.fill")
        .font(Font.system(size: 64))
        .background(Color.pink)

      ZStack {
        UIImageViewWrapper(uiImage: UIImage(systemName: "exclamationmark.triangle.fill")!)
          .background(Color.blue)
          .fixedSize()

        Image(systemName: "exclamationmark.triangle.fill")
          .font(Font.system(size: 64))
          .background(Color.pink)
          .opacity(0.5)
      }
    }
  }
}

From top to bottom, the screenshot shows the UIKit version, then the SwiftUI version, and finally the two versions overlaid to show where they don't overlap. Note that the blue regions above and below the third item represents where the frame of the UIKit version extends past the frame of the SwiftUI version.

Does anyone know why these are different?

Is there a property that I can set on the SwiftUI version to make it behave like UIKit, or vice-versa?

Thanks!

PS: The difference in frame appears to depend on the specific symbol being used. Here it is for "square.and.arrow.up":

Replies

In UIKit, symbol images have some additional vertical insets so that horizontally-arranged UILabels and UIImageViews can be correctly aligned both with centerY alignment and firstBaseline alignment.

Although symbols are treated as images in the UI frameworks, you should still treat them as text. As long as you are using baseline alignment, you shouldn't notice a difference between the UIKit behavior and the SwiftUI behavior. Avoid aligning views to the top or bottom of symbol images.

If you're still interested in a way to remove the additional insets, please file a feedback report and describe your use case. Thanks!

Facing the same issue - would love the option to remove the insets.