PreferenceKey 问题 - SwiftUI 有时似乎会生成额外的视图,这些视图在减少 PreferenceKey 时显示为默认值?

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

我有以下函数可以移动视图,使其适合使用 PreferenceKeys 的某个矩形。

该函数看起来像这样:

struct SizePreferenceKey: PreferenceKey {
    static let defaultValue: CGSize = CGSize.zero
    
    static func reduce(value: inout CGSize,
                       nextValue: () -> CGSize) {
        let otherSize = nextValue()
        print("reduce.value is: \(value)")
        print("reduce.nextValue is: \(otherSize)")
        // 
        // the asignment blow (although in many examples) is a problem - but no clue where 
        // the second key is coming from?
        // the value of otherSize is the default Size.zero, coming from a secondary view
        value = otherSize
        // should be: value = otherSize != defaultValue ? otherSize : value
    }
}

extension View {
    func moveIfFrame(withOrigin point:Binding<CGPoint>, outside screen: CGRect) -> some View {
        background(GeometryReader { proxy in
            let size = proxy.size
            // Color is the only child of background - or not?
            Color.clear.preference(
                key: SizePreferenceKey.self,
                value: size
            )
        })
        .onPreferenceChange(SizePreferenceKey.self) { size in
            var offset = CGPoint.zero
            offset.x = screen.maxX - (point.x.wrappedValue + size.width)
            offset.y = screen.maxY - (point.y.wrappedValue + size.height)
            point.wrappedValue = CGPoint(x: point.x.wrappedValue + ( offset.x < 0 ? offset.x : 0) , y: point.y.wrappedValue + (offset.y < 0 ? offset.y : 0 ))
        }
    }
}


// Usage
//
struct ContentView: View {
    
    @State var point = CGPoint(x:2000,y:2000)
    
    var body: some View {
        GeometryReader { proxy in
            ZStack(alignment:.topLeading) {
                Rectangle().foregroundColor(.green)
                Text("Foo")
                    .frame(width: 100, height: 100)
                    .background(Color.red)
    
                    // Operator used here
                    .moveIfFrame(withOrigin: $point, outside: proxy.frame(in: .local))
 
                    .offset(x: point.x, y:point.y)
            }
            .background(Color.green)
            
        }
    }
}

它在大多数情况下都有效,其中 SizePreferenceKey 的reduce 永远不会被调用 - 这是一种逻辑,因为背景只有一个 Child: Color 并且 PreferenceKey 是明确的,因此不需要reduce。 但在我的生产代码中,在某些情况下,突然调用了reduce,并显示了带有默认 Rect.zero 的第二个(newValue)。这个问题很容易解决,但出于我的精神状态,我想知道发生了什么。由于 moveIfFrame 中背景下的结构是在我的函数中构建的,因此我希望总是只有一个视图,因此一个 PreferenceKey 值出现在层次结构中,并且永远不会调用 reduce。

SwiftUI 有时会插入视图吗?或者有时第二个 PreferenceKey 值来自哪里?网络上流传的许多例子都使用了我在reduce函数中的类似实现(带有value = newValue的赋值) - 这似乎不正确。

所以我的问题是:第二种观点可能来自哪里?在什么情况下?或者我怎样才能弄清楚发生了什么(当我尝试进入视图层次结构时调试器崩溃)?

在任何情况下,对于像我上面这样的函数,reduce 需要忽略 defaultValues(或者只是空 - 我猜) - 尽管大多数示例都有一个reduce 分配...

感谢您的任何见解!

swiftui
1个回答
0
投票

不幸的是,PreferenceKey API 似乎以一些意想不到的方式工作。我在添加特定类型的视图时遇到了这个问题,其中之一是 ProgressView`:

ZStack {
    Text("Test")
        .preference(key: MainScreenBottomPaddingPreferenceKey.self, value: 123)
    Text("Test2")
}
.onPreferenceChange(MainScreenBottomPaddingPreferenceKey.self) { newState in
    print(newState)
}

在这个例子中,“预期”打印出“123”,并且不应调用reduce函数。但是,如果将第二个

Text
替换为
ProgressView
,则将
defaultValue
作为
nextValue()
,多次调用reduce 函数。

直观地说,似乎只有包含

.preference()
修饰符的视图才会导致调用reduce 函数。然而,实际情况并非如此。具体来说,defaultValue的文档指出:

没有明确键值的视图产生这个默认值 价值。组合子视图可能会删除由以下方式产生的隐式值 使用默认值。

文档没有指定视图何时或是否始终生成默认值。这意味着您不能总是假设只有带有

.preference()
修饰符的视图才会参与到reduce 函数中。

此行为的解决方法是分配一个 nil 值作为

defaultValue
并且仅使用非 nil 值:

struct MainScreenBottomPaddingPreferenceKey: PreferenceKey {
    typealias Value = CGFloat?
    static var defaultValue: Value = nil
    
    static func reduce(value: inout Value, nextValue: () -> Value) {
        value = nextValue() ?? value
    }
}

这确保了如果图表中的任何视图意外生成默认值,该值将被忽略。

相关链接:

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