SwiftUI:在列表视图中制作可扩展/可折叠部分



iOS开发中的常见任务是UITableView中的可扩展/折叠部分。 今天,我们正在使用SwiftUI实现此任务。 作为一个小小的转折,在节标题中添加一个动画三角形,并使单元格也展开。

针对macOS Catalina 10.15.1的Xcode 11.2进行了开发

开始项目


启动Xcode,文件-新项目-单视图应用程序。 在对话框中,指定Swift开发语言,我们将使用SwiftUI形成UI。



资料


作为演示数据,我们将使用几种有趣的拉丁语带翅膀的表达方式并将其翻译成俄语。

向项目添加一个新的Swift文件,将其命名为Data.swift并在其中写入以下内容:

struct QuoteDataModel : Identifiable { var id: String { return latin } var latin : String var russian : String var expanded = false } struct SectionDataModel : Identifiable { var id: Character { return letter } var letter : Character var quotes : [QuoteDataModel] var expanded = false } 

QuoteDataModel是单个表达式的模型,将来它将成为每个单个单元格的内容。 我们在其中存储表达式的原始文本,其翻译和“扩展”单元格的符号(默认情况下为“最小化”)

SectionDataModel是每个单独部分的模型,在这里我们存储该部分的“字母”,以该字母开头的引号数组以及“ expanded”部分的符号(默认情况下也为“ collapsed”)

将来,我们将在“列表”视图中显示所有内容,这要求其数据符合“可识别协议”。 为此,我们定义id属性,该属性对于列表中的每个项目都必须是唯一的。

此外,在同一个Data.swift文件中,我们形成数据:

 var latinities : [SectionDataModel] = [ SectionDataModel(letter: "C", quotes: [ QuoteDataModel(latin: "Calvitium non est vitium, sed prudentiae indicium.", russian: "  ,   ."), QuoteDataModel(latin: "Conjecturalem artem esse medicinam.", russian: "   ."), QuoteDataModel(latin: "Crede firmiter et pecca fortiter!", russian: "    !")]), SectionDataModel(letter: "H", quotes: [ QuoteDataModel(latin: "Homo sine religione sicut equus sine freno.", russian: "      ."), QuoteDataModel(latin: "Habet et musca splenem.", russian: "   .")]), SectionDataModel(letter: "M", quotes: [ QuoteDataModel(latin: "Malum est mulier, sed necessarium malum.", russian: "   ,   ."), QuoteDataModel(latin: "Mulierem ornat silentium.", russian: "  .")])] 

让我们来处理界面


现在,我们将确定节标题和每个单元格的外观。

从菜单中选择文件-新建-文件-SwiftUI视图。 将文件命名为HeaderView.swift并将其内容替换为以下内容:

 import SwiftUI struct HeaderView : View { var section : SectionDataModel var body: some View { HStack() { Spacer() Text(String(section.letter)) .font(.largeTitle) .foregroundColor(Color.black) Spacer() } .background(Color.yellow) } } struct HeaderView_Previews: PreviewProvider { static var previews: some View { HeaderView(section: latinities[0]) } } 



现在再次是File-New-File-SwiftUI View。 将文件命名为QuoteView.swift并将其内容替换为以下内容:

 import SwiftUI struct QuoteView: View { var quote : QuoteDataModel var body: some View { VStack(alignment: .leading, spacing: 5) { Text(quote.latin) .font(.title) if quote.expanded { Group() { Divider() Text(quote.russian).font(.body) }.transition(.move(edge: .top)).animation(.default) } } } } struct QuoteView_Previews: PreviewProvider { static var previews: some View { QuoteView(quote: latinities[0].quotes[0]) } } 



现在打开文件ContentView.swift并按如下所示更改ContentView的结构:

 struct ContentView: View { var body: some View { List { ForEach(latinities) { section in Section(header: HeaderView(section: section), footer: EmptyView()) { if section.expanded { ForEach(section.quotes) { quote in QuoteView(quote: quote) } } } } } .listStyle(GroupedListStyle()) } } 

恭喜,您刚刚在列表中填充了最新数据! 对于latinities数组的每个元素我们创建一个节,该节具有基于HeaderView的标题和一个空的页脚。 如果该部分是“展开的”,那么对于quotes数组中的每个表达式,我们将基于QuoteView形成一个单元格。 在我们的数据中,所有节和所有单元格都“折叠”,因此,如果使Canvas可见,您将仅看到节标题:



如您所知,现在该应用程序完全“死了”,距离我们的最终目标还很遥远。 但是很快我们将修复它!

略微修改节标题


回到HeaderView.swift文件。 在HeaderView结构内部,紧接在主体之后,添加以下代码:

 struct Triangle : Shape { func path(in rect: CGRect) -> Path { var path = Path() path.move(to: CGPoint(x: 0, y: 0)) path.addLine(to: CGPoint(x: 0, y: rect.height - 1)) path.addLine(to: CGPoint(x: sqrt(3)*(rect.height)/2, y: rect.height/2)) path.closeSubpath() return path } } 

该结构返回等边三角形。 现在将三角形添加到标题中。 在HStack内部,在第一个Spacer之前添加以下代码:

 Triangle() .fill(Color.black) .overlay( Triangle() .stroke(Color.red, lineWidth: 5) ) .frame(width : 50, height : 50) .padding() .rotationEffect(.degrees(section.expanded ? 90 : 0), anchor: .init(x: 0.5, y: 0.5)).animation(.default)) 



修改资料


回到我们的数据。 打开Data.swift并将latinities数组包装在新的UserData类中,如下所示:

 class UserData : ObservableObject { @Published var latinities : [SectionDataModel] = [ SectionDataModel(letter: "C", quotes: [ QuoteDataModel(latin: "Calvitium non est vitium, sed prudentiae indicium.", russian: "  ,   ."), QuoteDataModel(latin: "Conjecturalem artem esse medicinam.", russian: "   ."), QuoteDataModel(latin: "Crede firmiter et pecca fortiter!", russian: "    !")]), SectionDataModel(letter: "H", quotes: [ QuoteDataModel(latin: "Homo sine religione sicut equus sine freno.", russian: "      ."), QuoteDataModel(latin: "Habet et musca splenem.", russian: "   .")]), SectionDataModel(letter: "M", quotes: [ QuoteDataModel(latin: "Malum est mulier, sed necessarium malum.", russian: "   ,   ."), QuoteDataModel(latin: "Mulierem ornat silentium.", russian: "  .")])] } 

请记住,还应将纬度标记为@Published

我们做了什么?
ObservableObject是数据的特殊对象,可以“绑定”到某些View。 SwiftUI会“监视”所有可能影响View的更改,并在数据更改后更改View。

在“包裹”了拉丁语之后,我们遇到了很多错误,我们将对其进行纠正。 打开HeaderView.swift 并按如下所示纠正HeaderView_Previews的结构:

 struct HeaderView_Previews: PreviewProvider { static var previews: some View { HeaderView(section: UserData().latinities[0]) } } 

现在对QuoteView.swift进行类似的更改:

 struct QuoteView_Previews: PreviewProvider { static var previews: some View { QuoteView(quote: UserData().latinities[0].quotes[0]) } } 

打开ContentView.swift文件,并将其添加到主体声明之前

 @ObservedObject var userData = UserData() 

复兴风景


返回文件ContentView.swift。 在ContenView结构内部,紧随userData定义之后,添加两个函数:

 func sectionIndex(section : SectionDataModel) -> Int { userData.latinities.firstIndex(where: {$0.letter == section.letter})! } func quoteIndex(section : Int, quote : QuoteDataModel) -> Int { return userData.latinities[section].quotes.firstIndex(where: {$0.latin == quote.latin})! } 

onTapGesture修饰符添加到我们正在创建的节标题和单元格中。 身体内容的最终观点:

 var body: some View { List { ForEach(userData.latinities) { section in Section(header: HeaderView(section: section) .onTapGesture { self.userData.latinities[self.sectionIndex(section: section)].expanded.toggle() }, footer: EmptyView()) { if section.expanded { ForEach(section.quotes) { quote in QuoteView(quote: quote) .onTapGesture { let sectionIndex = self.sectionIndex(section: section) let quoteIndex = self.quoteIndex(section: sectionIndex, quote: quote) self.userData.latinities[sectionIndex].quotes[quoteIndex].expanded.toggle() } } } } } } .listStyle(GroupedListStyle()) } 

sectionIndexquoteUndex函数返回传递给它们的节和表达式的索引。 收到这些索引后,我们更改了拉丁语数组中扩展属性的值,这导致该节或表达式的折叠/展开。



结论


完成的项目可以在这里下载

一些有用的链接:



希望该出版物对您有所帮助!

Source: https://habr.com/ru/post/zh-CN475270/


All Articles