Home > Software design >  SwiftUI - Transition animation bug
SwiftUI - Transition animation bug

Time:05-17

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 HStacks 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:

Bug screen shot

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) {

enter image description here

  • Related