我需要在
SwiftUI
中获取渲染视图的宽度,这显然不是那么容易。
我的看法是,我需要一个返回视图尺寸的函数,就这么简单。
var body: some View {
VStack(alignment: .leading) {
Text(timer.name)
.font(.largeTitle)
.fontWeight(.heavy)
Text(timer.time)
.font(.largeTitle)
.fontWeight(.heavy)
.opacity(0.5)
}
}
获取视图尺寸(由 SwiftUI 自动调整大小)的唯一可用机制是
GeometryReader
。
GeometryReader
是一个代理视图,它返回渲染视图的容器的尺寸。
struct SomeView: View {
@State var size: CGSize = .zero
var body: some View {
VStack {
Text("VStack width: \(size.width)")
Text("VStack height: \(size.height)")
GeometryReader { proxy in
HStack {} // just an empty container to triggers the onAppear
.onAppear {
size = proxy.size
}
}
}
}
}
印刷尺寸为
VStack
的尺寸。
现在我们知道
GeometryReader
为我们提供了容器的大小,通常的后续问题是:如何使用它来获取特定视图的大小?
为此,我们需要将几何读取器移动到目标视图下方一级。如何?我们可以添加一个空背景来获取目标视图的大小并将此信息发送回
Binding
。
让我们创建一个
SizeCalculator
ViewModifier
,以便我们可以在每个视图上使用此功能:
struct SizeCalculator: ViewModifier {
@Binding var size: CGSize
func body(content: Content) -> some View {
content
.background(
GeometryReader { proxy in
Color.clear // we just want the reader to get triggered, so let's use an empty color
.onAppear {
size = proxy.size
}
}
)
}
}
extension View {
func saveSize(in size: Binding<CGSize>) -> some View {
modifier(SizeCalculator(size: size))
}
}
SizeCalculator
的工作是添加一个GeometryReader
作为我们目标视图的背景。 On 出现,所以在 SwiftUI 渲染完 content
后,它会将尺寸发送回 Binding
。
用途:
struct SomeView: View {
@State var size: CGSize = .zero
var body: some View {
VStack {
Text("text width: \(size.width)")
Text("text height: \(size.height)")
Text("hello")
.saveSize(in: $size)
}
}
}
获取子视图的尺寸是任务的第一部分。将维度值冒泡是第二部分。
GeometryReader
获取父视图的暗淡,这可能不是您想要的。为了获得相关子视图的暗淡,我们可以在其子视图上调用一个修饰符,该修饰符具有实际大小,例如 .background()
或 .overlay()
struct GeometryGetterMod: ViewModifier {
@Binding var rect: CGRect
func body(content: Content) -> some View {
print(content)
return GeometryReader { (g) -> Color in // (g) -> Content in - is what it could be, but it doesn't work
DispatchQueue.main.async { // to avoid warning
self.rect = g.frame(in: .global)
}
return Color.clear // return content - doesn't work
}
}
}
struct ContentView: View {
@State private var rect1 = CGRect()
var body: some View {
let t = HStack {
// make two texts equal width, for example
// this is not a good way to achieve this, just for demo
Text("Long text").overlay(Color.clear.modifier(GeometryGetterMod(rect: $rect1)))
// You can then use rect in other places of your view:
Text("text").frame(width: rect1.width, height: rect1.height).background(Color.green)
Text("text").background(Color.yellow)
}
print(rect1)
return t
}
}
这是另一种获取当前视图大小并执行某些操作的便捷方法:
readSize
函数。
extension View {
func readSize(onChange: @escaping (CGSize) -> Void) -> some View {
background(
GeometryReader { geometryProxy in
Color.clear
.preference(key: SizePreferenceKey.self, value: geometryProxy.size)
}
)
.onPreferenceChange(SizePreferenceKey.self, perform: onChange)
}
}
private struct SizePreferenceKey: PreferenceKey {
static var defaultValue: CGSize = .zero
static func reduce(value: inout CGSize, nextValue: () -> CGSize) {}
}
用途:
struct ContentView: View {
@State private var commonSize = CGSize()
var body: some View {
VStack {
Text("Hello, world!")
.padding()
.border(.yellow, width: 1)
.readSize { textSize in
commonSize = textSize
}
Rectangle()
.foregroundColor(.yellow)
.frame(width: commonSize.width, height: commonSize.height)
}
}
}
有一种更简单的方法可以使用
GeometryReader
获取视图的宽度。您需要创建一个状态变量来存储宽度,然后用 GeometryReader
包围所需的视图,并将宽度值设置为该宽度内的几何图形。例如:
struct ContentView: View {
@State var width: CGFloat = 0.00 // this variable stores the width we want to get
var body: some View {
VStack(alignment: .leading) {
GeometryReader { geometry in
Text(timer.name)
.font(.largeTitle)
.fontWeight(.heavy)
.onAppear {
self.width = geometry.size.width
print("text width: \(width)") // test
}
} // in this case, we are reading the width of text
Text(timer.time)
.font(.largeTitle)
.fontWeight(.heavy)
.opacity(0.5)
}
}
}
请注意,如果目标的视图也发生变化,宽度也会发生变化。如果你想存储它,我建议在其他地方使用 let 常量。希望有帮助!
参考 Giuseppe Sapienza 的答案,通过向 SizeCalculator 添加 .onChange 修饰符
.onChange(of: proxy.size, { oldValue, newValue in
size = proxy.size
})
ViewModifier 将动态响应正在测量的视图大小的变化。