SwiftUI 折叠多行文本

6

我正在尝试在具有多行文本的 Text 上添加展开/折叠动画,但遇到了奇怪的问题。

下面是一个GIF,显示了这个问题。我设置了缓慢的动画以使其更清晰。

https://www.dropbox.com/s/sx41g9tfx4hd378/expand-collapse-stack_overflow.gif

我正在对视图的高度属性进行动画处理,似乎无论动画周期如何,Text 将立即转换为一行。以下是一些代码:

struct ContentView: View {

    @State var expanded = false

    var body: some View {
        VStack(spacing: 20) {
            HStack {
                Button(expanded ? "Colapse" : "Expand") {
                    withAnimation {
                        self.expanded.toggle()
                    }
                }
            }
            VStack(spacing: 10) {
                Text(bigText)
                Text(bigText)
            }
            .frame(height: expanded ? .none : 0)
            .clipped()
            .background(Color.red)
            Text("Thist is another text underneath the huge one. ")
                .font(.system(.headline))
                .foregroundColor(.red)
            Spacer()
        }
    }
}

我尝试过很多其他的东西,目前这是最接近所需输出的方式,就像在UIKit中的UIStackView中动画标签一样。有没有正确的方法?这是一个bug吗?通常问题出在开发者身上,但我注意到如果我使用DisclosureGroup,当它展开时动画可以正常工作,但当它折叠时就没有动画了。所以这可能是多行Text的限制吗?非常感谢你。

1
你尝试过在“bigText”文本视图中添加.fixedSize(horizontal: false, vertical: true)吗? - Lae
1个回答

5
问题在于我们对nil0之间的帧进行动画处理,但是对于内部文本项,只传递了边缘值。
解决这个问题需要分两步进行:
  1. 将高度设置为可动画数据,这样每次更改高度值时都会传递给内容。
  2. 计算文本内容的实际最大高度,因为我们需要具体的可动画范围值。
以下是该方法的演示。使用 Xcode 13 / iOS 15 准备。
注意:模拟器中激活了缓慢动画以获得更好的可见性。 enter image description here
let bigText = """
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
"""

struct ContentView: View {

    // we need `true` on construction (not visible) to calculate
    // max content height
    @State var expanded = true    // << required initial true !!

    @State private var maxHeight: CGFloat?

    var body: some View {
        VStack(spacing: 20) {
            HStack {
                Button(expanded ? "Colapse" : "Expand") {
                    withAnimation {
                        self.expanded.toggle()
                    }
                }
            }
            VStack(spacing: 10) {
                Text(bigText)
                Text(bigText)
            }
            .background(GeometryReader { // read content height
                Color.clear.preference(key: ViewHeightKey.self,
                                              value: $0.frame(in: .local).size.height)
            })
            .onPreferenceChange(ViewHeightKey.self) {
                if nil == self.maxHeight {
                    self.maxHeight = $0     // << needed once !!
                }
            }
            .modifier(AnimatingFrameHeight(height: expanded ? maxHeight ?? .infinity : 0))
            .clipped()
            .background(Color.red)
            Text("Thist is another text underneath the huge one. ")
                .font(.system(.headline))
                .foregroundColor(.red)
            Spacer()
        }
        .onAppear {
            // this cases instance redraw on first render
            // so initial state will be invisible for user
            expanded = false    // << set if needed here !!
        }
    }
}

struct AnimatingFrameHeight: AnimatableModifier {
    var height: CGFloat = 0

    var animatableData: CGFloat {
        get { height }
        set { height = newValue }
    }

    func body(content: Content) -> some View {
        content.frame(height: height)
    }
}

struct ViewHeightKey: PreferenceKey {
    static var defaultValue: CGFloat { 0 }
    static func reduce(value: inout Value, nextValue: () -> Value) {
        value = value + nextValue()
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接