下图所示是一个常规的登录界面,只需少量代码即可实现:

1 | struct ContentView : View { |
但是通常我们为了美观,会将左边的文字列等宽对齐,也许固定宽度是个不错的想法,但是可扩展性太差,我们如何解决这个问题呢?
常规的思路就是,获取文字列所有的内容的宽度,取最大值,重绘界面即可。那么问题来了,如何获取这个最大值呢?答案就是 PreferenceKey
,它可以收集视图树中子视图的数据,回传给父视图(跨层级亦可)。这里我们需要获取尺寸,还用到了 GeometryReader。
改造后的效果和代码如下:

1 | struct ContentView : View { |
有一点需要注意,为什么我们要使用 TextBackgroundView
来作为背景回传所需要的值呢?因为我们期望 Form 列表的布局是根据子视图的布局来更新的,而子视图又依赖父视图传入的宽度值,这样形成了一个得不到结果的死循环。而 TextBackgroundView
可以打破这个僵局,父视图所依赖的布局不再是文字的布局,而是背景层的视图布局。
补充说明一下,SwiftUI 的视图层级是不同于 UIKit 的,在 UIKit 中,背景是控件的属性,而 SwiftUI 中,.background
会在视图树中生成一个新的视图,是独立与所修饰的控件的。
另外有一点令我不解的是,既然我是要获取最大宽度,只需要在 TextWidthPreferenceKey
将关联类型设置为 CGFloat
即可,在 reduce
方法中写入 value = max(value, nextValue())
,然后在 onPreferenceChange
中将最大值传给 textWidth
,这样不是更简单吗?但是事与愿违,这样达不到我们想要的效果,观察控制台,我发现确实可以获取到最大宽度值,但是不会更新视图布局,百思不得其解,网上也没找到合理的解释。暂且放下,以后再研究一下这个问题。
参考: