swift和swiftui_在swiftui中查看布局和演示
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.
您可以將上面的LazyVStack和LazyHStack部分的相同邏輯應用于網格。 如果我們想在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.
您可以在上面的LazyHGrid和LazyVGrid看到一些出色的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: EdgeInsets和size: 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中 ”找到此示例以及新的OutlineGroup和DisclosureGroup 。
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示例提供的Text和Image都不可見。
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
總結
以上是生活随笔為你收集整理的swift和swiftui_在swiftui中查看布局和演示的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: html展开插件,分享10款功能强大的H
- 下一篇: amd为什么还用针脚_为什么AMD的CP