SwiftUI 动画列表行的展开和折叠

Posted

技术标签:

【中文标题】SwiftUI 动画列表行的展开和折叠【英文标题】:SwiftUI animating expand and collapse of list rows 【发布时间】:2019-06-28 07:16:26 【问题描述】:

我正在使用 SwiftUI 为列表中的展开和折叠设置动画。

如何使部分的高度扩展像在带有 tableview 的 UIKit 中一样平滑地设置动画?

struct Rows: View 
    let rows = ["Row 1", "Row 2", "Row 3", "Row 4", "Row 5"]

    var body: some View 
        Section 
            ForEach(rows.identified(by: \.self))  name in
                Text(name)
                    .lineLimit(nil)
            
        
    


struct Header: View 

    @State var isExpanded: Bool = false

    var body: some View 

        VStack(alignment: .leading) 
            Button(action: 
                self.isExpanded.toggle()

            ) 
                Text(self.isExpanded ? "Collapse Me" : "Expand Me")
                    .font(.footnote)
            

            if self.isExpanded 
                Rows().animation(.fluidSpring())
            
        
    


struct ContentView : View 

    var body: some View 
        List(0...4)  _ in
            Header()
        
    

动画似乎只适用于行中的文本,而不是实际高度或分隔线为适应新行而增长。行文本似乎也从行的最顶部开始动画,而不是它出现在视图层次结构中的位置。我需要一个流畅的动画。

【问题讨论】:

【参考方案1】:

我是这样实现的:(它带有适当的动画)

struct ExpandCollapseList : View 
    @State var sectionState: [Int: Bool] = [:]

    var body: some View 
        NavigationView 
            List 
                ForEach(1 ... 6, id: \.self)  section in
                    Section(header: Text("Section \(section)").onTapGesture 
                        self.sectionState[section] = !self.isExpanded(section)
                    ) 
                        if self.isExpanded(section) 
                            ForEach(1 ... 4, id: \.self)  row in
                                Text("Row \(row)")
                            
                        
                    
                
            
            .navigationBarTitle(Text("Expand/Collapse List"))
            .listStyle(GroupedListStyle())
        
    

    func isExpanded(_ section: Int) -> Bool 
        sectionState[section] ?? false
    

【讨论】:

这不再适用于 Xcode 11 GM。更改它以修复它:tapAction -> onTapGesture forEach(1...6) -> forEach(1...6, id: \.self) .listStyle(.grouped) -> .listStyle(GroupedListStyle()) 当我折叠一个部分时,列表会将其滚动位置重置为初始状态。例如,当我折叠列表中的最后一个部分时,列表会向后滚动并显示前几个部分。在更新列表时保留滚动位置的任何想法?【参考方案2】:

感谢Aakash Jaiswal's answer,我能够扩展此实现以满足我扩展到三层的需要,即部分、子部分和课程。编译器无法在单个 View 中编译整个实现,这就是我将其分开的原因。

import SwiftUI

struct MenuView: View 
    var body: some View 
        HStack 
            List 
                ToggleableMenuItemsView(sections: menuItems)
                .padding()
            
        
        .background(Color("Gray"))
        .cornerRadius(30)
        .padding(.top, 30)
        .padding(.trailing, bounds.width * 0.2)
        .padding(.bottom, 30)
        .shadow(radius: 10)
    

    @State var menuItemState = [String: Bool]()
    private var bounds: CGRect  UIScreen.main.bounds 

    private func isExpanded(_ menuItem: MenuItem) -> Bool 
        menuItemState[menuItem.id] ?? false
    


struct ToggleableMenuItemsView: View 
    let sections: [MenuItem]

    var body: some View 
        ForEach(sections)  section in
            Section(
                header: Text(section.title)
                    .font(.title)
                    .onTapGesture  self.menuItemState[section.id] = !self.isExpanded(section) ,
                content: 
                    if self.isExpanded(section) 
                        ForEach(section.children)  subsection in
                            Section(
                                header: Text(subsection.title)
                                    .font(.headline)
                                    .onTapGesture  self.menuItemState[subsection.id] = !self.isExpanded(subsection) ,
                                content: 
                                    if self.isExpanded(subsection) 
                                        LessonsListView(lessons: subsection.children)
                                    
                                
                            )
                        
                    
                
            )
        
    

    @State var menuItemState = [String: Bool]()

    private func isExpanded(_ menuItem: MenuItem) -> Bool 
        menuItemState[menuItem.id] ?? false
    


struct LessonsListView: View 
    let lessons: [MenuItem]

    var body: some View 
        ForEach(lessons)  lesson in
            Text(lesson.title)
                .font(.subheadline)
        
    


class MenuItem: Identifiable 
    var id: String
    let title: String
    var children: [MenuItem]

    init(id: String, title: String, children: [MenuItem] = []) 
        self.id = id
        self.title = title
        self.children = children
    


let menuItems = [
    MenuItem(
        id: "01",
        title: "The Land in its World",
        children: [
            MenuItem(
                id: "01A",
                title: "North and South",
                children: [
                    MenuItem(
                        id: "01A01",
                        title: "Between Continents"
                    ),
                    MenuItem(
                        id: "01A02",
                        title: "The Wet North"
                    ),
                    MenuItem(
                        id: "01A03",
                        title: "The Dry South"
                    ),
                    MenuItem(
                        id: "01A04",
                        title: "Between Wet and Dry"
                    )
                ]
            ),
            MenuItem(
                id: "01B",
                title: "East and West",
                children: [
                    MenuItem(
                        id: "01B01",
                        title: "Sea and Desert"
                    ),
                    MenuItem(
                        id: "01B02",
                        title: "Exchange in Aram"
                    ),
                    MenuItem(
                        id: "01B03",
                        title: "Exchange in Egypt"
                    ),
                    MenuItem(
                        id: "01B04",
                        title: "A Bypass Between"
                    )
                ]
            ),
            MenuItem(
                id: "01C",
                title: "Between Empires",
                children: [
                    MenuItem(
                        id: "01C01",
                        title: "Imperial Dreams"
                    ),
                    MenuItem(
                        id: "01C02",
                        title: "Egypt Marches"
                    ),
                    MenuItem(
                        id: "01C03",
                        title: "Taking Egypt's Wealth"
                    ),
                    MenuItem(
                        id: "01C04",
                        title: "The Land Between"
                    )
                ]
            )
        ]
    )
]

struct MenuView_Previews: PreviewProvider 
    static var previews: some View 
        MenuView()
    

Here's a demo

【讨论】:

【参考方案3】:

尝试像这样实现它:

struct ContentView : View 

    @State var expanded:[Int:Bool] = [:]

    func isExpanded(_ id:Int) -> Bool 
        expanded[id] ?? false
    

    var body: some View 
        NavigationView
            List 
                ForEach(0...80)  section in
                    Section(header: CustomeHeader(name: "Section \(section)", color: Color.white).tapAction 
                        self.expanded[section] = !self.isExpanded(section)
                    ) 
                        if self.isExpanded(section) 
                            ForEach(0...30)  row in
                                Text("Row \(row)")
                            
                        
                    
                
            
        .navigationBarTitle(Text("Title"))
    


struct CustomeHeader: View 
    let name: String
    let color: Color

    var body: some View 
        VStack 
            Spacer()
            HStack 
                Text(name)
                Spacer()
            
            Spacer()
            Divider()
        
        .padding(0)
            .background(color.relativeWidth(1.3))
            .frame(height: 50)
    

【讨论】:

以上是关于SwiftUI 动画列表行的展开和折叠的主要内容,如果未能解决你的问题,请参考以下文章

水平 ScrollView (SwiftUI) 中的动画展开/折叠组

SwiftUI 中列表行(List Row)展开和收起无动画或动画诡异的解决

SwiftUI 中列表行(List Row)展开和收起无动画或动画诡异的解决

一起为多个列表项设置动画(展开/折叠)

React:如何在内容大小未知时为展开和折叠 Div 设置动画

展开/折叠棒棒糖工具栏动画(电报应用程序)