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)展开和收起无动画或动画诡异的解决