SwiftUI:将 Circle "fit" 设为 Text 元素

SwiftUI: Make Circle "fit" the Text element

我有以下代码:


import SwiftUI

struct EntryHeaderIconView: View {
    private let backgroundSize: Double = 88
    private let color: Color
    private let initials: String

    init(color: Color,
         initials: String = "") {
        self.color = color
        self.initials = initials
    }

    var body: some View {
        ZStack(alignment: .center) {
            Circle()
//                .frame(width: backgroundSize,
//                       height: backgroundSize)
            // Uncommenting this fixes the issue, but the text now clips
                .foregroundColor(color)
            icon
        }
    }


    @ViewBuilder
    private var icon: some View {
        Text(verbatim: initials)
            .font(.system(size: 48, weight: .bold, design: .rounded))
            .foregroundColor(.white)
            .accessibilityIdentifier("entry_header_initials")
    }
}

struct ContentView: View {
    var body: some View {
        VStack {
            EntryHeaderIconView(color: .red,
                                initials: "RT")

            EntryHeaderIconView(color: .green,
                                initials: "LV")

            EntryHeaderIconView(color: .red,
                                initials: "中国")
            Spacer()
        }
    }
}

我的目标是让 Circle 元素完全适合 Text 元素。然而,它要么在 VStack 中继续增长,以便它占据尽可能多的 space 并且看起来像这样(尺寸线被注释掉):

或者,如果我将固定大小设置为 Circle,内容将被剪裁:

    var body: some View {
        ZStack(alignment: .center) {
            Circle()
                .frame(width: backgroundSize,
                       height: backgroundSize)
            // Uncommenting this fixes the issue, but the text now clips
                .foregroundColor(color)
            icon
        }
    }

我的目标是使 Circle 大小依赖于 Text 大小,以便它一起增长(或缩小)。

使用 AutoLayout 很容易实现,如何使用 SwiftUI 实现同样的效果?

A ZStack 占据其子视图所需的 space 。您没有为 Circle 提供明确的 frame,那么 SwiftUI 怎么知道 Circle 的大小应该与 icon 的大小匹配?

相反,在将一些 padding 应用到 icon 之后,您应该将 Circle 作为 background 添加到您的 icon

struct EntryHeaderIconView: View {
    private let color: Color
    private let initials: String

    init(color: Color,
         initials: String = "") {
        self.color = color
        self.initials = initials
    }

    var body: some View {
        icon
            .padding(.all, 30)
            .background(background)
    }

    private var background: some View {
        Circle()
            .foregroundColor(color)
    }

    private var icon: some View {
        Text(verbatim: initials)
            .font(.system(size: 48, weight: .bold, design: .rounded))
            .foregroundColor(.white)
            .accessibilityIdentifier("entry_header_initials")
    }
}

struct EntryHeaderIconView_Preview: PreviewProvider {
    static var previews: some View {
        VStack {
            EntryHeaderIconView(color: .red,
                                initials: "RT")

            EntryHeaderIconView(color: .green,
                                initials: "LV")

            EntryHeaderIconView(color: .red,
                                initials: "中国")
        }
    }
}

您需要对文本应用填充,然后以圆形显示背景。

以下是实现该目标的方法:

struct Example: View {
    
    var body: some View {
            VStack {
                Text("1")
                    .font(.footnote)
                    .foregroundColor(.white)
                    .padding(5)
                    .background(.red)
                    .clipShape(Circle())
                Text("2")
                    .font(.headline)
                    .foregroundColor(.white)
                    .padding(5)
                    .background(.red)
                    .clipShape(Circle())
                Text("3")
                    .font(.largeTitle)
                    .foregroundColor(.white)
                    .padding(5)
                    .background(.red)
                    .clipShape(Circle())
            }
        
    }
}

我要 post 这是因为它与其他两个答案不同,后者更简单并且有效。这在处理不同尺寸时更加灵活,但仍然使它们看起来一致。这使用 PreferenceKey 来读取首字母的大小,并将圆限制为一定大小。

struct EntryHeaderIconView: View {
    @State var textViewSize = CGFloat.zero
    // This gives a consistent additional size of the circle around the intitials
    let circleSizeMultiplier = 1.5
    // To give it a minimum size, just have the size introduced here
    let minimumSize: CGfloat

    var backgroundSize: CGFloat {
        min(textViewSize * circleSizeMultiplier, minimumSize)
    }

    private let color: Color
    private let initials: String

    init(color: Color,
         initials: String = "") {
        self.color = color
        self.initials = initials
    }

    var body: some View {
        ZStack(alignment: .center) {
            Circle()
                // The frame is sized at the circleSizeMultiplier times the
                // largest dimension of the initials.
                .frame(width: backgroundSize
                       height: backgroundSize)
                .foregroundColor(color)
            icon
                // This reads the size of the initials
                .background(GeometryReader { geometry in
                    Color.clear.preference(
                        key: SizePreferenceKey.self,
                        value: geometry.size
                    )
                })
        }
        // this sets backgroundSize to be the max value of the width or height
        .onPreferenceChange(SizePreferenceKey.self) {
            textViewSize = max([=10=].width, [=10=].height)
        }
    }


    @ViewBuilder
    private var icon: some View {
        Text(verbatim: initials)
            .font(.system(size: 48, weight: .bold, design: .rounded))
            .foregroundColor(.white)
            .accessibilityIdentifier("entry_header_initials")
    }
}
// This is the actual preferenceKey that makes it work.
fileprivate struct SizePreferenceKey: PreferenceKey {
    static var defaultValue: CGSize = .zero
    static func reduce(value: inout CGSize, nextValue: () -> CGSize) {}
}

编辑:

除非图标是 SF 符号,否则必须以不同方式处理。但是,我更新了代码以添加 minimumSize 常量并将 backgroundSize 更改为计算变量。此视图未设置为处理图像,但您只需要确定要如何约束图像,或执行类似 .

的操作