首页 > 解决方案 > 正确对齐 HStack 中的两个 SwiftUI 文本视图

问题描述

我有一个包含两行的简单列表视图。

每行包含两个文本视图。查看一和查看二。

我想对齐每行中的最后一个标签(查看两个),以便名称标签前导对齐并保持对齐,无论字体大小如何。

第一个标签(查看一个)也需要前导对齐。

我尝试在第一个标签(查看一个)上设置最小帧宽度,但它不起作用。设置最小宽度并让文本视图在视图一中对齐似乎也是不可能的。

有任何想法吗?这在 UIKit 中是相当直接的。

列表显示

标签: iosswiftui

解决方案


我找到了一种解决此问题的方法,该方法支持动态类型并且不是 hacky。答案是使用PreferenceKeysGeometryReader

这个解决方案的本质是每个数字Text都有一个宽度,根据它的文本大小绘制它。GeometryReader可以检测到这个宽度,然后我们可以将PreferenceKey其冒泡到 List 本身,其中可以跟踪最大宽度,然后分配给每个 numberText的帧宽度。

APreferenceKey是您使用关联类型创建的类型(可以是任何符合 的结构Equatable,这是您存储有关首选项的数据的位置),它附加到任何View类型,当它附加时,它会在视图树中冒泡,并且可以是使用 . 在祖先视图中收听.onPreferenceChange(PreferenceKeyType.self)

首先,我们将创建 PreferenceKey 类型及其包含的数据:

struct WidthPreferenceKey: PreferenceKey {
    typealias Value = [WidthPreference]
    
    static var defaultValue: [WidthPreference] = []
    
    static func reduce(value: inout [WidthPreference], nextValue: () -> [WidthPreference]) {
        value.append(contentsOf: nextValue())
    }
}

struct WidthPreference: Equatable {
    let width: CGFloat
}

接下来,我们将创建一个名为 View 的视图,该视图WidthPreferenceSettingView将附加到我们想要调整大小的任何背景(在本例中为数字标签)。这将负责设置首选项,该首选项将使用 PreferenceKeys 传递此数字标签的首选宽度。

struct WidthPreferenceSettingView: View {
    var body: some View {
        GeometryReader { geometry in
            Rectangle()
                .fill(Color.clear)
                .preference(
                    key: WidthPreferenceKey.self,
                    value: [WidthPreference(width: geometry.frame(in: CoordinateSpace.global).width)]
                )
        }
    }
}

最后,列表本身!我们有一个@State 变量,它是数字“列”的宽度(从数字不直接影响代码中其他数字的意义上说,这并不是真正的列)。通过.onPreferenceChange(WidthPreference.self)我们监听我们创建的首选项的变化并将最大宽度存储在我们的宽度状态中。在绘制完所有数字标签并由 GeometryReader 读取它们的宽度后,宽度会向上传播并且最大宽度由.frame(width: width)

struct ContentView: View {
    @State private var width: CGFloat? = nil
    
    var body: some View {
        List {
            HStack {
                Text("1. ")
                    .frame(width: width, alignment: .leading)
                    .lineLimit(1)
                    .background(WidthPreferenceSettingView())
                Text("John Smith")
            }
            HStack {
                Text("20. ")
                    .frame(width: width, alignment: .leading)
                    .lineLimit(1)
                    .background(WidthPreferenceSettingView())
                Text("Jane Done")
            }
            HStack {
                Text("2000. ")
                    .frame(width: width, alignment: .leading)
                    .lineLimit(1)
                    .background(WidthPreferenceSettingView())
                Text("Jax Dax")
            }
        }.onPreferenceChange(WidthPreferenceKey.self) { preferences in
            for p in preferences {
                let oldWidth = self.width ?? CGFloat.zero
                if p.width > oldWidth {
                    self.width = p.width
                }
            }
        }
    }
}

如果您有多列数据,一种扩展方法是对列进行枚举或对它们进行索引,并且宽度的@State 将成为一个字典,其中每个键都是一列并.onPreferenceChange与键值进行比较列的最大宽度。

为了显示结果,是打开较大文本后的样子,就像一个魅力:)。

这篇关于 PreferenceKey 和检查视图树的文章非常有帮助:https ://swiftui-lab.com/communicating-with-the-view-tree-part-1/


推荐阅读