【问题标题】:How to collapse/expand view based on dynamic content and maximum size restriction?如何根据动态内容和最大尺寸限制折叠/展开视图?
【发布时间】:2021-12-28 12:05:23
【问题描述】:

我必须在 SwiftUI 中支持 iOS 13,并且我必须在图像上实现一个视图。

当用户点击阅读更多按钮时,视图会展开以显示所有内容。如果视图可以容纳所有内容,则没有阅读更多按钮。

如何在展开/折叠此视图时动态更改高度?

该 api 返回带有样式信息的文本数组或列表对象。当我构建一般信息视图时,我在 VStack{ ForEach { ... } } 中遍历它们。我已在此处附上简化代码以供参考。

使用下面的代码,这是我目前所拥有的,当我折叠视图(限制 maxHeight)时,我得到了:

查看外部 VStack(灰色)如何正确调整大小,但 GeneralInformationView 仍然很大。我尝试剪切它,但它只显示文本的中心。


class ViewState: ObservableObject {
    @Published var isExpanded: Bool = false
    @Published var fullHeight: CGFloat = 0
}

struct ContentView: View {

    @ObservedObject var state: ViewState = ViewState()

    let maximumHeight: CGFloat = 200

    var showReadMoreButton: Bool {
        if state.isExpanded {
            return true
        } else {
            return state.fullHeight > maximumHeight
        }
    }

    var calculatedHeight: CGFloat {
        if !state.isExpanded && state.fullHeight > maximumHeight {
           return maximumHeight
       } else {
           return state.fullHeight
       }
    }

    var body: some View {
        VStack(spacing: 0) {
            GeneralInformationView()
                .background(GeometryReader { geometry in
                    Color.clear.preference(
                        key: HeightPreferenceKey.self,
                        value: geometry.size.height
                    )
                })
                .background(Color(.white))
                .frame(maxHeight: calculatedHeight)

            if showReadMoreButton {
                ReadMoreButton().environmentObject(state)
            }
        }
        .padding(.all, 16)
        .frame(maxWidth: .infinity, maxHeight: calculatedHeight + (showReadMoreButton ? 60 : 0) // 60 is the read more button size
        .onPreferenceChange(HeightPreferenceKey.self) {
            state.fullHeight = $0
        }
        .background(Color(.gray))
    }

    struct HeightPreferenceKey: PreferenceKey {
        static let defaultValue: CGFloat = 0

        static func reduce(value: inout CGFloat,
                           nextValue: () -> CGFloat) {
            value = max(value, nextValue())
        }
    }
}

struct GeneralInformationView: View {
    var body: some View {
        VStack(spacing: 8) {

            Text("I am a title and I could be of any length.")
                .fixedSize(horizontal: false, vertical: true)
                .frame(maxWidth: .infinity, alignment: .leading)

            Text("""
                I am a text view but actually a list with bulletpoints!
                - I can be of any size
                - I am received by API
                - I must not be trunkated!
                - If I don't fit into the outer view when collapsed,
                    then I should just be clipped, from the top of course
            """)
                .fixedSize(horizontal: false, vertical: true)
                .frame(maxWidth: .infinity, alignment: .leading)
                .multilineTextAlignment(.leading)

            Text("I am another text here.")
                .fixedSize(horizontal: false, vertical: true)
                .frame(maxWidth: .infinity, alignment: .leading)

            Text("I am a text and I could be of any length.")
                .fixedSize(horizontal: false, vertical: true)
                .frame(maxWidth: .infinity, alignment: .leading)

            Text("I am a text and I could be of any length.")
                .fixedSize(horizontal: false, vertical: true)
                .frame(maxWidth: .infinity, alignment: .leading)

            Text("I am a text and I could be of any length.")
                .fixedSize(horizontal: false, vertical: true)
                .frame(maxWidth: .infinity, alignment: .leading)
        }
    }
}

struct ReadMoreButton: View {

    @EnvironmentObject var state: ViewState

    var body: some View {
        Button(action: {
            state.isExpanded.toggle()
        }, label: {
            HStack {
                Text(state.isExpanded ? "Collapse" : "Read More")
            }
            .frame(maxWidth: .infinity, minHeight: 60, maxHeight: 60, alignment: .center)
                        .foregroundColor(Color(.red))
                        .background(Color(.white))
            }).overlay(Rectangle()
                        .foregroundColor(.clear)
                        .background(LinearGradient(
                            gradient: Gradient(colors: [.white.opacity(0),
(state.isExpanded ? .white.opacity(0) : .white.opacity(1))]),

                            startPoint: .top,
                            endPoint: .bottom))
                        .frame(height: 25)
                        .alignmentGuide(.top) { $0[.top] + 25 },
                     alignment: .top)
    }
}

【问题讨论】:

  • 这需要Minimal, Reproducible Example。您的代码中没有任何地方显示stateGeneralInformationView 是什么,因此很难解释您的意图。但是,首选项键读取视图的实际当前高度,因此您可以将其应用到其他地方。它不会计算视图可能具有不同数据的高度。
  • 谢谢,我添加了一个最小的可重现示例!
  • 我已经玩了几天了。我得出的结论是,鉴于您发布的 MRE,以这种方式限制它是不可能的。我能做到的最接近的方法是让窗口看起来像您想要的图片,但文字会像您的其他图片一样溢出。即使我解决了这个问题,我也会建议你把它放在 ScrollView 中,因为它更合适。如果信息量大于屏幕会发生什么?使用您的解决方案,即使有效,用户也无法查看所有内容。
  • 感谢您的尝试。我想知道是否有另一种方法可以开发这种设计。

标签: ios swiftui


【解决方案1】:

如果它可以帮助某人,我在this answer 的帮助下找到了解决方案。 将 GeneralInformationView() 包装到禁用的 ScrollView 中,并在框架修饰符中使用 minHeight 而不是 maxHeight 似乎可以解决问题!

 var body: some View {
    VStack(spacing: 0) {
            ScrollView { // Here adding a ScrollView
                GeneralInformationView()
                    .background(GeometryReader { geometry in
                        Color.clear.preference(
                            key: HeightPreferenceKey.self,
                            value: geometry.size.height
                        )
                    })
                    .background(Color(.white))
                    .frame(minHeight: calculatedHeight) // Here using minHeight instead of maxHeight
            }
            .disabled(true) // Which is disabled

        if showReadMoreButton {
            ReadMoreButton().environmentObject(state)
        }

    }
// The rest is the same

【讨论】:

    猜你喜欢
    • 2022-08-02
    • 2014-03-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-03-23
    • 1970-01-01
    • 2015-07-18
    • 2012-04-05
    相关资源
    最近更新 更多