在列表行中制作等宽的 SwiftUI 视图 [英] Make Equal-Width SwiftUI Views in List Rows

查看:25
本文介绍了在列表行中制作等宽的 SwiftUI 视图的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个 List 显示当前月份的天数.List 中的每一行都包含缩写的日期、一个 DividerVStack 中的日期编号.然后将 VStack 嵌入到 HStack 中,以便我可以在日期和数字的右侧添加更多文本.

I have a List that displays days in the current month. Each row in the List contains the abbreviated day, a Divider, and the day number within a VStack. The VStack is then embedded in an HStack so that I can have more text to the right of the day and number.

struct DayListItem : View {

    // MARK: - Properties

    let date: Date

    private let weekdayFormatter: DateFormatter = {
        let formatter = DateFormatter()
        formatter.dateFormat = "EEE"
        return formatter
    }()

    private let dayNumberFormatter: DateFormatter = {
        let formatter = DateFormatter()
        formatter.dateFormat = "d"
        return formatter
    }()

    var body: some View {
        HStack {
            VStack(alignment: .center) {
                Text(weekdayFormatter.string(from: date))
                    .font(.caption)
                    .foregroundColor(.secondary)
                Text(dayNumberFormatter.string(from: date))
                    .font(.body)
                    .foregroundColor(.red)
            }
            Divider()
        }
    }

}

DayListItem 的实例用于 ContentView:

struct ContentView : View {

    // MARK: - Properties

    private let dataProvider = CalendricalDataProvider()

    private var navigationBarTitle: String {
        let formatter = DateFormatter()
        formatter.dateFormat = "MMMM YYY"
        return formatter.string(from: Date())
    } 

    private var currentMonth: Month {
        dataProvider.currentMonth
    }

    private var months: [Month] {
        return dataProvider.monthsInRelativeYear
    }

    var body: some View {
        NavigationView {
            List(currentMonth.days.identified(by: \.self)) { date in
                DayListItem(date: date)
            }
                .navigationBarTitle(Text(navigationBarTitle))
                .listStyle(.grouped)
        }
    }

}

代码结果如下:

这可能不是很明显,但分隔线没有对齐,因为文本的宽度可能因行而异.我想要实现的是让包含日期信息的视图具有相同的宽度,以便它们在视觉上对齐.

It may not be obvious, but the dividers are not lined up because the width of the text can vary from row to row. What I would like to achieve is to have the views that contains the day information be the same width so that they are visually aligned.

我曾尝试使用 GeometryReaderframe() 修饰符来设置最小宽度、理想宽度和最大宽度,但我需要确保文本可以通过动态类型设置缩小和增长;我选择不使用占父级百分比的宽度,因为我不确定如何确保本地化文本始终适合允许的宽度.

I have tried using a GeometryReader and the frame() modifiers to set the minimum width, ideal width, and maximum width, but I need to ensure that the text can shrink and grow with Dynamic Type settings; I chose not to use a width that is a percentage of the parent because I was uncertain how to be sure that localized text would always fit within the allowed width.

如何修改我的视图,使行中的每个视图都具有相同的宽度,而不管文本的宽度如何?

How can I modify my views so that each view in the row is the same width, regardless of the width of text?

关于动态类型,我将创建一个不同的布局,以便在更改该设置时使用.

Regarding Dynamic Type, I will create a different layout to be used when that setting is changed.

推荐答案

我使用 GeometryReaderPreferences 让它工作.

I got this to work using GeometryReader and Preferences.

首先,在ContentView中,添加这个属性:

First, in ContentView, add this property:

@State var maxLabelWidth: CGFloat = .zero

然后,在DayListItem中,添加这个属性:

Then, in DayListItem, add this property:

@Binding var maxLabelWidth: CGFloat

接下来,在 ContentView 中,将 self.$maxLabelWidth 传递给 DayListItem 的每个实例:

Next, in ContentView, pass self.$maxLabelWidth to each instance of DayListItem:

List(currentMonth.days.identified(by: \.self)) { date in
    DayListItem(date: date, maxLabelWidth: self.$maxLabelWidth)
}

现在,创建一个名为 MaxWidthPreferenceKey 的结构:

Now, create a struct called MaxWidthPreferenceKey:

struct MaxWidthPreferenceKey: PreferenceKey {
    static var defaultValue: CGFloat = .zero
    
    static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
        let nextValue = nextValue()
        
        guard nextValue > value else { return }
        
        value = nextValue
    }
}

这符合 PreferenceKey 协议,允许您在视图之间通信首选项时将此结构用作密钥.

This conforms to the PreferenceKey protocol, allowing you to use this struct as a key when communicating preferences between your views.

接下来,创建一个名为 DayListItemGeometryView - 这将用于确定 DayListItemVStack 的宽度代码>:

Next, create a View called DayListItemGeometry - this will be used to determine the width of the VStack in DayListItem:

struct DayListItemGeometry: View {
    var body: some View {
        GeometryReader { geometry in
            Color.clear
                .preference(key: MaxWidthPreferenceKey.self, value: geometry.size.width)
        }
        .scaledToFill()
    }
}

然后,在 DayListItem 中,将您的代码更改为:

Then, in DayListItem, change your code to this:

HStack {
    VStack(alignment: .center) {
        Text(weekdayFormatter.string(from: date))
            .font(.caption)
            .foregroundColor(.secondary)
        Text(dayNumberFormatter.string(from: date))
            .font(.body)
            .foregroundColor(.red)
    }
    .background(DayListItemGeometry())
    .onPreferenceChange(MaxWidthPreferenceKey.self) {
        self.maxLabelWidth = $0
    }
    .frame(width: self.maxLabelWidth)

    Divider()
}

我所做的是创建了一个 GeometryReader 并将其应用到 VStack 的背景.几何图形告诉我 VStack 的尺寸,它根据文本的大小调整自身的大小.MaxWidthPreferenceKey 每当几何变化时都会更新,并且在 MaxWidthPreferenceKey 中的 reduce 函数计算出最大宽度后,我读取了偏好变化并更新了 self.maxLabelWidth.然后我将 VStack 的框架设置为 .frame(width: self.maxLabelWidth),并且由于 maxLabelWidth 是绑定的,每个 DayListItem 在计算新的 maxLabelWidth 时更新.请记住,顺序在这里很重要.将 .frame 修饰符放在 .background.onPreferenceChange 之前将不起作用.

What I've done is I've created a GeometryReader and applied it to the background of the VStack. The geometry tells me the dimensions of the VStack which sizes itself according to the size of the text. MaxWidthPreferenceKey gets updated whenever the geometry changes, and after the reduce function inside MaxWidthPreferenceKey calculates the maximum width, I read the preference change and update self.maxLabelWidth. I then set the frame of the VStack to be .frame(width: self.maxLabelWidth), and since maxLabelWidth is binding, every DayListItem is updated when a new maxLabelWidth is calculated. Keep in mind that the order matters here. Placing the .frame modifier before .background and .onPreferenceChange will not work.

这篇关于在列表行中制作等宽的 SwiftUI 视图的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆