SwiftUI对列表行的展开和折叠进行动画处理

时间:2019-06-28 07:16:26

标签: swiftui

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

如何获得截面的高度扩展以像使用表视图的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()
        }
    }
}

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

3 个答案:

答案 0 :(得分:3)

由于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

答案 1 :(得分:2)

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

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

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

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

答案 2 :(得分:1)

尝试这样实现:

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)
    }
}