列表内的 SWIftUI anchorPreference
Posted
技术标签:
【中文标题】列表内的 SWIftUI anchorPreference【英文标题】:SWiftUI anchorPreference inside List 【发布时间】:2020-12-21 02:34:51 【问题描述】:我使用anchorPreferences
来设置GeometryReader
的height
以适应其内容的height
。我遇到的问题是,在上下滚动List
几次后,应用程序冻结,设计变得混乱,我在控制台中收到以下消息:
Bound preference CGFloatPreferenceKey tried to update multiple times per frame.
有什么办法可以解决这个问题吗?
重要提示:我已尽可能简化我的设计。
代码如下:
struct ListAndPreferences: View
var body: some View
List(1..<35) idx in
HStack
Text("idx: \(idx)")
InnerView(idx: idx)
struct InnerView: View
@State var height: CGFloat = 0
var idx: Int
var body: some View
GeometryReader proxy in
generateContent(maxWidth: proxy.frame(in: .global).size.width)
.anchorPreference(key: CGFloatPreferenceKey.self, value: Anchor<CGRect>.Source.bounds, transform: anchor in
proxy[anchor].size.height
)
.frame(height: height)
.onPreferenceChange(CGFloatPreferenceKey.self, perform: value in
height = value
)
private func generateContent(maxWidth: CGFloat) -> some View
VStack
HStack
Text("hello")
.padding()
.background(Color.purple)
Text("world")
.padding()
.background(Color.purple)
.frame(width: maxWidth)
struct CGFloatPreferenceKey: PreferenceKey
static var defaultValue: CGFloat = 0
static func reduce(value: inout CGFloat , nextValue: () -> CGFloat)
value = nextValue()
【问题讨论】:
【参考方案1】:实际上我们不知道 SwiftUI 可以在堆栈上渲染多少次自定义视图的主体,但偏好确实只需要编写一次(然后它们应该被转换,这更复杂)。
可能的解决方案是在 Preferences 中使用不同类型的容器,因此值不会重写而是累积。
这是您的代码的修改部分。使用 Xcode 12.1 / ios 14.1 测试
// use dictionary to store calculated height per view idx
struct CGFloatPreferenceKey: PreferenceKey
static var defaultValue: [Int: CGFloat] = [:]
static func reduce(value: inout [Int: CGFloat] , nextValue: () -> [Int: CGFloat])
value.merge(nextValue()) $1
struct InnerView: View
@State var height: CGFloat = 0
var idx: Int
var body: some View
GeometryReader proxy in
generateContent(maxWidth: proxy.frame(in: .global).size.width)
.anchorPreference(key: CGFloatPreferenceKey.self, value: Anchor<CGRect>.Source.bounds, transform: anchor in
[idx: proxy[anchor].size.height]
)
.frame(minHeight: height)
.onPreferenceChange(CGFloatPreferenceKey.self, perform: value in
height = value[idx] ?? .zero
)
private func generateContent(maxWidth: CGFloat) -> some View
VStack
HStack
Text("hello")
.padding()
.background(Color.purple)
Text("world")
.padding()
.background(Color.purple)
.frame(width: maxWidth)
【讨论】:
but preferences really required to be written only once
- 因此“尝试每帧更新多次”。是坏事吗?
我不认为这是坏,这只是一个警告,意味着您存储的一些偏好值可能会丢失,而在需要时不应用。跨度>
嗯,有道理。谢谢!
谢谢@Asperi!有没有办法在 InnerView 中“生成”“idx”,这样我就不必将其作为参数发送?【参考方案2】:
List
中的代码试图做太多事情。 SwiftUI 的好处之一是您无需手动设置视图的高度。也许GeometryReader
只是在您简化示例时遗留下来的,但在这种简化的情况下,您不需要它(或首选项)。这就是你所需要的:
import SwiftUI
struct ListAndPreferences: View
var body: some View
List(1..<35) idx in
HStack
Text("idx: \(idx)")
InnerView()
struct InnerView: View
var body: some View
VStack
HStack
Text("hello")
.padding()
.background(Color.purple)
Text("world")
.padding()
.background(Color.purple)
.frame(maxWidth: .infinity)
struct InnerView_Previews: PreviewProvider
static var previews: some View
ListAndPreferences()
如果您出于某种原因确实需要通过onPreferenceChange
或就此而言onChange(of:)
监听更改,则需要确保任何影响 GUI 的更改都发生在主线程上.以下更改将使您在原始代码中看到的警告静音:
.onPreferenceChange(CGFloatPreferenceKey.self) value in
DispatchQueue.main.async
height = value
【讨论】:
以上是关于列表内的 SWIftUI anchorPreference的主要内容,如果未能解决你的问题,请参考以下文章
如何使用滚动视图单击列表部分内的项目以导航到详细信息页面 SwiftUI
访问全局函数内的环境变量 - SwiftUI + CoreData