This is a follow up to a prior question which was solved then unsolved.
The situation is that I have a grid of text on a screen that is presented via a transition from another view. I can't use LazyVGrid
to present the grid because the width of one column needs to match the longest text in it. So the solution I have is to use HStack
s and set the width of the column. To set that width as the width of the longest text I'm using a GeometryReader
to read the size of the text and then sending that information via a anchorPreference
up the view tree to a @State
variable that's used to set the width of all the labels in the column.
It sounds complicated but it works. At least ... until I tried to transition to it. Then an old bug returned where the use of the anchorPreference
and onPreferenceChange(...)
function seem to change the animations bringing on the view and cause the text to slide on too fast. As per this screen capture:
At the moment I'm at a loss as to how to correct the animations so the text slides on with the parent view. Any suggestions?
Here is the complete code for this bug:
import SwiftUI
@main
struct TransitionAnimationBug: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
struct ContentView: View {
@State var displaySettings = false
var body: some View {
Group {
if displaySettings {
DataView(displaySettings: $displaySettings)
.transition(.slide)
} else {
MainView(displaySettings: $displaySettings)
.transition(.slide)
}
}
.animation(.easeInOut, value: displaySettings)
}
}
struct MainView: View {
let displaySettings: Binding<Bool>
var body: some View {
VStack(spacing: 20) {
Button("Show transition bug") {
displaySettings.wrappedValue.toggle()
}
Text("Watch the text as it animates on. it should slide with the view, but instead moves around independently.")
.padding(20).multilineTextAlignment(.center)
Text("This bug is triggered by the label width update via a @State variable in the onPreferenceChange function.")
.padding(20).multilineTextAlignment(.center)
}
}
}
// The preference key used to advise the parent view of a label's width.
struct LabelWidthPreferenceKey: PreferenceKey {
static var defaultValue = 0.0
static func reduce(value: inout Double, nextValue: () -> Double) {
if value != nextValue() {
value = max(value, nextValue())
}
}
}
struct DataView: View {
let displaySettings: Binding<Bool>
@State private var labelWidth: CGFloat = 0.0
var body: some View {
VStack(spacing: 30) {
row(title: "Short title", desc: "Short title long description")
row(title: "Rather long title", desc: "Rather long title long description")
row(title: "SS", desc: "Super short text")
Button("Close") { displaySettings.wrappedValue.toggle() }
}
.onPreferenceChange(LabelWidthPreferenceKey.self) {
// Updating the label width here triggers the bug.
if $0 != labelWidth {
labelWidth = $0
}
}
}
private func row(title: String, desc: String) -> some View {
GeometryReader { geometry in
HStack(alignment: .center) {
Text(title)
.frame(minWidth: labelWidth, alignment: .leading)
.border(.red)
.anchorPreference(key: LabelWidthPreferenceKey.self, value: .bounds) {
geometry[$0].width.rounded(.up)
}
Text(desc)
.border(.red)
}
}
.fixedSize(horizontal: false, vertical: true)
.padding([.leading, .trailing], 20)
}
}
CodePudding user response:
Not a SwiftUI bug. Find below a fix (tested with Xcode 13.3 / iOS 15.4)
VStack(spacing: 30) {
row(title: "Short title", desc: "Short title long description")
row(title: "Rather long title", desc: "Rather long title long description")
row(title: "SS", desc: "Super short text")
Button("Close") { displaySettings.wrappedValue.toggle() }
}
.animation(nil, value: labelWidth) // << here !!
.onPreferenceChange(LabelWidthPreferenceKey.self) {