我们如何使用GeometryReader在SwiftUI中获取和读取文本大小?

9
我正在尝试根据文本字体的大小读取文本的宽度。我们知道GeometryReader会占用所有可能给定的空间,但在这些代码中,它只占用了我传递的给定框架大小,而没有考虑我的文本尺寸!我做错了什么?我希望GeometryReader只读取我的文本大小,而不是自身的框架宽度。

enter image description here

这是我的代码:

struct ContentView: View {
    @State var fontSize: CGFloat = 20.0

    var body: some View {
        Spacer()

        textWidthGeometryReader(fontSize: $fontSize)

        Spacer()

        Text("Font size:" + "\(fontSize)")

        Slider(value: $fontSize, in: 20...40, step: 1)
            .padding()

        Spacer()
    }
}

struct textWidthGeometryReader: View {
    @Binding var fontSize: CGFloat

    var body: some View {
        GeometryReader { inSideGeometry in

            Text("width of Text:" + String(format: "%.0f", inSideGeometry.size.width))
                .font(.system(size: fontSize))
                .background(Color.yellow)
                .position(x: inSideGeometry.size.width / 2, y: inSideGeometry.size.height / 2)
        }
        .frame(width: 400, height: 300, alignment: .center)
        .background(Color.gray)
        .cornerRadius(20)
    }
}

https://dev59.com/DlMI5IYBdhLWcg3wS5gv#69059069 - lorem ipsum
4个回答

16

您可以使用视图首选项。

  1. 首先为视图尺寸创建自定义PreferenceKey
struct ViewSizeKey: PreferenceKey {
    static var defaultValue: CGSize = .zero

    static func reduce(value: inout CGSize, nextValue: () -> CGSize) {
        value = nextValue()
    }
}
  1. 创建一个视图(view),该视图将计算其大小并将其分配给ViewSizeKey:
struct ViewGeometry: View {
    var body: some View {
        GeometryReader { geometry in
            Color.clear
                .preference(key: ViewSizeKey.self, value: geometry.size)
        }
    }
}
  1. 在视图中使用它们:
struct ContentView: View {
    @State var fontSize: CGFloat = 20.0
    @State var textSize: CGSize = .zero

    var body: some View {
        Spacer()
        Text("width of Text:" + String(format: "%.0f", textSize.width))
            .font(.system(size: fontSize))
            .background(ViewGeometry())
            .onPreferenceChange(ViewSizeKey.self) {
                textSize = $0
            }
        Spacer()
        Text("Font size:" + "\(fontSize)")
        Slider(value: $fontSize, in: 20...40, step: 1)
            .padding()
        Spacer()
    }
}

视图首选项是一个相当高级的主题。您可以在此处找到更详细的解释:


@Omid 1) GeometryReader的所有点是什么? - 用于读取视图的大小。但是,GeometryReader会消耗所有可用空间 - 这就是为什么它需要放置在后台(与Text具有相同的大小)。2)我的意思是Text肯定知道它的宽度 - 不,它不知道。SwiftUI并非UIKit。 - pawello2222
我一般了解GeometryReader的使用方法。当我说“GeometryReader的所有点是什么?”时,其实是在提出一个哲学性的问题:它为什么需要存在?你说Text不知道自己的长度!我认为Text是一个视图,每个视图都有自己特殊的大小以及在内存中的特殊ID,如果作为视图的Text不知道自己的大小,那么谁知道呢? - ios coder
@Omid 如果 Text 作为视图不知道自己的大小,那么谁知道呢? - GeometryReader。我理解你的意思,只是 SwiftUI 并不是这样设计的。在大多数情况下,您不需要获取 Text 的宽度。您可以让 SwiftUI 为您布局视图。 - pawello2222
我正在尝试找到有关文本宽度的易懂信息,不考虑GeometryReader。当我们构建一个新的SwiftUI项目时,我们得到什么?我们得到“Hello World!”文本。为了呈现和显示我们的应用程序或Xcode需要回答一些严肃的问题,比如:1-字符串,2-背景,3-前景颜色等等。现在我来谈谈你所说的“SwiftUI不是UIKit”,我知道这一点,SwiftUI就像一位专业的酒吧调酒师,你只需点白俄罗斯鸡尾酒,而在UIKit中,你必须准确地说明要混合多少伏特加或百利甜酒,这样我们才能向SwiftUI询问饮料的详细信息!为什么不呢? - ios coder
为什么我要去点另一个白俄罗斯来获取有关GeometryReader中饮料信息的信息?我可以问那个专业的酒吧侍者我的问题!因为他已经做过那个饮料(文本),他知道一切。 - ios coder
显示剩余4条评论

6

另一种方法是根本不进行渲染(这将允许您从无状态的代码(如扩展程序)中进行计算):

extension String
{
   func sizeUsingFont(usingFont font: UIFont) -> CGSize
    {
        let fontAttributes = [NSAttributedString.Key.font: font]
        return self.size(withAttributes: fontAttributes)
    }
}

然后像这样使用:

// calculate render width and height of text using provided font (without actually rendering)
let sizeOfText: CGSize = "test string".sizeUsingFont(usingFont: UIFont.systemFont(ofSize: 40, weight: UIFont.Weight.bold))

我使用的源码是:如何在SwiftUI中动态确定字符串的宽度

如果你还决定使用GeometryReader,请记住可以将GeometryReader放在.background()内,并在适当的on-appear或on-whatever事件中保存到状态变量中:

import Foundation
import SwiftUI

struct TestView: View
{
    @State var sizeOfText: CGSize = CGSize(width: 0, height: 0)
    
    var body: some View
    {
        Text("Hello again")
            .font(Font.system(size:60, design: Font.Design.rounded))
            .background(GeometryReader { (geometryProxy : GeometryProxy) in
                HStack {}
                .onAppear {
                    sizeOfText = geometryProxy.size
                    print("sizeOfText: \(sizeOfText)")
                }
            })
            .offset(x: sizeOfText.width, y: sizeOfText.height)
    }
}

3

在使用GeometryReader后,我发现了一种更简单的方法来获取任何视图,包括文本的大小。我只是想回答我的问题,试试我的代码,或者重构它,如果你能让它更小,我会很高兴看到你的方式。这是我做的:

struct ContentView: View {
    @State var sizeOfText: CGSize = .zero
    @State var fontSizeOfText: CGFloat = 20.0

    var body: some View {
        Text("Size of Text: " + String(format: "%.0f", sizeOfText.width) + "⭐︎" + String(format: "%.0f", sizeOfText.height))
            .font(.system(size: fontSizeOfText))
            .background(Color.yellow)
            .background(sizeOfView(fontSizeOfText: $fontSizeOfText, sizeOfText: $sizeOfText))

        Spacer()
        Text("Font size:" + "\(fontSizeOfText)")
        Slider(value: $fontSizeOfText, in: 20...40, step: 1)
            .padding()
        Spacer()
    }
}

struct sizeOfView: View {
    @Binding var fontSizeOfText: CGFloat
    @Binding var sizeOfText: CGSize

    var body: some View {
        GeometryReader { proxy in

            HStack {}
                .onAppear { sizeOfText = proxy.size }
                .onChange(of: fontSizeOfText) { _ in sizeOfText = proxy.size }
        }
    }
}

是的,这也可以使用,但优先选择偏好设置方法更加通用。正如你所看到的,必须使用GeometryReader :) 顺便说一下,你可以结合onAppearonChange - 参见如何在SwiftUI视图中结合onChange和onAppear事件? - pawello2222
谢谢,我一开始想用.onReceive来实现,但是没有成功!你有什么想法吗? - ios coder
你可以使用 onReceive(Just(fontSizeOfText)) { ... },并且需要导入 Combine - pawello2222
你可以先 import Combine,然后在你的代码中使用 .onReceive(Just(fontSizeOfText)) { _ in sizeOfText = proxy.size }。但如果你仍然有问题,请创建一个新的问题来解决。 - pawello2222
@pawello2222:请查看我的新回答。 - ios coder

0
我找到了另一种解决这个问题的方法,这次你也可以使用.onReceive来处理,看看我的代码并改进它,这种方式是版本3.0.0!哈哈
代码:
class TextModel: ObservableObject
{
    @Published var sizeOfFont: CGFloat = 20.0
    @Published var sizeOfText: CGSize = .zero
}




struct ContentView: View
{
    
    @StateObject var textModel = TextModel()
    
    var body: some View
    {
        
        Spacer()
        
        Text("Size of Text: " + String(format: "%.0f", textModel.sizeOfText.width) + "⭐︎" +  String(format: "%.0f", textModel.sizeOfText.height))
            .font(.system(size: textModel.sizeOfFont))
            .background(Color.yellow)
            .background( sizeOfViewOptionA(textModel: textModel) )
        
        Spacer()
        
        Text("Size of Text: " + String(format: "%.0f", textModel.sizeOfText.width) + "⭐︎" +  String(format: "%.0f", textModel.sizeOfText.height))
            .font(.system(size: textModel.sizeOfFont))
            .background(Color.red)
            .background( sizeOfViewOptionB(textModel: textModel) )
  
        
        Spacer()
        
        Text("Font size:" + "\(textModel.sizeOfFont)")
        
        Slider(value: $textModel.sizeOfFont, in: 20...40, step: 1)
            .padding()
        
        Spacer()
  
    }
  
}



struct sizeOfViewOptionA: View
{
    
    @ObservedObject var textModel: TextModel

    var body: some View {
        
        GeometryReader { proxy in
            
            HStack{}
                .onAppear() {textModel.sizeOfText = proxy.size}
                .onChange(of: textModel.sizeOfFont) { _ in textModel.sizeOfText = proxy.size}
        }
 
    }
}


struct sizeOfViewOptionB: View
{
    
    @ObservedObject var textModel: TextModel

    var body: some View {
        
        GeometryReader { proxy in
            
            HStack{ Color.clear }
                .onReceive(textModel.$sizeOfFont) { _ in textModel.sizeOfText = proxy.size}
        }
 
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接