如何根据动态内容和最大尺寸限制折叠/展开视图?

Posted

技术标签:

【中文标题】如何根据动态内容和最大尺寸限制折叠/展开视图?【英文标题】: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 中,因为它更合适。如果信息量大于屏幕会发生什么?使用您的解决方案,即使有效,用户也无法查看所有内容。 感谢您的尝试。我想知道是否有另一种方法可以开发这种设计。 【参考方案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

【讨论】:

以上是关于如何根据动态内容和最大尺寸限制折叠/展开视图?的主要内容,如果未能解决你的问题,请参考以下文章

如何通过单击根据其内容更改表格单元格的高度(折叠和展开)?

动态展开可折叠内容

处理包含能够折叠和展开的子视图的视图

获取height固定折叠元素真实高度方法

如何在 Qt 中制作可展开/可折叠的部分小部件

考虑到动态内容高度,使用 vue 过渡平滑展开/折叠过渡