我有以下函数可以移动视图,使其适合使用 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 分配...
感谢您的任何见解!
不幸的是,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
}
}
这确保了如果图表中的任何视图意外生成默认值,该值将被忽略。
相关链接: