SwiftUI - 可调整大小的列表高度取决于元素计数

问题描述 投票:0回答:5

我在动态更改取决于元素计数的列表高度时遇到一些麻烦。

我尝试了这个解决方案,但没有成功。

List {
    ForEach(searchService.searchResult, id: \.self) { item in
        Text(item)
        .font(.custom("Avenir Next Regular", size: 12))
    }
}.frame(height: CGFloat(searchService.searchResult.count * 20))
swiftui swiftui-list
5个回答
33
投票

TL;博士

这不是 SwiftUI 的设计者希望你使用列表的方式。要么你必须想出一个将来可能会崩溃的黑客解决方案(见下文),要么使用列表以外的东西。

背景

SwiftUI 往往有两种类型的视图

  1. 那些设计为易于修改和组合,为独特的外观和感觉提供无限的可定制性。
  2. 旨在为某种类型的交互提供标准、一致的感觉,无论它们在什么应用程序中使用。

类型 1 的一个例子是文本。您可以更改字体大小、粗细、字体、颜色、背景、填充等。它是专为您修改而设计的。

类型 2 的一个例子是列表。你不能直接控制行高,你不能改变视图周围的填充,你不能告诉它只显示这么多行,等等。他们不希望它是非常可定制的,因为每个应用程序的列表的行为会有所不同,从而违背了标准控件的目的。

List 旨在用尽可能多的行填充整个父视图,即使它们是空的或仅部分显示在屏幕上(如果有太多行无法一次显示,则滚动)。

您的问题

您遇到的问题是因为列表的大小不会以任何方式影响其行的大小。 SwiftUI 并不关心行数是否太多或太少而无法满足您的首选大小;它会很乐意根据内容调整行的大小,即使这意味着它们没有全部显示或显示了空行。

如果您确实需要根据父行的大小调整行的大小,则应该使用 VStack。如果需要滚动,则需要将 VStack 包装在 ScrollView 中。

黑客解决方案

如果您仍然坚持使用列表,则必须执行以下操作:

struct ContentView: View {

    @State private var textHeight: Double = 20
    let listRowPadding: Double = 5 // This is a guess
    let listRowMinHeight: Double = 45 // This is a guess
    var listRowHeight: Double {
        max(listRowMinHeight, textHeight + 2 * listRowPadding)
    }

    var strings: [String] = ["One", "Two", "Three"]

    var body: some View {
        VStack {
            HStack {
                Text(String(format: "%2.0f", textHeight as Double))
                Slider(value: $textHeight, in: 20...60)
            }
                VStack(spacing: 0) {
                    Color.red
                    List {
                        ForEach(strings, id: \.self) { item in
                            Text(item)
                                .font(.custom("Avenir Next Regular", size: 12))
                                .frame(height: CGFloat(self.textHeight))
                                .background(Color(white: 0.5))
                        }
                    }
                    // Comment out the following line to see how List is expected to work
                    .frame(height: CGFloat(strings.count) * CGFloat(self.listRowHeight))
                    Color.red
            }.layoutPriority(1)
        }
    }
}

滑块用于显示列表行高如何随子视图的高度而变化。您必须手动选择

listRowPadding
listRowMinHeight
才能获得最符合您期望的外观。如果 Apple 更改了列表的外观(更改填充、最小行高等),您必须记住返回并手动调整这些值。


27
投票

自身尺寸
List

如果你想要一个

List
一次性显示它的内容,这意味着你不需要回收功能(列表的关键功能),所以你所需要的就是不使用
List
!相反,您可以直接使用
ForEach
,然后它会根据其内容自行调整大小:

ForEach(searchService.searchResult, id: \.self) { item in
    VStack(alignment: .leading) {
        Text(item).font(.custom("Avenir Next Regular", size: 12))
        Divider()
    }.padding(.horizontal, 8)
}

您可以根据您的需要更改所有尺寸和间距

请注意您可以使用

iOS 14
中的LazyVStack使其延迟加载并提高其性能。


9
投票

从 iOS 14 开始,您可以使用

LazyVStack
代替
List
。 列表似乎跨越整个父视图高度,与行高或计数无关。

LazyVStack {
    ForEach(searchService.searchResult, id: \.self) { item in
        Text(item)
        .font(.custom("Avenir Next Regular", size: 12))
    }
}.frame(height: 

其他解决方案是基于

.frame(height: )
或其他
rowCount*rowHeight
 在列表上设置 
GeometryReader -> geometry.size.height


0
投票

可以使用 SwiftUIViewInspector 开源框架,您可以将其添加为 Swift 包,然后添加到构建阶段、目标和链接阶段。那么它就是这样的:

import Inspect
...
@State var listHeight: CGFloat = 100 // just any default height
...
List {
    ...
}
.frame(height: listHeight)
.inspect { list in
    switch list {
        case .collectionView(let collectionView):
            self.listHeight = collectionView.contentSize.height
        case .tableView((let tableView)):
            self.listHeight = tableView.contentSize.height
    }
}

首先,列表以默认高度显示,它在内部计算可滚动大小,其中包括所有标题和行,并将其存储在其

contentSize
属性中。因此,我们只需将
listHeight
状态设置为此值,然后调整列表大小以显示所有内容。

这带有明显的警告,仅将其用于小表,因为本质上它会禁用行重用。或者如果它正在进行一些离屏优化,则可能不会。


-5
投票

SwiftUi 已经发展。这是 SwiftUI 3 的简单明了的答案:https://stackoverflow.com/a/65769005/4514671

© www.soinside.com 2019 - 2024. All rights reserved.