I'm struggling trying to implement a PreferenceKey. I've read countless articles, so obviously I'm missing something. onPreferenceChange reports once at simulator startup but not during scrolling. Scrolling updates the text in the TextEditor overlay, but the offsetCGSize property is apparently never updated. Here's a simplified version of the code:
struct SOView: View {
@State private var firstText: String = "first"
@State private var secondText: String = "second"
@State private var thirdText: String = "third"
@State private var offsetCGSize: CGSize = .zero
var body: some View {
ScrollView {
VStack {
ForEach(0..<20) {index in
Text("Line number \(index)")
}
Image(systemName: "globe")
.imageScale(.large)
.foregroundColor(.accentColor)
TextField("First", text: $firstText)
GeometryReader { teGeo in
TextEditor(text: $secondText)
.frame(height: 100)
.overlay(
RoundedRectangle(cornerRadius: 10).stroke(lineWidth: 2)
)
.overlay(
Text("\(teGeo.frame(in: .global).minY)")
)
.preference(key: OffsetPreferenceKey.self, value: CGSize(width: 0, height: teGeo.frame(in: .global).minY))
}//geo
.frame(height: 100)
TextField("Third", text: $thirdText)
Text("TextEditor top is \(offsetCGSize.height)")
}//v
.padding()
}//scroll
.onPreferenceChange(OffsetPreferenceKey.self) { value in
offsetCGSize = value
print("offsetCGSize is \(offsetCGSize)")
}
}//body
}//so view
private struct OffsetPreferenceKey: PreferenceKey {
static var defaultValue: CGSize = CGSize.zero
static func reduce(value: inout CGSize, nextValue: () -> CGSize) {
value = nextValue()
}
}//off pref
Any guidance would be appreciated. Xcode 14.0.1, iOS 16
CodePudding user response:
For others - I don't understand why, but by moving the two TextFields, the Image and the reporting Text outside of the ScrollView I get the results I expect. (In this file, I changed the key name to OffsetPreferenceKey2)
struct SOView2: View {
@State private var firstText: String = "first"
@State private var secondText: String = "second"
@State private var thirdText: String = "third"
@State private var offsetCGSize: CGSize = .zero
var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundColor(.accentColor)
TextField("First", text: $firstText)
.padding(.horizontal)
ScrollView {
VStack {
ForEach(0..<20) {index in
Text("Line number \(index)")
}
GeometryReader { teGeo in
TextEditor(text: $secondText)
.frame(height: 100)
.overlay(
RoundedRectangle(cornerRadius: 10).stroke(lineWidth: 2)
)
.overlay(
Text("\(teGeo.frame(in: .global).minY)")
)
.preference(key: OffsetPreferenceKey2.self, value: CGSize(width: 0, height: teGeo.frame(in: .global).minY))
}//geo
.frame(height: 100)
}//v
.padding()
}//scroll
.onPreferenceChange(OffsetPreferenceKey2.self) { value in
offsetCGSize = value
print("offsetCGSize is \(offsetCGSize)")
}// pref change
TextField("Third", text: $thirdText)
.padding(.horizontal)
Text("TextEditor top is \(offsetCGSize.height)")
}//outer v
}//body
}//so view
private struct OffsetPreferenceKey2: PreferenceKey {
static var defaultValue: CGSize = CGSize.zero
static func reduce(value: inout CGSize, nextValue: () -> CGSize) {
value = nextValue()
}
}//off pref