SwiftUI - 过渡动画错误(已解决:不是错误!)

SwiftUI - Transition animation bug (Resolved: not a bug!)

这是对先前已解决但未解决的问题的跟进。

情况是我在屏幕上有一个文本网格,它是通过另一个视图的转换呈现的。我不能使用 LazyVGrid 来呈现网格,因为一列的宽度需要匹配其中最长的文本。所以我的解决方案是使用 HStacks 并设置列的宽度。要将该宽度设置为最长文本的宽度,我使用 GeometryReader 来读取文本的大小,然后通过视图树中的 anchorPreference 将该信息发送到 @State 变量,用于设置列中所有标签的宽度。

听起来很复杂,但确实有效。至少......直到我尝试过渡到它。然后返回了一个旧错误,其中 anchorPreferenceonPreferenceChange(...) 函数的使用似乎改变了带来视图的动画并导致文本滑动得太快。根据此屏幕截图:

目前我不知道如何更正动画以使文本随父视图一起滑动。有什么建议吗?

这是此错误的完整代码:


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 [=10=] != labelWidth {
                labelWidth = [=10=]
            }
        }
    }

    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[[=10=]].width.rounded(.up)
                    }
                Text(desc)
                    .border(.red)
            }
        }
        .fixedSize(horizontal: false, vertical: true)
        .padding([.leading, .trailing], 20)
    }
}

不是 SwiftUI 错误。在下面找到一个修复(使用 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) {