swift 如何使多列列表中的一列具有相同的宽度,而不使用宽度的框架修饰符,以保持灵活性

nhhxz33t  于 2022-12-17  发布在  Swift
关注(0)|答案(3)|浏览(94)

我有一个条目列表,其中包含多个UI列,除第一列外,所有列都可以 * 水平 * 设置为唯一大小(即,它们的长度/长度与其内容要求一样)。我知道,对于第一个大小一致的列,我可以设置一个框架修饰符宽度来实现这一点,但我希望有一种更好、更灵活的方式来获得所需的行为。原因是我不相信解决方案是最优化的,以考虑用户的的显示大小,也不是列的实际最大内容宽度。也就是说,当显示大小设置为最大值时,设置的宽度不够宽,或者,如果设置为最大值,则在较小/常规显示大小上,设置的宽度将不必要地宽。
这是我目前最好的尝试:

GeometryReader { geometry in
            
        VStack {
        
            HStack {
                HStack {
                    Text("9am")
                    
                    Image(systemName: "cloud.drizzle").font(Font.title2)
                        .offset(y: 4)
                }.padding(.all)
                .background(Color.blue.opacity(0.2))
                .cornerRadius(16)
                            
                VStack {
                    HStack {
                        Text("Summary")
                            .padding(.trailing, 4)
                            .background(Color.white)
                            .layoutPriority(1)
                        VStack {
                            Spacer()
                            Divider()
                            Spacer()
                        }
                        VStack {
                            Text("12°")
                            Text("25%")
                                .foregroundColor(Color.black)
                                .background(Color.white)
                        }.offset(y: -6)
                        Spacer()
                    }.frame(width: geometry.size.width/1.5)
                }
                
                Spacer()
            }
            
            HStack {
                HStack {
                    Text("10am")
                        .customFont(.subheadline)
                    
                    Image(systemName: "cloud.drizzle").font(Font.title2)
                        .offset(y: 4)
                        .opacity(0)
                }
                .padding(.horizontal)
                .padding(.vertical,4)
                .background(Color.blue.opacity(0.2))
                .cornerRadius(16)
                            
                VStack {
                    HStack {
                        ZStack {
                            Text("Mostly cloudy")
                                .customFont(.body)
                                .padding(.trailing, 4)
                                .background(Color.white)
                                .opacity(0)
                            VStack {
                                Spacer()
                                Divider()
                                Spacer()
                            }
                        }
                        VStack {
                            Text("13°")
                            Text("25%")
                                .foregroundColor(Color.black)
                                .background(Color.white)
                        }.offset(y: -6)
                        Spacer()
                    }.frame(width: geometry.size.width/1.75)
                }
                
                Spacer()
            
            } 
      } 
}

对我来说,这看起来像:

正如你所看到的,上午10点比上午9点稍微宽一些。为了让它们尽可能的接近,我还在其中加入了一个云图标,尽管不透明度为零。理想情况下,上午10点的大小应该和上午9点一样,而不需要透明的云图标。更一般地说,有意义的是识别该列中最宽的HStack,然后无论其宽度是多少都将应用于所有其他列。我上面的代码是静态的用于演示目的。2它将是一个视图,通过一个行集合迭代呈现。

mlmc2os5

mlmc2os51#

您可以使用动态帧修改器,如帧(.maxWidth:.infinity)修饰符来扩展视图,使其填充整个帧,即使帧是动态的。下面是一个示例,可以帮助您开始:

struct CustomContent: View {
    
    var body: some View {
        VStack {
            VStack {
                CustomRow(timeText: "9am", systemIcon: "cloud.drizzle", centerText: "Summary", temperature: "12°", percent: "25%")
                CustomRow(timeText: "10am", systemIcon: nil, centerText: nil, temperature: "13°", percent: "25%")
            }
            
            VStack {
                CustomRow(timeText: "9am", systemIcon: "cloud.drizzle", centerText: "Summary", temperature: "12°", percent: "25%")
                CustomRow(timeText: "10am", systemIcon: nil, centerText: nil, temperature: "13°", percent: "25%")
            }
            .frame(width: 300)
            
        }
    }
}

struct CustomContent_Previews: PreviewProvider {
    static var previews: some View {
        CustomContent()
    }
}

struct CustomRow: View {
    
    let timeText: String
    let systemIcon: String?
    let centerText: String?
    let temperature: String
    let percent: String
    
    var body: some View {
        HStack {
            
            //Left column
            HStack(alignment: .center) {
                Text(timeText)
                
                if let icon = systemIcon {
                    Image(systemName: icon)
                        .font(.title2)
                }
            }
            .padding(.all)
            .frame(width: 105, height: 60)
            .background(Color.blue.opacity(0.2))
            .cornerRadius(16)

            // Center column
            ZStack(alignment: .leading) {
                Capsule()
                    .fill(Color.black.opacity(0.3))
                    .frame(height: 0.5)
                if let text = centerText {
                    Text(text)
                        .lineLimit(1)
                        .background(Color.white)
                }
            }
            
            // Right column
            VStack {
                Text(temperature)
                Text(percent)
                    .foregroundColor(Color.black)
            }
            
        }
    }
}
nwlls2ji

nwlls2ji2#

https://www.wooji-juice.com/blog/stupid-swiftui-tricks-equal-sizes.html的指导下,我完成了这一点。
这是我希望确保所有行的UI水平大小相等,宽度设置为最大值的UI:

HStack {
                VStack {
                    Spacer()
                    Text("9am")
                    Spacer()
                }
            }.frame(minWidth: self.maximumSubViewWidth)
            .overlay(DetermineWidth())

包含上述内容的堆栈具有OnPreferenceChange修改器:

.onPreferenceChange(DetermineWidth.Key.self) {
                if $0 > maximumSubViewWidth {
                    maximumSubViewWidth = $0
                }
            }

奇迹就发生在这里:

struct MaximumWidthPreferenceKey: PreferenceKey
{
    static var defaultValue: CGFloat = 0
    static func reduce(value: inout CGFloat, nextValue: () -> CGFloat)
    {
        value = max(value, nextValue())
    }
}

struct DetermineWidth: View
{
    typealias Key = MaximumWidthPreferenceKey
    var body: some View
    {
        GeometryReader
        {
            proxy in
            Color.clear
                .anchorPreference(key: Key.self, value: .bounds)
                {
                    anchor in proxy[anchor].size.width
                }
        }
    }
}

顶部的链接最好地描述了每个人的目的。

最大宽度首选项键

这将定义一个新键,将缺省值设置为零,并在添加新值时,采用最宽的

确定宽度

这个视图只是一个空的(Color.clear)背景,但是我们的新首选项设置为它的宽度。我们稍后会回到清晰背景部分,但首先:有几种方法可以设置偏好,这里我们使用anchorPreference,为什么?
嗯,anchorPreference有“没有可用的概览”,所以我实际上没有一个好的答案,除了它在实践中似乎更可靠。是的,货物崇拜代码。哇!我有一种预感,它需要一个块和所有,SwiftUI可以重新运行该块,以获得更新的值时,有影响布局的变化。
我的另一个希望是,这些东西将得到更好的记录,以便我们可以更好地理解这些不同类型的用途,新的SwiftUI开发人员可以加入进来,而不必花费所有的时间在堆栈溢出或阅读像这样的博客文章。
总之,锚是一个表示视图中的维度或位置的令牌,但它不会直接给予你值,你必须用GeometryProxy来兑现它,才能得到实际值,所以,这就是我们所做的--为了得到值,你用它作为代理的下标,所以proxy[anchor].size.width得到了我们想要的,当锚是.bounds(我们传递给anchorPreference调用的值)时,它有点扭曲,但它完成了任务。
maximumSubViewWidth是从父视图传入的绑定变量,用于确保每个子视图引用的maximumSubViewWidth始终是最新的最大值。

ForEach(self.items) { item, in
                ItemSubview(maximumSubViewWidth: $maximumSubViewWidth, item: item)
            }

这个问题的一个问题是,在整个行上有一个不想要的微妙但仍然明显的动画,任何UI都被调整到最大宽度。我所做的工作是向父容器添加一个动画修改器,该修改器从nil开始,在显式触发后切换回.default。

.animation(self.initialised ? .default : nil)

在用户显式地与行交互之后,我将self. initialized设置为true(就我而言,他们点击一行来展开显示附加信息)-这确保了初始动画不会错误地发生,但动画在那之后会恢复正常。我最初的尝试是在.onAppear修饰符中切换initialized的状态,以便更改是自动的,但这没有起作用,因为我“假设在初始出现之后可以进行尺寸调整。
另一件需要注意的事情(这可能表明,虽然这个解决方案的工作,它不是最好的方法)是我看到这个消息在控制台重复的每一个项目,或只是那些需要调整大小(不清楚,但警告总数=项目数):
绑定首选项MaximumWidthPreferenceKey尝试每帧更新多次。
如果有人能想出一种方法来实现上述同时避免这个警告,那么太好了!

更新:我已了解上述内容。

这实际上是一个重要的变化,因为如果不解决这个问题,我会看到这个专栏在随后的屏幕访问中变得越来越宽。
该视图有一个新的widthDetermined @State变量,该变量被设置为false,并在. onAppeared中变为true。
如果widthDetermined为false,即没有设置,我就只确定视图的宽度,我使用https://fivestars.blog/swiftui/conditional-modifiers.html中建议的条件修饰符来完成这一操作:

func `if`<Content: View>(_ conditional: Bool, content: (Self) -> Content) -> TupleView<(Self?, Content?)> {
    if conditional { return TupleView((nil, content(self))) }
    else { return TupleView((self, nil)) }
}

并且在视图中:

.if(!self.widthDetermined) {
                $0.overlay(DetermineWidth())
            }
wn9m85ua

wn9m85ua3#

我有类似的问题。我的文本在一行的标签之一是从2个字符到20个字符不等。它搞乱了水平对齐,正如你所看到的。我正在寻找使这一列的行作为固定宽度。我想出了一些非常简单的东西。它为我工作。

var body: some View { // view for each row in list
        VStack(){
            HStack {
                  Text(wire.labelValueDate)
                        .
                        .
                        .foregroundColor(wire.labelColor)
                        .fixedSize(horizontal: true, vertical: false)
                        .frame(width: 110.0, alignment: .trailing)
                    }
                  }
}

相关问题