swift和swiftui_在SwiftUI 2.0中使用OutlineGroup和DisclosureGroup构建可扩展列表
swift和swiftui
Building an expandable list with nested items is quite a complex and error prone task to implement when using UITableView
in UIKit
. Luckily with SwiftUI 2.0, Apple introduced OutlineGroup
and DisclosureGroup
. With the views in our arsenal, we can build a hierarchical expandable and collapsible list with minimal lines of code using declarative syntax and reactive state management.
在UIKit
使用UITableView
,要实现一个带有嵌套项目的可扩展列表,这是一项非常复杂且容易出错的任务。 幸运的是,借助SwiftUI 2.0,Apple引入了OutlineGroup
和DisclosureGroup
。 利用我们的工具库中的视图,我们可以使用声明性语法和React性状态管理,以最少的代码行构建层次结构可扩展和可折叠的列表。
我们将建立什么 (What We Will Build)
We’re going to explore about OutlineGroup
and DisclosureGroup
, and how we can use them in practice to build List
that represent hierarchical data in the UI by building three different kind of screens:
我们将探讨OutlineGroup
和DisclosureGroup
,以及如何在实践中使用它们通过构建三种不同类型的屏幕来构建表示UI中分层数据的List
:
-
Using
List
to render hierarchical data.使用
List
呈现层次结构数据。 -
Using
OutlineGroup
in List to handle multiple kind of views.在List中使用
OutlineGroup
处理多种视图。 -
Using
DisclosureGroup
for expandable/collapsible behavior to group of views with state binding.使用
DisclosureGroup
的可扩展/可折叠行为将视图绑定到具有状态绑定的视图组。
You can download the completed project from the GitHub Repository.
您可以从GitHub Repository下载完成的项目。
You can also watch the full video tutorial from YouTube.
您也可以观看YouTube上的完整****。
使用列表呈现层次结构数据 (Using List to Render Hierarchical Data)
Let’s move on to the first example, which is showing list of items that have nested items inside of them.
让我们继续第一个示例,该示例显示其中包含嵌套项目的项目列表。
By looking at the screenshoot above, we can see that at the root level, we have items such as Computers, Smartphones, Tablets, and, Wearables.
通过查看上面的屏幕快照,我们可以看到在根级别上我们有诸如计算机,智能手机,平板电脑和可穿戴设备之类的项目。
Inside the Computers, we have desktop and laptops. Inside the desktops, we have the children such as iMac, Mac Mini, Mac Pro. For the children of the Laptops, we have MacBook Pro, MacBook Air, and MacBook Pro. In this case, the depth of the Computers category is 3.
在计算机内部,我们有台式机和笔记本电脑。 在台式机内部,我们拥有诸如iMac,Mac Mini,Mac Pro之类的子级。 对于笔记本电脑的孩子,我们提供MacBook Pro,MacBook Air和MacBook Pro。 在这种情况下,“计算机”类别的深度为3。
Inside the smartphones, we have the children such as iPhone 11, iPhone XS, iPhone XR, iPhone X, iPhone SE. In this case the depth of the Smartphones category is 2.
在智能手机内部,我们有孩子,例如iPhone 11,iPhone XS,iPhone XR,iPhone X,iPhone SE。 在这种情况下,“智能手机”类别的深度为2。
Let’s dive into Xcode and learn how SwiftUI can help us to build this UI with very minimal lines of code. Create a new Xcode Project, give it any name that you prefer to.
让我们深入研究Xcode,了解SwiftUI如何可以帮助我们以最少的代码行来构建此UI。 创建一个新的Xcode项目,为其提供任何您喜欢的名称。
创建物品模型 (Create Item Model)
struct Item: Identifiable {
let id = UUID()
let title: String
let children: [Item]?
}
First, we’ll create a model to represent the item in the list. Create a new file named Item.swift
. Declare the Item as a struct
that conforms to Identifiable
. Let’s declare the id
property to satisfy the protocol requirement
. We’ll use UUID
as the type of the id
as well as assigning it with default value. When we initialize UUID
instance using the default initializer
, the value will be unique
.
首先,我们将创建一个模型来表示列表中的项目。 创建一个名为Item.swift
的新文件。 将Item声明为符合Identifiable
的struct
。 让我们声明id
属性以满足protocol requirement
。 我们将使用UUID
作为id
的类型,并为其分配默认值。 当我们使用默认的initializer
程序初始化UUID
实例时,该值将是unique
。
Next, let’s declare the title
property with type of String
, we’ll use this to render the Text
. Continuing on, to represent the children
in the model, we need to declare a property containing the Array of the Item
as this will be used by SwiftUI to determine whether the current item has children in the hierarchy. Let’s declare and named it as children
.
接下来,让我们声明String
类型的title
属性,我们将使用它来呈现Text
。 继续,以表示模型中的children
,我们需要声明一个包含Array of the Item
属性,因为SwiftUI将使用该属性来确定当前项目在层次结构中是否具有子代。 让我们声明并将其命名为children
。
创建ItemList UI (Create ItemList UI)
Let’s move on to build the View. Create a new file named ItemList.swift
. Declare an instance property named items
which is an array of Item
. Inside the body
implementation, we just need to initialize List
passing the items
. To enable the nesting
of the children
, we need to pass the keypath
property name that contains the array of the Item
to the children parameter
. In our case, we pass the \.children
as the keypath
. Inside the view builder closure, we just need to render the Item
inside the text using the title
property.
让我们继续构建视图。 创建一个名为ItemList.swift
的新文件。 声明一个名为items
的实例属性,该属性是array of Item
的array of Item
。 在body
实现内部,我们只需要初始化List
并传递items
。 为了使nesting
了的children
,我们需要通过keypath
包含数组属性名Item
给children parameter
。 在本例中,我们将\.children
keypath
作为keypath
。 在视图构建器闭包内部,我们只需要使用title
属性在文本内呈现Item
。
Before we can preview the UI, we need to inject
the Stub
data into the preview. At the bottom of the source file, create an extension
for the Item
to help us stub the model. Declare the static constant stubs
with type of Item Array
. The first item will be computers, it has 2 children, desktops, and laptops. The desktops has 3 children: iMac, Mac Pro, and Mac Mini. The laptops has 2 children: MacBook Pro and MacBook Air. Let’s try this first, we just need to pass this when initializing the ItemList
inside the preview like so.
在预览UI之前,我们需要inject
Stub
数据注入到预览中。 在源文件的底部,为Item
创建extension
,以帮助我们对模型进行存根。 用Item Array
类型声明static constant stubs
。 第一项是计算机,它有2个孩子,台式机和笔记本电脑。 台式机有3个子级:iMac,Mac Pro和Mac Mini。 笔记本电脑有2个孩子:MacBook Pro和MacBook Air。 让我们先尝试一下,像这样在预览中初始化ItemList
时只需传递它。
struct ItemList_Previews: PreviewProvider {
static var previews: some View {
ItemList(items: Item.stubs)
}
}
The computers is shown in the preview with a disclosure indicator. To enable interaction in the live preview, make sure to press on the play button. Click on the indicator to make it expands to show the desktops and laptops. Try to also expand the desktops and laptops. As you can see with only 3 lines of UI related code, we’re able to show hierarchical nested data
inside our SwiftUI list! It works recursively
until the item has no more children
.
预览中显示了带有公开指示符的计算机。 要在实时预览中启用交互,请确保按下播放按钮。 单击指示器使其展开以显示台式机和笔记本电脑。 尝试同时扩展台式机和笔记本电脑。 如您所见,只有3行与UI相关的代码,我们能够在SwiftUI列表中显示hierarchical nested data
! 它recursively
起作用,直到该项目no more children
。
在列表中对多个视图使用OutlineGroup (Using OutlineGroup in List for Multiple Kind of Views)
You might be thinking, how can we display different kind of views and data in the list. No worries, we can use the new OutlineGroup
to handle this scenario. Let’s take a look at the second screen we’ll build!
您可能在想,我们如何在列表中显示不同类型的视图和数据。 不用担心,我们可以使用新的OutlineGroup
来处理这种情况。 让我们看一下我们要构建的第二个屏幕!
We have a Sidebar List
containing menu items. At the top we, have the home
menu, then at the middle section, we have the hierarchical items we have created before, finally at the bottom, we have the settings section which is also expandable
containing the Account
, Help
, and Logout
menu.
我们有一个包含菜单项的Sidebar List
。 在顶部,有home
菜单,然后在中间部分,有我们之前创建的分层项目,最后在底部,有设置部分,该部分也可以expandable
包含Account
, Help
和Logout
菜单。
Let’s create a new SwiftUI view named SidebarList.swift
. Declare an instance property named items
which is an array of Item
model. Let’s pass the stub items in the preview class to the initializer and activate the live preview.
让我们创建一个名为SidebarList.swift
的新SwiftUI视图。 声明一个名为items
的实例属性,该属性是Item
模型的数组。 让我们将Preview类中的存根项目传递给初始化程序并**实时预览。
In the body
implementation, declare an empty List
, also add a ListStyle
modifier passing the newly available SidebarListStyle
. This style is suitable for sidebar list of menus especially in iPad.
在body
实现中,声明一个空的List
,还添加一个ListStyle
修饰符,以传递新可用的SidebarListStyle
。 此样式特别适用于iPad的侧边栏菜单列表。
At the top of view builder closure, declare a Label
passing Home
as the `title` and house
as the systemImage
. Label
is a new view in SwiftUI 2.0 that renders a text as the leading item and a SF Symbol Image as the trailing item.
在视图构建器关闭的顶部,声明一个将Home
传递为Label
将house
传递给systemImage
。 Label
是SwiftUI 2.0中的一个新视图,该视图将文本作为前导项目,将SF符号图像作为尾随项目。
To render our hierarchical items, we can use the OutlineGroup
passing the array of items
and keypath of the children
property containing the array of the items
. In the view builder closure, we can just render a text using the title of the item. Using the live preview, try to expand and collapse the items from the live preview to make sure it works.
要渲染层次项,我们可以使用OutlineGroup
传递array of items
和包含array of the items
keypath of the children
属性的keypath of the children
。 在视图构建器关闭中,我们可以使用项目的标题来呈现文本。 使用实时预览,尝试扩展和折叠实时预览中的项目以确保其有效。
Let’s move to the bottom section. By using SidebarListStyle
as the ListStyle
, we can use the Section
View, this will add the expand and collapse behavior automatically
for the views inside.
让我们转到底部。 通过使用SidebarListStyle
作为ListStyle
,我们可以使用Section
View,这将automatically
为内部视图添加展开和折叠行为。
Let’s implement this, declare a Section
, then pass the Text
with Setting string as the Header. In the view builder, declare the 3 labels for account, help, and logout. Finally, add the Divider
between each section.
让我们实现它,声明一个Section
,然后传递带有Setting字符串的Text
作为Header。 在视图构建器中,为帐户,帮助和注销声明3个标签。 最后,在每个部分之间添加Divider
。
Run the live preview, the settings section now provides the disclosure indicator where we can use it to expand or collapse the section. Awesome!
运行实时预览,设置部分现在提供了公开指示器,我们可以在其中使用它来展开或折叠该部分。 太棒了!
使用DisclosureGroup的可扩展/可折叠行为对具有状态绑定的视图组 (Using DisclosureGroup for Expandable/Collapsible Behavior to Group of Views with State Binding)
Last, i want to show you the DisclosureGroup
view which we can use to add expand and collapse behavior for a group of views within the same hierarchy. Let’s take a look at the screenshoot below.
最后,我想向您展示DisclosureGroup
视图,我们可以使用该视图为同一层次结构中的一组视图添加展开和折叠行为。 让我们看一下下面的屏幕截图。
At the top of the Form, we have the personal information section containing textfields
for names and email. Then, we have a datepicker
for birthday. The section within the form can be collapsed and expanded, and the default state
for the personal info section is expanded.
在表格顶部,我们有个人信息部分,其中包含姓名和电子邮件的textfields
。 然后,我们有一个生日的datepicker
。 表单中的部分可以折叠和展开,个人信息部分的default state
也可以展开。
In the next section, we have a preferences notification section. It has three toggles
where user can opt-in to receive notifications via email, sms, and push notification. The default state
for the section is collapsed.
在下一部分中,我们有一个首选项通知部分。 它具有三个toggles
按钮,用户可以在其中选择通过电子邮件,短信和推送通知接收通知。 该部分的default state
为折叠状态。
Let’s go back to Xcode and implement the form using DisclosureGroup
. Create a new SwiftUI file named FormList.swift
.
让我们回到Xcode并使用DisclosureGroup
实现表单。 创建一个名为FormList.swift
的新SwiftUI文件。
In the body
implementation, declare Form
as the root view. Inside the view builder, declare a DisclosureGroup
. Inside view builder, the Let’s declare the texfields
and DatePicker
. For the simplicity of this example, i just passed an inline constant
as the binding
instead of passing state properties. Let’s set the label parameter
with Text
passing Personal Information string. This syntax is part of the multiple trailing closure
feature of Swift 5.3.
在body
实现中,将Form
声明为根视图。 在视图构建器内部,声明DisclosureGroup
。 在视图构建器内部,让我们声明texfields
和DatePicker
。 为了简化本示例,我只是传递了一个inline constant
作为binding
而不传递状态属性。 让我们通过传递个人信息字符串的Text
设置label parameter
。 此语法是Swift 5.3的multiple trailing closure
功能的一部分。
Let’s see the result in the live preview by clicking on the disclosure indicator to expand and collapse the section.
通过单击公开指示器以展开和折叠该部分,让我们在实时预览中查看结果。
Next, let’s declare the notification preferences section. Declare a DisclosureGroup
. Inside the view builder, declare the three toggles
. For the label
, just pass the Text
containing the notification preferences setting.
接下来,让我们声明“通知首选项”部分。 声明一个DisclosureGroup
。 在视图构建器内部,声明three toggles
。 对于label
,只需传递包含通知首选项设置的Text
。
To control the expand and collapse state of a DisclosureGroup
manually
, we can pass a binding containing a boolean
. Let’s declare a state property isProfileSectionExpanded
and assign true
as the default value
. On the Profile DisclosureGroup
, we can `pass the binding of the state to the isExpanded parameter
.
要manually
控制DisclosureGroup
的展开和折叠状态,我们可以pass a binding containing a boolean
。 让我们声明一个状态属性isProfileSectionExpanded
并将true
分配为default value
。 在Profile DisclosureGroup
,我们可以将状态绑定传递给isExpanded parameter
。
Let’s rebuild the app and run the live preview. As we can see, the profile section has an expanded state as the default behavior.
让我们重建应用程序并运行实时预览。 如我们所见,概要文件部分具有扩展状态作为默认行为。
结论 (Conclusion)
That’s it for this quick and practical example of how we can build a List with hierarchical data using OutlineGroup and Disclosure Group.
就是这个快速而实际的示例,它说明了我们如何使用OutlineGroup和Disclosure Group使用分层数据构建List。
You can watch the related WWDC 2020 session to learn more about how the view work. You will be amazed that in at implementation level, Apple basically used DisclosureGroup and OutlineGroup recursively to enable the nesting for List and OutlineGroup!
您可以观看相关的WWDC 2020会议,以了解有关该视图如何工作的更多信息。 您会惊奇地发现,在实现级别,Apple基本上递归地使用DisclosureGroup和OutlineGroup为List和OutlineGroup启用嵌套!
Until the next one, lets keep the lifelong learning goes on!
直到下一个,让我们继续进行终身学习!
swift和swiftui