代码之家  ›  专栏  ›  技术社区  ›  drekka

如何阻止SwiftUI视图动画级联?

  •  0
  • drekka  · 技术社区  · 4 年前

    我之前问过这个问题,但我知道我已经把它提炼成了一块可以在操场上运行的示例代码。

    问题的基础是,我有两个视图被父视图切换。内容视图和设置视图。内容视图有三个标签,第三个标签的框架设置为与其他两个标签的最宽尺寸相匹配。

    在我将动画添加到在“内容”视图和“设置”视图之间切换的视图之前,这一切都很正常。但当我添加动画时,它会“级联”到内容视图中,导致3标签在屏幕上飞过,而不仅仅是正确对齐。

    Screen capture

    下面是示例代码,如果你想玩它,可以将其剪切并粘贴到操场上。

    import PlaygroundSupport
    import SwiftUI
    
    // Preference key which tracks the largest with of a label.
    struct LabelWidthPreferenceKey: PreferenceKey {
        static var defaultValue: CGFloat = .zero
        static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
            value = max(value, nextValue())
        }
    }
    
    // View modifier for tracking the size of a label.
    struct LabelWidthObserver: ViewModifier {
    
        func body(content: Content) -> some View {
            content.background {
                GeometryReader { geometry in
                    Color.clear.preference(key: LabelWidthPreferenceKey.self, value: geometry.size.width)
                }
            }
        }
    }
    
    struct RootView: View {
        @State private var showSettings = false
        var body: some View {
            Group {
                if showSettings {
                    SettingsView { showSettings.toggle() }
                } else {
                    ContentView { showSettings.toggle() }
                }
            }
            .animation(.easeInOut, value: showSettings)
        }
    }
    
    struct SettingsView: View {
        let toggleSettings: () -> Void
        var body: some View {
            Text("Here be settings")
            Button("Cancel") { toggleSettings() }
        }
    }
    
    struct ContentView: View {
        let toggleSettings: () -> Void
        @State private var labelWidth: CGFloat = 20.0
        var body: some View {
            VStack(alignment: .leading, spacing: 4.0) {
                Text("Line 1").modifier(LabelWidthObserver()).border(.red)
                Text("longer line two").modifier(LabelWidthObserver()).border(.red)
    
                Text("Line 3")
                    .frame(minWidth: labelWidth, alignment: .trailing)
                    .border(.green)
                Button("Show settings") { toggleSettings() }
            }
            .onPreferenceChange(LabelWidthPreferenceKey.self) {
                labelWidth = max(labelWidth, $0)
            }
        }
    }
    
    PlaygroundPage.current.setLiveView(RootView())
    

    有人知道如何在不触发第三个标签动画的情况下,为两个视图之间的过渡设置动画吗?

    0 回复  |  直到 4 年前
        1
  •  1
  •   Asperi    4 年前

    这真的是一个bug,因为 ContentView 不依赖于 showSettings 价值,所以值得向苹果提交反馈。

    这里有一个可能的解决方法。使用Xcode 13.2/iOS 15.2进行测试

    var body: some View {
        VStack(alignment: .leading, spacing: 4.0) {
            Text("Line 1").modifier(LabelWidthObserver()).border(.red)
            Text("longer line two").modifier(LabelWidthObserver()).border(.red)
    
            Text("Line 3")
                .frame(minWidth: labelWidth, alignment: .trailing)
                .border(.green)
            Button("Show settings") { toggleSettings() }
        }
        .animation(nil, value: labelWidth)                    // << here !!
        .onPreferenceChange(LabelWidthPreferenceKey.self) {
            labelWidth = max(labelWidth, $0)
        }
    }