简体   繁体   中英

How to make SF Symbols in SwiftUI have the same width

Is there a way to make all symbols in the same size have the same width?

As you can see from the screenshot below, person.2.fill has the longest width and square.and.arrow.up is the nearest. Setting the font to a mono-spaced font does not seem to change anything in this case.

 Image(systemName: "square.and.arrow.up").font(Font.system(size: 18, weight:.medium, design:.monospaced))

A workaround would be not "forcing" them to have the same width but horizontally centered inside the same width container.

在此处输入图像描述

I fixed this using PreferenceKey , also work well with dynamic font sizing.

Test in Xcode13.2 Swift5.5

struct ContentView: View { @State private var iconWidth: Double = 0 private var icons: [String] = ["person.crop.circle", "tag", "text.book.closed", "icloud.and.arrow.up"] var body: some View { List(icons, id: \.self) { iconName in HStack { Image(systemName: iconName).sync(with: $iconWidth).frame(width: iconWidth) Text(iconName) }.onPreferenceChange(SymbolWidthPreferenceKey.self) { iconWidth = $0 } } } } struct SymbolWidthPreferenceKey: PreferenceKey { static var defaultValue: Double = 0 static func reduce(value: inout Double, nextValue: () -> Double) { value = max(value, nextValue()) } } struct SymbolWidthModifier: ViewModifier { @Binding var width: Double func body(content: Content) -> some View { content.background(GeometryReader { geo in Color.clear.preference(key: SymbolWidthPreferenceKey.self, value: geo.size.width) }) } } extension Image { func sync(with width: Binding<Double>) -> some View { modifier(SymbolWidthModifier(width: width)) } }

在此处输入图像描述

A workaround for the issue. However, the problem is that it does not work well with dynamic font sizing.

 HStack { Image(systemName: "square.and.arrow.up").font(Font.system(size: 18, weight:.medium, design:.monospaced)).foregroundColor(Color("secondary")) }.frame(minWidth: 30)

在此处输入图像描述

You can set the scalable width/height with the @ScaledMetric property wrapper that is available since iOS 14 or macOS 11.

For example, define this view first:

 struct ScaledFrame<Content>: View where Content: View { @ScaledMetric private var width: Double @ScaledMetric private var height: Double private var alignment: Alignment private var content: () -> Content init( width: ScaledMetric<Double>? = nil, height: ScaledMetric<Double>? = nil, alignment: Alignment =.center, content: @escaping () -> Content ) { _width = width?? ScaledMetric(wrappedValue: -1) _height = height?? ScaledMetric(wrappedValue: -1) self.alignment = alignment self.content = content } var body: some View { content().frame( width: width > 0? width: nil, height: height > 0? height: nil, alignment: alignment) } } // For convenience:) extension View { func scaledFrame( width: Double?, height: Double?, relativeTo textStyle: Font.TextStyle, alignment: Alignment =.center ) -> some View { ScaledFrame( width: width.flatMap { ScaledMetric(wrappedValue: $0, relativeTo: textStyle) }, height: height.flatMap { ScaledMetric(wrappedValue: $0, relativeTo: textStyle) }) { self } } }

Then you can use it like this:

 struct Previews_ScaledFrame_Previews: PreviewProvider { static var previews: some View { let size = 30.0 let textStyle = Font.TextStyle.body let scaledSize = ScaledMetric(wrappedValue: size, relativeTo: textStyle) return VStack(alignment:.leading, spacing: 0) { HStack { // Use it directly like this ScaledFrame(width: scaledSize, height: scaledSize) { Image(systemName: "folder") } Text("Hello") } HStack { // Or use it via the method like this Image(systemName: "mappin").scaledFrame( width: size, height: size, relativeTo: textStyle) Text("World") } } } }

These are how it looks like when using the Dynamic Type:

xSmall

在此处输入图像描述

Large

在此处输入图像描述

AX5

在此处输入图像描述

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM