本文为
Eul
样章,如果您喜欢,请移步
AppStore/Eul
查看更多内容。
Eul
是一款 SwiftUI 教程类 App(iOS、macOS),以文章(文字、图片、代码)配合真机示例(Xcode 12+、iOS 14+,macOS 11+)的形式呈现给读者。笔者意在尽可能使用简洁明了的语言阐述 SwiftUI 相关的知识,使读者能快速掌握并在 iOS 开发中实践。
GeometryReader
GeometryReader
是一个通过闭包来构建视图的容器,可以返回一个
GeometryProxy
类型的结构体,它包含如下属性和方法,由此我们可以获取当前视图容器(即父视图)的尺寸和位置,绘制以其为参考坐标系的视图。
var safeAreaInsets: EdgeInsets
var size: CGSize
func frame (in : CoordinateSpace ) -> CGRect
比如,我们需要绘制一个长宽均为父视图一半的矩形:
struct ContentView : View {
var body: some View {
GeometryReader { gr in
RoundedRectangle (cornerRadius: 10 )
.fill(Color .blue)
.frame(width: gr.size.width * 0.5 , height: gr.size.height * 0.5 )
.position(x: gr.frame(in: .local).midX, y: gr.frame(in: .local).midY)
我们再来看看 GeometryProxy
包含的实例方法:func frame(in: CoordinateSpace) -> CGRect
,这里的 CoordinateSpace
是个枚举类型,有以下几种情况:
case global
case local
case named(AnyHashable )
通过这个方法,我们可以获取到当前视图在不同参考系中的位置和尺寸,我们将代码改成如下:
struct ContentView : View {
var body: some View {
VStack (spacing: 10 ) {
text("Top" , width: 100 , height: 50 )
HStack (spacing: 10 ) {
text("Left" , width: 50 , height: 100 )
roundRect
.background(Color .black)
text("Right" , width: 50 , height: 100 )
text("Bottom" , width: 100 , height: 50 )
.coordinateSpace(name: "VStack" )
var roundRect: some View {
GeometryReader { gr in
RoundedRectangle (cornerRadius: 10 )
.fill(Color .blue)
.frame(width: gr.size.width * 0.5 , height: gr.size.height * 0.5 )
.position(x: gr.frame(in: .local).midX, y: gr.frame(in: .local).midY)
.onTapGesture {
print ("screen: \(UIScreen.main.bounds) " )
print ("global: \(gr.frame(in: .global)) " )
print ("local: \(gr.frame(in: .local)) " )
print ("custom: \(gr.frame(in: .named("VStack" ))) " )
func text (_ text : String , width : CGFloat , height : CGFloat ) -> some View {
Text (text)
.frame(width: width, height: height)
.background(Color .orange)
.cornerRadius(10 )
运行模拟器 iPhone 12 Pro(safeAreaInsets: 47.0, 0.0, 34.0, 0.0),点击蓝色区域,控制台打印如下结果:
screen: (0.0, 0.0, 375.0, 812.0)
global: (60.0, 148.0, 255.0, 570.0)
local: (0.0, 0.0, 255.0, 570.0)
custom: (60.0, 60.0, 255.0, 570.0)
这与我们之前所说的枚举类型对应的坐标参考系是一致的。
PreferenceKey
还记得我们在前面的“自定义对齐方式”中讲过的,如何对齐手机和电子邮箱的例子吗?其实,我们还有另外一种思路来实现类似的效果,那就是获取文字列所有的内容的宽度,取最大值,重绘界面即可。那么问题来了,如何获取这个最大值呢?答案就是 PreferenceKey
,它可以收集视图树中子视图的数据,回传给父视图(跨层级亦可)。这里我们需要获取尺寸,还用到了 GeometryReader。
struct ContentView : View {
@State private var email = ""
@State private var password = ""
@State private var textWidth: CGFloat ?
var body: some View {
Form {
HStack {
Text ("电子邮箱" )
.frame(width: textWidth, alignment: .leading)
.background(TextBackgroundView ())
TextField ("请输入" , text: $email )
.textFieldStyle(RoundedBorderTextFieldStyle ())
HStack {
Text ("密码" )
.frame(width: textWidth, alignment: .leading)
.background(TextBackgroundView ())
TextField ("请输入" , text: $email )
.textFieldStyle(RoundedBorderTextFieldStyle ())
.onPreferenceChange(TextWidthPreferenceKey .self ) { (value) in
print (value)
textWidth = value.max()
struct TextBackgroundView : View {
var body: some View {
GeometryReader { gr in
Rectangle ()
.fill(Color .clear)
.preference(key: TextWidthPreferenceKey .self ,
value: [gr.size.width])
struct TextWidthPreferenceKey : PreferenceKey {
static var defaultValue: [CGFloat ] = []
static func reduce (value : inout [CGFloat ], nextValue : () -> [CGFloat ]) {
value.append(contentsOf: nextValue())
有一点需要注意,为什么我们要使用 TextBackgroundView
来作为背景回传所需要的值呢?因为我们期望 Form 列表的布局是根据子视图的布局来更新的,而子视图又依赖父视图传入的宽度值,这样形成了一个得不到结果的死循环。而 TextBackgroundView
可以打破这个僵局,父视图所依赖的布局不再是文字的布局,而是背景层的视图布局。
补充说明一下,SwiftUI 的视图层级是不同于 UIKit 的,在 UIKit 中,背景是控件的属性,而 SwiftUI 中,.background
会在视图树中生成一个新的视图,是独立与所修饰的控件的。
另外有一点令笔者不解的是,既然我们是要获取最大宽度,只需要在 TextWidthPreferenceKey
将关联类型设置为 CGFloat
即可,在 reduce
方法中写入 value = max(value, nextValue())
,然后在 onPreferenceChange
中将最大值传给 textWidth
,这样不是更简单吗?但是事与愿违,这样达不到我们想要的效果,观察控制台,笔者发现确实可以获取到最大宽度值,但是不会更新视图布局,百思不得其解,网上也没找到合理的解释。如果有读者明白其中的奥妙,请不吝赐教,笔者先在此谢过。
本文为 Eul 样章,如果您喜欢,请移步 AppStore/Eul 查看更多内容。
4848
Bowen_Jin
Swift
SwiftUI
2839
健了个平_24
SwiftUI
Swift
1206
会飞的金鱼
ReactiveCocoa
RxSwift
SwiftUI
4529
会飞的金鱼
RxSwift
SwiftUI
Alamofire
6014
Bowen_Jin
Flutter
SwiftUI
2382
会飞的金鱼
RxSwift
SwiftUI
WebRTC
2011
会飞的金鱼
SwiftUI
RxSwift
UI Kit
2256
zzzwco
Swift
SwiftUI
1244
zzzwco
Cooker @ HomeStation