为什么不是';如果调用onPreferenceChange,则为';SwiftUI中的滚动视图中有什么?
我在ScrollView中看到了一些偏好键的奇怪行为。如果我把为什么不是';如果调用onPreferenceChange,则为';SwiftUI中的滚动视图中有什么?,swiftui,Swiftui,我在ScrollView中看到了一些偏好键的奇怪行为。如果我把onPreferenceChange放在ScrollView里面,它不会被调用,但是如果我把它放在外面,它会被调用 我设置了一个宽度首选项,如下所示: struct WidthPreferenceKey:PreferenceKey{ typealias值=CGFloat 静态var defaultValue=CGFloat(0) 静态函数reduce(值:inout CGFloat,nextValue:()->CGFloat){ v
onPreferenceChange
放在ScrollView
里面,它不会被调用,但是如果我把它放在外面,它会被调用
我设置了一个宽度首选项,如下所示:
struct WidthPreferenceKey:PreferenceKey{
typealias值=CGFloat
静态var defaultValue=CGFloat(0)
静态函数reduce(值:inout CGFloat,nextValue:()->CGFloat){
value=nextValue()
}
}
以下简单视图不打印:
struct ContentView:View{
var body:一些观点{
滚动视图{
文本(“你好”)
.首选项(键:WidthPreferenceKey.self,值:20)
.onPreferenceChange(WidthPreferenceKey.self){
print($0)//未被调用,我们处于滚动视图中。
}
}
}
}
但这是可行的:
struct ContentView:View{
var body:一些观点{
滚动视图{
文本(“你好”)
.首选项(键:WidthPreferenceKey.self,值:20)
}
.onPreferenceChange(WidthPreferenceKey.self){
印刷品(0美元)
}
}
}
我知道我可以使用后一种方法来解决这个问题,但有时我在一个子视图中,该子视图无法访问其父滚动视图,但我仍然希望录制一个首选项键 关于如何获得
onPreferenceChange
以在滚动视图中调用有什么想法吗
注意:我得到了绑定首选项宽度PreferenceKey尝试在每帧中更新多次。
当我将函数放入滚动视图时,这可能解释了发生了什么,但我无法理解
谢谢 您只能在superView中阅读,但设置后可以使用transformPreference
进行更改
struct ContentView: View {
var body: some View {
ScrollView {
VStack{
Text("Hello")
.preference(key: WidthPreferenceKey.self, value: 20)
}.transformPreference(WidthPreferenceKey.self, {
$0 = 30})
}.onPreferenceChange(WidthPreferenceKey.self) {
print($0)
}
}
}
最后一个值现在是30
。希望这是你想要的
您可以从其他层读取:
ScrollView {
Text("Hello").preference(key: WidthPreferenceKey.self, value: CGFloat(40.0))
.backgroundPreferenceValue(WidthPreferenceKey.self) { x -> Color in
print(x)
return Color.clear
}
}
问题似乎不一定与ScrollView
有关,而是与您使用PreferenceKey
有关。例如,这里是一个示例结构,其中根据矩形的宽度设置首选项键
,然后使用.onPreferenceChange()
打印,所有这些都在滚动视图
中。当您拖动滑块更改宽度时,会更新按键并执行打印关闭
struct ContentView: View {
@State private var width: CGFloat = 100
var body: some View {
VStack {
Slider(value: $width, in: 100...200)
ScrollView(.vertical) {
Rectangle()
.background(WidthPreferenceKeyReader())
.onPreferenceChange(WidthPreferenceKey.self) {
print($0)
}
}
.frame(width: self.width)
}
}
}
struct WidthPreferenceKeyReader: View {
var body: some View {
GeometryReader { geometry in
Rectangle()
.fill(Color.clear)
.preference(key: WidthPreferenceKey.self, value: geometry.size.width)
}
}
}
正如您所注意到的,当密钥第一次尝试设置时,控制台会打印“绑定首选项宽度首选项密钥尝试每帧更新多次”,但随后会立即设置一个实际值,并继续动态更新
您实际试图设置的值是什么?在.onPreferenceChange()
中您试图做什么?我认为您的示例中没有调用onPreferenceChange,因为它的函数与preference(键…)有很大的不同
首选项(键:…)为在其上使用的视图设置首选项值。
而onPreferenceChange是在父视图上调用的函数,父视图是视图树层次结构中较高位置的视图。它的功能是遍历所有子项和子项,并收集它们的首选项(键:)值。当它找到一个值时,它将使用PreferenceKey中的reduce函数来处理这个新值和所有已收集的值。一旦收集并减少了所有值,它将对结果执行onPreference闭包
在第一个示例中,从未调用此闭包,因为文本(“Hello”)视图没有设置首选项键值的子对象(实际上,该视图根本没有子对象)。在第二个示例中,滚动视图有一个子视图,用于设置其首选项值(文本视图)
所有这些都不能解释每帧多次的错误——这很可能是不相关的
最新更新(2020年4月24日):
在类似的情况下,我可以通过改变PreferenceData的可等式条件来诱导onPreferenceChange的调用。首选项数据需要是相等的(可能是为了检测它们的变化)。但是,锚类型本身不再是相等的。要提取锚类型中包含的值,需要使用GeometryProxy。通过GeometryReader获得GeometryProxy。为了不干扰视图的设计,我在PreferenceData结构的Equalable函数中生成了一个GeometryReader,将其中一些视图封装到GeometryReader中:
struct ParagraphSizeData: Equatable {
let paragraphRect: Anchor<CGRect>?
static func == (value1: ParagraphSizeData, value2: ParagraphSizeData) -> Bool {
var theResult : Bool = false
let _ = GeometryReader { geometry in
generateView(geometry:geometry, equality:&theResult)
}
func generateView(geometry: GeometryProxy, equality: inout Bool) -> Rectangle {
let paragraphSize1, paragraphSize2: NSSize
if let anAnchor = value1.paragraphRect { paragraphSize1 = geometry[anAnchor].size }
else {paragraphSize1 = NSZeroSize }
if let anAnchor = value2.paragraphRect { paragraphSize2 = geometry[anAnchor].size }
else {paragraphSize2 = NSZeroSize }
equality = (paragraphSize1 == paragraphSize2)
return Rectangle()
}
return theResult
}
}
struct ParagraphSizeData:equalable{
让段落直截了当:锚?
静态函数==(值1:ParagraphSizeData,值2:ParagraphSizeData)->Bool{
var结果:Bool=false
设u=GeometryReader{geometry in
generateView(几何图形:几何图形、等式和结果)
}
func generateView(几何图形:GeometryProxy,等式:inout Bool)->矩形{
让第1段,第2段:NSSize
如果让anAnchor=value1.paragraphRect{paragraphSize1=geometry[anAnchor].size}
else{paragraphSize1=NSZeroSize}
如果让anAnchor=value2.paragraphRect{paragraphSize2=geometry[anAnchor].size}
else{paragraphSize2=NSZeroSize}
相等=(第1段==第2段)
返回矩形()
}
返回结果
}
}
带着亲切的问候我很长时间以来一直在努力解决这个问题,并找到了如何处理这个问题的方法,尽管我所使用的方法只是一种变通方法
使用带有标志的onAppear
到ScrollView
,使其子项显示出来
或者
使用列表
代替它。
它具有滚动功能,您可以使用它自己的功能和UI方面的UITableView外观对其进行自定义。最重要的是它按照我们的预期工作
[如果您有时间阅读更多]<
...
@State var isShowingContent = false
...
ScrollView {
if isShowingContent {
ContentView()
}
}
.onAppear {
self.isShowingContent = true
}
struct ContentView: View {
var body: some View {
ScrollView {
Text("Hello")
.preference(key: WidthPreferenceKey.self, value: 20)
.onPreferenceChange(WidthPreferenceKey.self) {
print($0) // Not being called, we're in a scroll view.
}
}
}
}
struct ContentView: View {
var body: some View {
ScrollView {
Text("Hello")
.preference(key: WidthPreferenceKey.self, value: 20)
}
.onPreferenceChange(WidthPreferenceKey.self) {
print($0)
}
}
}