swift和swiftui

您一直在等待的完整SwiftUI 2文档 (The Complete SwiftUI 2 Documentation You’ve Been Waiting For)

At the start of 2020, I wrote a long Medium article called “The Complete SwiftUI Documentation You’ve Been Waiting For.”

在2020年初,我写了一篇很长的中型文章,名为“ 您一直在等待的完整SwiftUI文档 。”

This was my way of sharing what I learned when I tried to fill in the gaps left by the insufficient documentation provided by Apple.

这是我分享我尝试填补Apple提供的文档不足所留下的空白时所学到的知识的方式。

Although my article seemed to help a lot of people, I also wrote it eight months late.

尽管我的文章似乎对很多人有帮助,但我也写了八个月。

Now that Apple’s 2020 developer conference is over, SwiftUI has been given some new capabilities, so hopefully, this update will make my documentation more helpful than ever before. This will be released as a series, with one chapter per article. The names of these chapters correspond with the chapter names in Apple’s SwiftUI documentation. They can be read in any order, so that’s why I’m not numbering them.

现在,Apple的2020年开发人员大会已经结束,SwiftUI已获得一些新功能,因此希望此更新将使我的文档比以往任何时候都更有帮助。 这将作为系列发布,每篇文章一个章节。 这些章节的名称与Apple的SwiftUI文档中的章节名称相对应。 可以按任何顺序读取它们,所以这就是为什么我不给它们编号。

As I promised, the current chapter isn’t as long as “Views and Controls,” which was longer than my original documentation!

如我所言,本章不如“视图和控件”长,它比我的原始文档还要长!

  • View Layout and Presentation

    查看布局和演示

  • Views and Controls

    视图和控件

  • App Structure and Behavior
    应用结构和行为
  • Drawing and Animation
    绘画与动画
  • Framework Integration
    框架整合
  • State and Data Flow
    状态和数据流
  • Gestures
    手势
  • Preview
    预习

I encourage you to contact me in a response below if you spot any mistakes or a subject you think I should cover in more detail.

如果您发现任何错误或您认为我应该更详细介绍的主题,建议您在以下答复中与我联系。

LazyHStack and LazyVStack (New in 2.0)LazyVGrid (New in 2.0)LazyHGrid (New in 2.0)GridItem (New in 2.0)List (Updated in 2.0)ForEach & DynamicViewContent (Updated in 2.0)ScrollViewReader (New in 2.0)ScrollViewProxy (New in 2.0)Group (Updated in 2.0)Groupbox (Updated), OutlineGroup (NEW), & DisclosureGroup (NEW)NavigationView (Updated in 2.0)TabView (Updated in 2.0)

LazyHStack和LazyVStack(2.0中的新增功能) (LazyHStack and LazyVStack (New in 2.0))

Back to contents ↑

返回目录↑

One thing that was pretty ambiguous in the first iteration of SwiftUI was whether the rows of a List are queued or not.

在SwiftUI的第一次迭代中,一个很不明确的事情是List的行是否排队。

When you scroll on a UITableView, cells that leave the bottom or top of the screen are added to a queue, meaning that every cell in the table does not have to be stored in memory at once. When a cell is about to be scrolled into view, a method like func dequeueReusableCell(withIdentifier: String) -> UITableViewCell? is called. The cells are considered to be reusable, since they can be destroyed and recreated, and removing them from the queue is called dequeueing.

UITableView上滚动时,离开屏幕底部或顶部的单元格将添加到队列中,这意味着表中的每个单元格不必一次存储在内存中。 当一个单元格要滚动到视图中时,类似func dequeueReusableCell(withIdentifier: String) -> UITableViewCell? 叫做。 单元被认为是可重用的,因为它们可以被销毁和重新创建,并且将它们从队列中删除称为出队

Anyway, it turns out that List does reuse cells. But if you want to use a ScrollView instead, you’re back to everything loading at once and not queuing when they leave the top or bottom of the screen. You might be okay with using List instead of a verticalScrollView, but what happens if you want to scroll horizontally?

无论如何,事实证明List 确实重用了cell 。 但是,如果您要使用ScrollView ,则可以返回到一次加载的所有内容,并且当它们离开屏幕顶部或底部时不会排队。 使用List而不是垂直的ScrollView可能会没事,但是如果要水平滚动会怎样?

If you try it, you’ll notice that List has no option to scroll horizontally.

如果尝试,您会发现List没有水平滚动的选项。

struct ContentView: View {@State var text = ""var body: some View {VStack {WhatJustHappenedView(text: text)ScrollView(.horizontal) {MyLazyHStack(text: $text)}.frame(height: 50)ScrollView(.vertical) {MyLazyVStack(text: $text)}}}
}struct WhatJustHappenedView: View {let text: String@State var toggleIsOn = truevar body: some View {Group {Toggle(isOn: $toggleIsOn) {Text("Show what just happened")}.padding(.top)if toggleIsOn {Text("What just happened?")Text("\(text)")}}.padding(.horizontal)}
}struct MyLazyHStack: View {@Binding var text: Stringvar body: some View {LazyHStack {ForEach(0..<150, id: \.self) {index inText("LazyHStack \(index)").onAppear {text = "LazyHStack \(index) appeared"print(text)}.onDisappear {text = "LazyHStack \(index) disappeared"print(text)}}}}
}struct MyLazyVStack: View {@Binding var text: Stringvar body: some View {LazyVStack {ForEach(0..<150, id: \.self) {index inText("LazyVStack \(index)").onAppear {text = "LazyVStack \(index) appeared"print(text)}.onDisappear {text = "LazyVStack \(index) disappeared"print(text)}}}}
}

In my example, we have an aptly named WhatJustHappenedView, which prints the most recent queueing event. If the stacks weren’t lazy, every Text cell inside them would appear once at the beginning, and they would never disappear when they are queued.

在我的示例中,我们有一个恰当地命名为WhatJustHappenedView ,它打印最近的排队事件。 如果堆栈不是惰性的,则其中的每个Text单元格将在开始时出现一次,并且在排队时它们永远不会消失。

Instead, we see the events that prove that our memory is being allocated dynamically and not all at once.

相反,我们看到的事件证明了我们的内存是动态分配的,而不是一次分配。

LazyVGrid(2.0中的新增功能) (LazyVGrid (New in 2.0))

Back to contents ↑

返回目录↑

You can apply the same logic of the LazyVStack and LazyHStack section above to a grid. What if we want to lay views out in rows and columns in SwiftUI? In the original version, there was no way to go about this other than manually coding your own logic, of course! The LazyVGrid bears a lot of visual similarity to the UICollectionView from UIKit, but it’s a lot easier to implement. You can construct these grids using an array of GridItem objects, which can act as rows in your layout.

您可以将上面的LazyVStackLazyHStack部分的相同逻辑应用于网格。 如果我们想在SwiftUI中按行和列布局视图怎么办? 当然,在原始版本中,除了手动编码自己的逻辑外,别无他法! UICollectionView与UIKit中的LazyVGrid具有很多视觉相似性,但是实现起来容易UICollectionView 。 您可以使用GridItem对象数组构造这些网格,这些对象可以在布局中充当行。

To make it easier to see the effect of changing properties of your grids, I’ve created a convenient way to lay out six steppers called SteppersView. I’m going to be providing examples that are sized using .fixed, .adaptive, and .relative sizing types. These are all cases of the enum GridItem.Size, and while .fixed requires only one CGFloat value, the other two require a minimum and maximum for the system to choose a value between.

为了更轻松地查看更改网格属性的效果,我创建了一种方便的方法来布置六个名为SteppersView的步进器。 我将要提供使用大小的例子.fixed.adaptive.relative大小类型。 这些都是枚举GridItem.Size所有情况,而.fixed仅需要一个CGFloat值,而其他两个则需要最小值和最大值,系统才能在其中选择一个值。

First we have .fixed, which gives an explicit width to the columns of the LazyVGrid:

首先,我们有.fixed ,这给出了一个明确的宽度到的列LazyVGrid

// Requires SteppersView which can be found here:
// https://gist.github.com/sturdysturge/eed04e007cef3222729663d9eed0d7d6import SwiftUIstruct ContentView: View {@State var column1Width: CGFloat = 20.0@State var column2Width: CGFloat = 20.0@State var column3Width: CGFloat = 20.0@State var column1Spacing: CGFloat = 50.0@State var column2Spacing: CGFloat = 50.0@State var column3Spacing: CGFloat = 50.0let rows = 50let columns = 3var body: some View {VStack {SteppersView(control1A: ("Column 1 Width", $column1Width),control1B: ("Column 1 Spacing", $column1Spacing),control2A: ("Column 2 Width", $column2Width),control2B: ("Column 2 Spacing", $column2Spacing),control3A: ("Column 3 Width", $column3Width),control3B: ("Column 3 Spacing", $column3Spacing))ScrollView(.vertical) {LazyVGrid(columns: [GridItem(.fixed(column1Width), spacing: column1Spacing),GridItem(.fixed(column2Width), spacing: column2Spacing),GridItem(.fixed(column3Width), spacing: column3Spacing)], alignment: .center, spacing: 19) {ForEach(0..<(columns * rows), id: \.self) {index inRectangle().foregroundColor(.red).frame(height: 25)}}}.frame(maxWidth: .infinity).padding(.vertical)Spacer()}}
}

Now we have .flexible, which allows the columns to grow to the maximum width they have available. This is similar to using the .frame(maxWidth: .infinity) modifier on any other view. Although columns can grow or shrink according to the requirements of those around them, they cannot change the number of columns in a row. This means that we still end up with an appropriate number of rows, as is seen if you scroll to the bottom and see that the bottom row has the same number as all previous rows.

现在我们有了.flexible ,它可以使列增长到可用的最大宽度。 这类似于在其他任何视图上使用.frame(maxWidth: .infinity)修饰符。 尽管可以根据周围的列的要求来增加或缩小列,但是它们不能更改一行中的列数。 这意味着我们仍然可以得到适当数量的行,就像您滚动到底部并看到最底部的行与所有先前的行具有相同的行数一样。

// Requires SteppersView which can be found here:
// https://gist.github.com/sturdysturge/eed04e007cef3222729663d9eed0d7d6import SwiftUIstruct LazyVGridFlexibleView: View {@State var column1MinWidth: CGFloat = 50.0@State var column2MinWidth: CGFloat = 50.0@State var column3MinWidth: CGFloat = 50.0@State var column1MaxWidth: CGFloat = 50.0@State var column2MaxWidth: CGFloat = 50.0@State var column3MaxWidth: CGFloat = 50.0let rows = 50let columns = 3var body: some View {VStack {SteppersView(control1A: ("Column 1 Min Width", $column1MinWidth),control1B: ("Column 1 Max Width", $column1MaxWidth),control2A: ("Column 2 Min Width", $column2MinWidth),control2B: ("Column 2 Max Width", $column2MaxWidth),control3A: ("Column 3 Min Width", $column3MinWidth),control3B: ("Column 3 Max Width", $column3MaxWidth))ScrollView(.vertical) {LazyVGrid(columns: [GridItem(.flexible(minimum: column1MinWidth, maximum: column1MaxWidth)),GridItem(.flexible(minimum: column2MinWidth, maximum: column2MaxWidth)),GridItem(.flexible(minimum: column3MinWidth, maximum: column3MaxWidth)),]) {ForEach(0..<(columns * rows), id: \.self) {index inRectangle().foregroundColor(.red).frame(height: 25)}}}.frame(maxWidth: .infinity).padding(.vertical)Spacer()}}
}

GridItem.Size.adaptive is different from .flexible in one simple way. While these cells still have a minimum and maximum width, they will not prevent cells from the row below moving up in order to occupy available space. This is assuming that the available space is larger than the minimum width that the cells can occupy, of course. The difference here can be observed most clearly when scrolling to the bottom, as it is easy to achieve a situation in which the last row has less cells in it than the previous rows.

GridItem.Size.adaptive.flexible以一种简单的方式不同。 尽管这些单元格仍具有最小和最大宽度,但它们不会阻止下一行的单元格向上移动以占用可用空间。 当然,这是假定可用空间大于单元格可以占用的最小宽度。 滚动到底部时,可以最清楚地观察到此处的差异,因为很容易实现最后一行中的单元格少于前一行的情况。

This is because the number of cells we calculated using columns * rows is no longer an accurate representation of the cells, as there are more items per row than previously expected.

这是因为我们使用columns * rows计算的单元格数量不再是单元格的准确表示,因为每行中的项目比以前预期的要多。

// Requires SteppersView which can be found here:
// https://gist.github.com/sturdysturge/eed04e007cef3222729663d9eed0d7d6import SwiftUIstruct ContentView: View {@State var column1MinWidth: CGFloat = 50.0@State var column2MinWidth: CGFloat = 50.0@State var column3MinWidth: CGFloat = 50.0@State var column1MaxWidth: CGFloat = 50.0@State var column2MaxWidth: CGFloat = 50.0@State var column3MaxWidth: CGFloat = 50.0let rows = 50let columns = 3var body: some View {VStack {SteppersView(control1A: ("Column 1 Min Width", $column1MinWidth),control1B: ("Column 1 Max Width", $column1MaxWidth),control2A: ("Column 2 Min Width", $column2MinWidth),control2B: ("Column 2 Max Width", $column2MaxWidth),control3A: ("Column 3 Min Width", $column3MinWidth),control3B: ("Column 3 Max Width", $column3MaxWidth))ScrollView(.vertical) {LazyVGrid(columns: [GridItem(.adaptive(minimum: column1MinWidth, maximum: column1MaxWidth)),GridItem(.adaptive(minimum: column2MinWidth, maximum: column2MaxWidth)),GridItem(.adaptive(minimum: column3MinWidth, maximum: column3MaxWidth)),]) {ForEach(0..<(columns * rows), id: \.self) {index inRectangle().foregroundColor(.red).frame(height: 25)}}}.frame(maxWidth: .infinity).padding(.vertical)Spacer()}}
}

LazyHGrid(2.0中的新增功能) (LazyHGrid (New in 2.0))

Back to contents ↑

返回目录↑

Like LazyVGrid above, the examples here require controls so that you can play around with them in the subsequent examples. All of the examples use six different steppers, so I’ve provided SteppersView, which allows you to lay them out for each example.

与上面的LazyVGrid一样,此处的示例也需要控件,以便您可以在后续示例中使用它们。 所有示例都使用六个不同的步进器,因此我提供了SteppersView ,它允许您为每个示例布置它们。

// Requires StepperView which can be found here:
// https://gist.github.com/sturdysturge/eed04e007cef3222729663d9eed0d7d6import SwiftUIstruct LazyHGridFixedView: View {@State var row1Height: CGFloat = 20.0@State var row2Height: CGFloat = 20.0@State var row3Height: CGFloat = 20.0@State var row1Spacing: CGFloat = 50.0@State var row2Spacing: CGFloat = 50.0@State var row3Spacing: CGFloat = 50.0let columns = 50let rows = 3var body: some View {VStack {SteppersView(control1A: ("Row 1 Height", $row1Height),control1B: ("Row 1 Spacing", $row1Spacing),control2A: ("Row 2 Height", $row2Height),control2B: ("Row 2 Spacing", $row2Spacing),control3A: ("Row 3 Height", $row3Height),control3B: ("Row 3 Spacing", $row3Spacing))ScrollView(.horizontal) {LazyHGrid(rows: [GridItem(.fixed(row1Height), spacing: row1Spacing),GridItem(.fixed(row2Height), spacing: row2Spacing),GridItem(.fixed(row3Height), spacing: row3Spacing)], alignment: .center, spacing: 19) {ForEach(0..<(columns * rows), id: \.self) {index inRectangle().foregroundColor(.red).frame(width: 25)}}}.frame(maxHeight: .infinity).padding(.vertical)Spacer()}}
}

Now we have .flexible, which allows the rows to grow to the maximum height they have available. This is similar to using the .frame(maxHeight: .infinity) modifier on any other view. Although columns can grow or shrink according to the requirements of those around them, they cannot change the number of rows in a column. This means that we still end up with an appropriate number of columns, as is seen if you scroll to the right and see that the last column has the same number as all previous columns.

现在我们有了.flexible ,它可以使行增长到可用的最大高度。 这类似于在其他任何视图上使用.frame(maxHeight: .infinity)修饰符。 尽管可以根据周围的列的要求来增加或缩小列,但是它们不能更改列中的行数。 这意味着我们仍然可以得到适当数量的列,就像您向右滚动并看到最后一列具有与所有先前列相同的列数一样。

// Requires SteppersView which can be found here:
// https://gist.github.com/sturdysturge/eed04e007cef3222729663d9eed0d7d6import SwiftUIstruct LazyHGridAdaptiveView: View {@State var row1MinHeight: CGFloat = 50.0@State var row2MinHeight: CGFloat = 50.0@State var row3MinHeight: CGFloat = 50.0@State var row1MaxHeight: CGFloat = 50.0@State var row2MaxHeight: CGFloat = 50.0@State var row3MaxHeight: CGFloat = 50.0let columns = 50let rows = 3var body: some View {VStack {SteppersView(control1A: ("Row 1 Min Height", $row1MinHeight),control1B: ("Row 1 Max Height", $row1MaxHeight),control2A: ("Row 2 Min Height", $row2MinHeight),control2B: ("Row 2 Max Height", $row2MaxHeight),control3A: ("Row 3 Min Height", $row3MinHeight),control3B: ("Row 3 Max Height", $row3MaxHeight))ScrollView(.horizontal) {LazyHGrid(rows: [GridItem(.adaptive(minimum: row1MinHeight, maximum: row1MaxHeight)),GridItem(.adaptive(minimum: row2MinHeight, maximum: row2MaxHeight)),GridItem(.adaptive(minimum: row3MinHeight, maximum: row3MaxHeight)),], alignment: .center, spacing: 19) {ForEach(0..<(columns * rows), id: \.self) {index inRectangle().foregroundColor(.red).frame(width: 25)}}}.frame(maxHeight: .infinity).padding(.vertical)Spacer()}}
}

GridItem.Size.adaptive is different from .flexible in one simple way. While these cells still have a minimum and maximum height, they will not prevent cells from the column to the right moving left in order to occupy available space. This is assuming that the available space is larger than the minimum height that the cells can occupy, of course. The difference here can be observed most clearly when scrolling to the right, as it is easy to achieve a situation in which the last column has less cells in it than the previous columns.

GridItem.Size.adaptive.flexible以一种简单的方式不同。 尽管这些单元格仍具有最小和最大高度,但它们不会阻止从列到右侧的单元格向左移动以占用可用空间。 当然,这是假定可用空间大于单元格可以占用的最小高度。 向右滚动时,可以最清楚地观察到此处的差异,因为很容易实现最后一列的单元格少于前一列的情况。

This is because the number of cells we calculated using columns * rows is no longer an accurate representation of the cells, as there are more items per column than previously expected.

这是因为我们使用columns * rows计算的单元格数量不再是单元格的准确表示,因为每列中的项比以前预期的要多。

// Requires SteppersView which can be found here:
// https://gist.github.com/sturdysturge/eed04e007cef3222729663d9eed0d7d6import SwiftUIstruct LazyHGridFlexibleView: View {@State var row1MinHeight: CGFloat = 50.0@State var row2MinHeight: CGFloat = 50.0@State var row3MinHeight: CGFloat = 50.0@State var row1MaxHeight: CGFloat = 50.0@State var row2MaxHeight: CGFloat = 50.0@State var row3MaxHeight: CGFloat = 50.0let columns = 50let rows = 3var body: some View {VStack {SteppersView(control1A: ("Row 1 Min Height", $row1MinHeight),control1B: ("Row 1 Max Height", $row1MaxHeight),control2A: ("Row 2 Min Height", $row2MinHeight),control2B: ("Row 2 Max Height", $row2MaxHeight),control3A: ("Row 3 Min Height", $row3MinHeight),control3B: ("Row 3 Max Height", $row3MaxHeight))ScrollView(.horizontal) {LazyHGrid(rows: [GridItem(.flexible(minimum: row1MinHeight, maximum: row1MaxHeight)),GridItem(.flexible(minimum: row2MinHeight, maximum: row2MaxHeight)),GridItem(.flexible(minimum: row3MinHeight, maximum: row3MaxHeight))], alignment: .center, spacing: 19) {ForEach(0..<(columns * rows), id: \.self) {index inRectangle().foregroundColor(.red).frame(width: 25)}}}.frame(maxHeight: .infinity).padding(.vertical)Spacer()}}
}

GridItem(2.0中的新增功能) (GridItem (New in 2.0))

Back to contents ↑

返回目录↑

You can see some great examples of GridItem in action above, in LazyHGrid and LazyVGrid.

您可以在上面的LazyHGridLazyVGrid看到一些出色的GridItem示例。

A GridItem must be given a size, but spacing and alignment are optional.

必须给GridItem指定大小,但是间距和对齐方式是可选的。

The GridItem.Size enum has three cases:

GridItem.Size枚举有以下三种情况:

  • case adaptive(minimum: CGFloat, maximum: CGFloat)

    case adaptive(minimum: CGFloat, maximum: CGFloat)

  • case fixed(CGFloat)

    case fixed(CGFloat)

  • case flexible(minimum: CGFloat, maximum: CGFloat)

    case flexible(minimum: CGFloat, maximum: CGFloat)

Bear in mind that failing to give a value for the spacing property allows your columns (in LazyVGrid) or your rows (in LazyHGrid) to potentially end up touching one another if they are not given enough space.

请记住,如果不给LazyVGrid属性指定值,则如果没有足够的空间,则您的列(在LazyVGrid )或行(在LazyHGrid )可能最终彼此接触。

Being explicit about spacing gives you more control about how you want them to adapt, assuming that the size they were given was not of type .fixed.

明确指定间距可以让您更好地控制它们的适应方式,假设给出的尺寸不是.fixed类型。

列表(在2.0中更新) (List (Updated in 2.0))

Back to contents ↑

返回目录↑

List, the vertical ScrollView that allows lazy loading of content only when it is visible on the screen, has some new initialisers in 2.0.

List是一种垂直ScrollView ,仅在屏幕上可见时才允许延迟加载内容,它在2.0中具有一些新的初始化程序。

  • init<Data, RowContent>(Data, children: KeyPath<Data.Element, Data?>, selection: Binding<SelectionValue?>?, rowContent: (Data.Element) -> RowContent)

    init<Data, RowContent>(Data, children: KeyPath<Data.Element, Data?>, selection: Binding<SelectionValue?>?, rowContent: (Data.Element) -> RowContent)

  • init<Data, RowContent>(Data, children: KeyPath<Data.Element, Data?>, selection: Binding<Set<SelectionValue>>?, rowContent: (Data.Element) -> RowContent)

    init<Data, RowContent>(Data, children: KeyPath<Data.Element, Data?>, selection: Binding<Set<SelectionValue>>?, rowContent: (Data.Element) -> RowContent)

  • init<Data, ID, RowContent>(Data, id: KeyPath<Data.Element, ID>, children: KeyPath<Data.Element, Data?>, selection: Binding<Set<SelectionValue>>?, rowContent: (Data.Element) -> RowContent)

    init<Data, ID, RowContent>(Data, id: KeyPath<Data.Element, ID>, children: KeyPath<Data.Element, Data?>, selection: Binding<Set<SelectionValue>>?, rowContent: (Data.Element) -> RowContent)

  • init<Data, ID, RowContent>(Data, id: KeyPath<Data.Element, ID>, children: KeyPath<Data.Element, Data?>, selection: Binding<SelectionValue?>?, rowContent: (Data.Element) -> RowContent)

    init<Data, ID, RowContent>(Data, id: KeyPath<Data.Element, ID>, children: KeyPath<Data.Element, Data?>, selection: Binding<SelectionValue?>?, rowContent: (Data.Element) -> RowContent)

These initialisers all have one thing in common. They were all available when SwiftUI launched, but they were only available on tvOS and watchOS.

这些初始化程序有一个共同点。 它们在SwiftUI启动时都可用,但是仅在tvOS和watchOS上可用。

All of these initialisers have now been added iOS, macOS and Mac Catalyst.

所有这些初始化程序现已添加到iOS,macOS和Mac Catalyst。

ForEach和DynamicViewContent(在2.0中更新) (ForEach & DynamicViewContent (Updated in 2.0))

Back to contents ↑

返回目录↑

In the “Views and Controls” chapter of this documentation, I talked about the new UTType structure that had replaced a rather confusing method. Instead of being able to create objects that represent data types, we had to resort to passing an array of strings that represented data types.

在本文档的“ 视图和控件 ”一章中,我谈到了新的UTType结构,该结构已替代了一个相当混乱的方法。 除了能够创建代表数据类型的对象外,我们不得不诉诸于传递代表数据类型的字符串数组。

This is not obvious in the initialiser for ForEach, but it conforms to the DynamicViewContent protocol. This happens when the generic Content conforms to View, which confusingly isn’t required by the ForEach structure itself. Every initialiser exists in an extension that does require that Content conforms to View though, so don’t go thinking you can use ForEach for any other purpose.

这在ForEach的初始化程序中并不明显,但它符合DynamicViewContent协议。 当通用Content符合View ,就会发生这种情况,而ForEach结构本身并不需要混淆性的要求。 每个扩展程序都存在于一个扩展中,该扩展确实要求Content符合View ,所以不要以为您可以将ForEach用于任何其他目的。

DynamicViewContent requires a Collection of data, the particular type of which is inferred by the data that it is given. What does it do, you ask. It provides methods such as onDelete, which gives you the ability to run a closure when the user deletes a row of a List. While onDelete hasn’t changed since last year, onInsert has. This occurs when an item is dragged using the onDrag modifier, as List uses onInsert instead of the more conventional onDrop modifier.

DynamicViewContent需要数据Collection ,其特定类型由给出的数据推断。 您会问,它是做什么的。 它提供了诸如onDelete方法,该方法使您能够在用户删除List的一行时运行闭包。 虽然onDelete还没有从去年开始改变, onInsert了。 当使用onDrag修改器拖动项目时会发生这种情况,因为List使用onInsert而不是更常规的onDrop修改器。

More information on drag and drop was contained in the “Views and Controls” chapter, so the main thing to point out is that onInsert now takes a UTType structure instead of the previous array of strings representing the UTTypes. This allows us to specify what kind of data can be dragged and dropped into a List, as otherwise we would not know whether we can add that data to the underlying Collection or not.

有关拖放的更多信息包含在“视图和控件”一章中,因此主要要指出的是, onInsert现在采用UTType结构,而不是之前的表示UTType的字符串数组。 这使我们可以指定可以将哪种数据拖放到List ,否则我们将不知道是否可以将该数据添加到基础Collection

But that’s not all that’s changed.

但这还不是全部更改。

If you look at the new initialiser for ForEach, you might notice something is different:

如果您查看ForEach的新初始化程序,您可能会发现有所不同:

init(_ data: Data, id: KeyPath<Data.Element, ID>, @ViewBuilder content: @escaping (Data.Element) -> Content)

Like the body: some View property of a View struct, the initialiser now takes a @ViewBuilder closure. Why does this matter? This is is effectively like wrapping our layout in a Group in the first iteration of SwiftUI. We did this because we wanted to be able to return one concrete type that conforms to the view protocol, and adding multiple values in the closure made it impossible to do that.

就像body: some View View结构的body: some View属性一样,初始化程序现在使用@ViewBuilder闭包。 为什么这么重要? 这实际上就像在SwiftUI的第一次迭代中将布局包装在Group中一样。 之所以这样做,是因为我们希望能够返回一种符合视图协议的具体类型,并且在闭包中添加多个值使其无法实现。

Now you can add whatever you want inside a ForEach, as long as it is less than ten views in size.

现在,您可以在ForEach添加所需的任何内容,只要它的大小小于十个视图即可。

Obviously this excludes the underlying data, so you could for instance have a List row with ten views in it, but that row is one of 100 or more rows that get their data from an array or other data structure.

显然,这不包括基础数据,因此,例如,您可以有一个包含十个视图的List行,但是该行是从数组或其他数据结构获取其数据的100个或更多行中的一个。

The power of ForEach is the ability to effectively treat as many items as you want as if they were one view in your hierarchy.

ForEach是能够有效地处理ForEach数量的项目,就好像它们是层次结构中的一个视图一样。

ScrollViewReader(2.0中的新增功能) (ScrollViewReader (New in 2.0))

Back to contents ↑

返回目录↑

There is some similarity between the existing GeometryReader and the new ScrollViewReader.

现有的GeometryReader与新的ScrollViewReader之间存在一些相似之处。

They are both closures that pass in a single parameter.

它们都是传递单个参数的闭包。

A GeometryReader passes a GeometryProxy which has two properties: safeAreaInsets: EdgeInsets and size: CGSize. This proxy comes with a method that will return a CGRect for the frame, but it requires a coordinate space in which to calculate this frame. The most obvious one is .global, as this gives a frame that is relative to the entire screen. But you can create custom coordinateSpace with a name that you specify, allowing you to get a frame relative to another View in the hierarchy.

一个GeometryReader传递一个GeometryProxy ,它具有两个属性: safeAreaInsets: EdgeInsetssize: CGSize 。 该代理附带有一种方法,该方法将为框架返回CGRect ,但是它需要一个坐标空间来计算该框架。 最明显的是.global ,因为它提供了相对于整个屏幕的框架。 但是你可以创建自定义 coordinateSpace 与您指定的名称 ,让您获得相对于层次结构中的另一个View的框架。

ScrollViewProxy has no properties, but it has a single method that performs an action instead of returning a value. When we specify an id for Views in a ScrollView, we can provide any Hashable type. With this we are telling Swift which part of our type is unique so that it can differentiate between instances of that type.

ScrollViewProxy没有属性,但它具有执行操作而不是返回值的单个方法。 当我们在ScrollView为Views指定一个id ,我们可以提供任何Hashable类型。 这样我们告诉Swift我们类型的哪一部分是唯一的,以便可以区分该类型的实例。

In my example, I’m just using the index for each row in my List as an ID.

在我的示例中,我只是将List每一行的索引用作ID。

Many provided Swift types already conform to Hashable, so this is easier than making a Hashable type yourself. Here’s how to conform to the Hashable protocol if you’re interested, and you’ll see there that it isn’t a lot of effort at all. Now that I can identify the rows of my List, I provided a TextField that you can type a number into and a Button that will send the ScrollView to that row automatically.

许多提供的Swift类型已经符合Hashable,因此这比自己制作Hashable类型要容易。 如果您感兴趣的话,这里是如何遵循Hashable协议的方法 ,您会发现它根本不需要花费很多精力。 现在,我可以识别List的行了,我提供了一个TextField可以在其中键入数字)和一个Button ,它将ScrollView自动发送到该行。

struct Contentview: View {@State var target = 0var body: some View {ScrollViewReader { proxy inVStack {Group {Text("Type a number using lower case words like 'thirty-four' and press return on the keyboard")HStack {TargetTextField(target: $target)GoToButton(target: target, proxy: proxy)}}.padding()List {ForEach(0..<100, id: \.self) {index inText("Item \(index)").id(index)}Button("Back to top") {proxy.scrollTo(0)}}}}}
}struct GoToButton: View {let target: Intlet proxy: ScrollViewProxyvar body: some View {Button("Go to \(target)") {UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)withAnimation {proxy.scrollTo(target)}}.padding().background(Color.blue).foregroundColor(.white).cornerRadius(15)}
}struct TargetTextField: View {static var formatter: NumberFormatter {let formatter = NumberFormatter()formatter.numberStyle = .spellOutreturn formatter}@Binding var target: Intvar body: some View {TextField("Enter a number", value: $target, formatter: Self.formatter).padding().background(Color.gray).cornerRadius(15)}
}

I’ve made my example more difficult to use by using a NumberFormatter which uses a numberStyle called spellOut. This requires you to spell your numbers as lowercase words, putting a hyphen between a number like thirty-four and omitting words like and. Have a play around with it, and if you get bored of it, you can always change the NumberFormatter to something more sensible if you prefer.

我做了我的例子比较难用用NumberFormatter其采用了numberStyle称为spellOut 。 这需要你拼出你的号码为小写的话,把一个连字符数量之间像34和省略的话就像 。 试一试,如果您对它感到厌烦,可以随时将NumberFormatter更改为更明智的选择。

Notice that the scrolling is animated, but at the bottom of the List there is a Button that says “Back to top.” Unlike the GoToButton at the top, which puts its proxy.scrollTo(_:) inside a withAnimation block, this Button does not add an explicit animation. This is the default behaviour of the scrollTo(_:) action, instantly scrolling without any animation. Keep this in mind if you want to animate any changes to the scroll location.

请注意,滚动是动画的,但是在List的底部有一个按钮,显示“返回页首”。 与顶部的GoToButton不同,该Button将其proxy.scrollTo(_:)放在withAnimation块内,此Button不会添加显式动画。 这是scrollTo(_:)操作的默认行为,无需任何动画即可立即滚动。 如果要对滚动位置进行动画处理,请记住这一点。

Notice how I was able to pass the ScrollViewProxy as a parameter to GoToButton, so that the ability to change the scroll location can be passed between views.

请注意,我如何能够将ScrollViewProxy作为参数传递给GoToButton ,以便可以在视图之间传递更改滚动位置的功能。

ScrollViewProxy(2.0中的新增功能) (ScrollViewProxy (New in 2.0))

Back to contents ↑

返回目录↑

See ScrollViewReader above, which passes a ScrollViewProxy as a parameter into its closure the same way as GeometryReader passes a GeometryProxy.

参见上面的ScrollViewReader ,它将ScrollViewProxy作为参数传递到它的闭包中,就像GeometryReader传递GeometryProxy

组(在2.0中更新) (Group (Updated in 2.0))

Back to contents ↑

返回目录↑

Now that more structures take a @ViewBuilder closure, and therefore return a TupleView that contains up to ten children that all conform to View, you might think that Group no longer has much purpose.

现在,更多结构采用@ViewBuilder闭包,并因此返回一个TupleView ,其中包含最多十个都符合View的子级,您可能会认为Group不再具有太大的用途。

After all this “affordance for grouping view content,” as Apple calls it, did little else at that point.

就像苹果公司所说的那样,在完成了所有“为视图内容分组后的功能”之后,其他事情就没有了。

But now we have new possibilities, as we can now group anything conforming to these protocols too:

但是现在有了新的可能性,因为我们现在也可以将符合这些协议的所有内容归为一组:

  • Scene

    Scene

  • Widget

    Widget

  • Commands

    Commands

  • ToolbarContent

    ToolbarContent

I’ll go into a lot more detail about what these are in a later chapter called “App Structure and Behavior,” but the important thing to know is that Group has new capabilities.

在后面的“应用程序结构和行为”一章中,我将详细介绍这些内容,但重要的是要知道Group具有新功能。

In much the same way that @ViewBuilder allows Group to combine up to ten views, @_WidgetBuilder allows a combination of up to ten widgets. When macOS has commands that it will display in the menus at the top of the screen, up to ten can be added with @CommandBuilder.

@ViewBuilder允许Group最多组合十个视图,而@_WidgetBuilder可以组合十个小部件。 当macOS具有将显示在屏幕顶部菜单中的命令时, @CommandBuilder最多可以添加@CommandBuilder

Building a toolbar?

建立工具列?

You guessed it:@ToolbarBuilder will allow up to ten children.

您猜对了: @ToolbarBuilder最多允许十个孩子。

Now that SwiftUI apps can be created without an AppDelegate, we use a structure that conforms to the App protocol, which in turn requires a body that conforms to the Scene protocol.

现在,无需使用AppDelegate即可创建SwiftUI应用程序,我们使用符合App协议的结构,而该结构又需要符合Scene协议的主体。

When multiple scenes are provided within a Group, @SceneBuilder allows us to add up to ten children.

当一个Group中提供多个场景时, @SceneBuilder允许我们最多添加十个孩子。

This differs from WindowGroup, which specifically provides views that will be given identically structured yet separate windows. Since WindowGroup conforms to the Scene protocol itself, it can be at the top of the hierarchy inside the body of an App structure. If a Group only has children that conform to the View protocol, it cannot be used in the same way.

这不同于WindowGroup ,后者专门提供了视图,这些视图将具有相同的结构,但具有独立的窗口。 由于WindowGroup符合Scene协议本身,因此它可以位于App结构主体内部的层次结构的顶部。 如果Group仅具有符合View协议的子级,则不能以相同的方式使用它。

In other words, a structure conforming to App can contain:

换句话说,符合App的结构可以包含:

  • A Group made up of up to tenWindowGroup children

    Group由多达十WindowGroup儿童

  • A group made up of up to ten Scene-conforming children

    由最多十个符合Scene孩子组成的小组

  • A WindowGroup made up of up to ten Group- or other View-conforming children

    一个WindowGroup由最多十个符合WindowGroup或其他符合View的子项组成

If this is confusing, don’t worry. It’ll be covered in way more detail in the “App Structure and Behavior” chapter.

如果这令人困惑,请不要担心。 “应用程序的结构和行为”一章将对此进行更详细的介绍。

Groupbox,OutlineGroup和DisclosureGroup (Groupbox, OutlineGroup, & DisclosureGroup)

Of these three, GroupBox is the only one that isn’t new in 2.0.

在这三个组件中, GroupBox是2.0版中唯一不新增的组件。

When Groupbox was made available when SwiftUI was originally released, it was only available on macOS, and the main change is that it is now cross-platform. This is an easy way of grouping content together with an optional label. OutlineGroup provides an ability to reveal additional information about an item that would otherwise be hidden. DisclosureGroup has a similar purpose, with the addition of a Binding<Bool> that can control whether or not the additional information is shown.

在最初发布Groupbox时使Groupbox可用时,它仅在macOS上可用,主要的变化是现在它是跨平台的。 这是将内容与可选标签一起分组的一种简便方法。 OutlineGroup提供了显示有关可能会隐藏的项目的其他信息的功能。 DisclosureGroup具有类似的目的,增加了一个Binding<Bool> ,它可以控制是否显示其他信息。

You can find examples of this, as well as the new OutlineGroup and DisclosureGroup, in “SwiftUI’s GroupBox, OutlineGroup, and DisclosureGroup in iOS 14” by Anupam Chugh.

您可以在 Anupam Chugh 撰写的 “ iOS 14中的SwiftUI的GroupBox,OutlineGroup和DisclosureGroup中 ”找到此示例以及新的OutlineGroupDisclosureGroup

NavigationView(在2.0中更新) (NavigationView (Updated in 2.0))

Back to contents ↑

返回目录↑

I thought this was already available on watchOS, as I had previously released a watchOS app that lets you choose pictures of dogs from a List. But it turns out that despite using a NavigationLink in that app, I was not embedding it inside a NavigationView. This would compile for iOS and macOS, but it would not allow navigation due to the lack of NavigationView. Presumably something about the way watchOS always works on the basis of stacked navigation makes this unnecessary, but other platforms have no expectation that this would be the case.

我以为它已经在watchOS上可用了,因为我以前发布了一个watchOS应用程序,可以从List选择狗的图片。 但事实证明,尽管在该应用程序中使用了NavigationLink ,但我并未将其嵌入到NavigationView 。 这将针对iOS和macOS进行编译,但由于缺少NavigationView ,因此将不允许导航。 可能有关watchOS始终基于堆叠导航的工作方式的某些事情使此操作变得不必要,但是其他平台并不期望会出现这种情况。

WatchOS now has the ability to use .navigationViewStyle, but it seems the only provided value for it is StackNavigationViewStyle.

WatchOS现在可以使用.navigationViewStyle ,但是似乎唯一提供的值是StackNavigationViewStyle

The only other option on any platform isDoubleColumnNavigationViewStyle, and you can bet that's not coming to WatchOS any time soon!

在任何平台上,唯一的其他选项是DoubleColumnNavigationViewStyle ,您可以打赌,很快就不会在WatchOS上使用它了!

TabView(在2.0中更新) (TabView (Updated in 2.0))

Back to contents ↑

返回目录↑

I already mentioned this when I went through the new standard View Modifiers in the “Views and Controls” chapter. That was when I was covering the .tabItem modifier, which has changed in the same way as the TabView it applies to.

我在“ 视图和控件 ”一章中通过新的标准视图修改器时已经提到了这一点。 那是我讨论.tabItem修饰符的时候,该修饰符的更改方式与其应用于的TabView相同。

To recap what I said then, I’ll post Apple’s example with the addition of the @available attribute at the top.

回顾一下我刚才讲的内容,我将在顶部添加@available属性的情况下发布Apple的示例。

@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 7.0, *)
struct TabItem: View {var body: some View {TabView {View1().tabItem {Image(systemName: "list.dash")Text("Menu")}View2().tabItem {Image(systemName: "square.and.pencil")Text("Order")}}}
}struct View1: View {var body: some View {Text("View 1")}
}struct View2: View {var body: some View {Text("View 2")}
}

Notice anything?

注意到什么了吗?

TabView, along with the modifier .tabItem that allows you to create the icon that represents that page on the tab bar, is new to watchOS. Although it was available on Mac, iOS, iPadOS and tvOS last year, it has only just come to the watch this year. What form could it possibly take, you might ask? It resembles a UIPageViewController from UIKit, with each page requiring you to swipe horizontally from one to the other. The although the .tabItem modifier exists, neither the Text nor the Image that Apple’s example provides are visible.

TabView和修改器.tabItem一起使您可以在选项卡栏上创建代表该页面的图标,这是watchOS的新增功能。 尽管它去年在Mac,iOS,iPadOS和tvOS上可用,但今年才出现。 您可能会问,它可能采用什么形式? 它类似于UIKit中的UIPageViewController ,每个页面都需要您从一个页面水平滑动到另一个页面。 尽管存在.tabItem修饰符,但Apple示例提供的TextImage都不可见。

Instead we get dots, much in the same way that UIPageViewController makes use of a UIPageControl, which Apple describes as "a horizontal series of dots, each of which corresponds to a page in the app’s document or other data-model entity.”

相反,我们得到点,就像UIPageViewController使用UIPageControl ,Apple将其描述为“水平的点序列,每个点对应于应用程序文档或其他数据模型实体中的页面”。

下一步 (Next Steps)

SwiftUI is only a year old as I’m writing this, and there are already a wealth of resources out there. My writing would not be possible without the following websites:

在我撰写本文时,SwiftUI才刚成立一年,并且那里已经有很多资源。 没有以下网站,我的写作将是不可能的:

  • LOSTMOA Blog

    LOSTMOA博客

  • Hacking with Swift

    用Swift入侵

  • Swift UI Lab

    Swift UI实验室

  • Swift with Majid

    斯威夫特与马吉德

  • WWDC by Sundell

    WWDC,桑德尔

  • Swift by Sundell

    迅捷的桑德尔

If you’ve got a great resource to share with the community, let me know and I’ll gladly add it to this list.

如果您有很多资源可以与社区分享,请告诉我,我们很乐意将其添加到此列表中。

As I said at the start of the article, If you have requests for more detail on a subject, or if you think I’ve made a mistake, let me know in a response below.

就像我在文章开头所说的那样,如果您要求提供有关某个主题的更多详细信息,或者您认为自己犯了一个错误,请在下面的回复中告诉我。

Thanks for reading!

谢谢阅读!

翻译自: https://medium.com/better-programming/view-layout-and-presentation-in-swiftui-705b7d81f03

swift和swiftui

http://www.taodudu.cc/news/show-6007164.html

相关文章:

  • 使用python基于socket的tcp服务器聊天室
  • 2021-06-14 Socketio学习使用搭建一个聊天室
  • windows网络编程 --网络聊天室(2)
  • JavaSE聊天室项目
  • Linux-Socket实现模拟群聊(多人聊天室)
  • 撸一个聊天室(vue+koa2+websokect+mongodb)
  • 【计算机网络】Socket聊天室程序
  • 06 java GUI 网络编程:图形界面聊天室
  • Java WebSocket编程与网页简易聊天室
  • springboot 使用 Spring Boot WebSocket 创建聊天室 2-11
  • 教你六种方式实现聊天室
  • 多人群聊聊天室java_#java 聊天室(一)—— 实现一个简单Telnet多人群聊聊天室...
  • Linux环境下——C语言聊天室项目
  • GTK+实现linux聊天室代码详解-clientr端
  • python聊天室设计_如何使用 Python 开发一个聊天室?
  • Linux一句话将文件夹的用户用户组设置为wps:wps
  • 你想每天定时向你的女朋友发一句早安吗 ?教你实现一个微信机器人
  • 【EXCEL】解决因为删除、移动、隐藏行后序号不连续的问题
  • NPOI 隐藏行
  • 在 JavaScript 中隐藏表格行
  • Excel 2011 显示被隐藏的第一行
  • 王者荣耀官网全皮肤超超超清原皮爬取!!!
  • Cocos2d-html5《王者之剑》实现 (1)
  • 假如工资有段位,你是个啥?
  • 王者荣耀服务器维护到什么时候7月9号,王者荣耀7月9号更新产生的新变化
  • python接私活王者_Python从青铜到王者这5个实战项目要会
  • 操作系统语言包在c盘哪里,win10系统通过卸载语言包释放c盘空间的具体教程
  • win10语言包在c盘哪里,win10系统通过卸载语言包释放c盘空间的操作方法
  • 计算机控制台win10,Win10系统打开Windows控制台的方法
  • VMware16.0如何装win7和win10

swift和swiftui_在swiftui中查看布局和演示相关推荐

  1. android怎么查看方法被谁调用,Android中查看布局文件中的控件(view,id)在哪里被调用(使用)...

    在阅读别人的代码时通常是很痛苦的,有时很想要看一看布局中的控件在哪里被调用了,为之很苦恼 在这里提供一种方法. 复制要查看的控件ID,到R文件中搜索到该ID, 接下来就好办的了,选中ID按下Ctrl鼠 ...

  2. 【Swift开发】SwiftUI中使用MapKit实现地图功能

    前言 ios中可以直接使用苹果官方提供的map--MapKit.在SwiftUI中如何使用MapKit网上有也有不少文章,但是大部分不详细,大部分只是简单的展示出地图.所以本文来详细的讲解一下如何使用 ...

  3. swiftui_使用SwiftUI在30分钟内制作一个应用

    swiftui This post was first delivered as a live coding presentation at a Telstra Purple Back2Base ev ...

  4. 使用自定义字体升级您的 SwiftUI 应用程序教程,如何在 SwiftUI 中添加自定义字体

    大家好,我叫 Izzy,在这篇短文中,我想向您展示如何将新字体添加到您的 SwiftUI 项目中.XCode 项目的默认字体易于阅读,通常适用于很多用例,但有时添加另一种字体以获得更多细节会很好. 先 ...

  5. SwiftUI 中的水平条形图

    水平条形图以矩形条的形式呈现数据类别,其宽度与它们所代表的数值成正比.本文展示了如何在垂直条形图的基础上创建一个水平柱状图. 水平条形图不是简单的垂直条形图的旋转.在 Numbers 等应用程序中,水 ...

  6. c++builder中dbgrid控件排序_如何实现APP中各种布局效果?学会这几个控件就够了...

    学习Flutter有一段时间了,也做了一些APP,但是总感觉对控件的使用一知半解,没有系统的概念,所以特意花了几天时间,把所有的控件都研究了一遍,总结出了常用的控件和使用方法, 俗话说:工欲善其事,必 ...

  7. C++中的布局new操作符

    C++中的布局new操作符 通常,new负责在堆内存中找到一个能够满足要求的内存块.new操作符还有一种变体:placement new 操作符,它能让你指定要使用的内存的位置. 程序员可以使用这种特 ...

  8. 官方文档——一篇文章弄懂Flutter中的布局

    来自Flutter中文资源主页https://flutter.cn/ 原文:https://flutter.cn/docs/development/ui/layout Flutter 中的布局 要点 ...

  9. 安卓中的布局优化之clude、merge、ViewStub

    1.include include标签常用于将布局中的公共部分提取出来供其他layout共用,以实现布局模块化,也是平常我们设计布局时用的最多的. 需要注意的是如果一个根布局引入多个include需要 ...

最新文章

  1. screen史上最全教程
  2. js中显示一个指定html文档,JS实现选定指定HTML元素对象中指定文本内容功能示例...
  3. Android按钮持续按下执行,Android 按钮长按下去重复执行某个动作,放开后停止执行动作...
  4. 致NLP学习者,该跟大佬学习做项目了,附资料
  5. find函数matlab_从零开始的matlab学习笔记——(39)find函数
  6. C语言(CED)如何用sort函数根据结构体里的某一属性进行排序
  7. LeetCode:Permutations, Permutations II(求全排列)
  8. matlab生成网络流量,BP神经网络 预测网络流量
  9. java8实现Joiner:数组集合转换字符串
  10. HTK的Network把所有的NetNode对象chain,并重新排序
  11. Linux开机启动过程分析
  12. dcx游走 - 组合计数
  13. jQuery 效果 - stop() 方法
  14. 土豆linux,土豆里安装linux是什_土豆发芽图片
  15. c语言编程被7整除的4位数,四位数3a8c能被7整除,且是55的倍数,求a..._一建考试_帮考网...
  16. 如何寻找创业合伙人、搭建创业团队?
  17. 移动磁盘使用驱动器中的光盘之前需要格式化数据如何恢复
  18. 如何解决Google浏览器被拦截
  19. mybatis判断list非空
  20. 电商遭遇成本寒冬:京东商城放弃全场免运费

热门文章

  1. 考研英语 - word-list-41
  2. 考研英语 - word-list-29
  3. bad_pool_caller蓝屏故障分析
  4. 关于playerunknown's battlegrounds中归零距离小谈
  5. 2021年美容师(初级)最新解析及美容师(初级)模拟考试题
  6. 微信app支付服务端开发记录
  7. OS学习笔记-11(清华大学慕课)进程与线程
  8. ue4 小知识点 图片变灰 hlsl 材质 custom shader
  9. Spoon Kettle 输入之获取文件名(Get file names)
  10. 3-订单持续时间的计算