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

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


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. Although my post seemed to help a lot of people, I also wrote it six months late.

这是我分享我尝试填补Apple提供的文档不足所留下的空白时所学到的知识的方式。 尽管我的帖子似乎对很多人都有帮助,但我也写了六个月才写完。

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 post.


The names of these chapters correspond with the chapter names in Apple’s SwiftUI documentation.


I can guarantee that none of them will be as long as this one, if that’s something you’re worried about!


  • App Structure and Behavior

  • View Layout and Presentation

  • Drawing and Animation

  • Framework Integration

  • State and Data Flow

  • Gestures

  • Preview


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


The View Protocol @ViewBuilderNew and Updated ViewsColorPicker (NEW in 2.0)SpriteView (NEW in 2.0)TextEditor (NEW in 2.0)SignInWithAppleButton (NEW in 2.0)ProgressView (NEW in 2.0)GaugeView (NEW in 2.0)Label (NEW in 2.0)Link (NEW in 2.0)Menu (NEW in 2.0)MenuButton (Deprecated in 2.0)Text (Updated in 2.0)Image (Updated in 2.0)Button (Updated in 2.0)PasteButton (Updated in 2.0)Toggle (Updated in 2.0)DatePicker (Updated in 2.0)New and Updated View Modifiers.matchedGeometryEffect (NEW in 2.0).help (NEW in 2.0).accessibility(inputLabels:) (NEW in 2.0).accessibility(selectionIdentifier:) (Deprecated in 2.0).scaleEffect (Updated in 2.0).imageScale (NEW in 2.0).accentColor (Updated in 2.0).preferredColorScheme (Updated in 2.0).textContentType (NEW in 2.0).listItemTint (NEW in 2.0).listRowPlatterColor (Deprecated in 2.0).onLongPressGesture (Updated in 2.0).onOpenURL (NEW in 2.0).onPasteCommand (NEW in 2.0).onDrag and .onDrop (Updated in 2.0).onChange (NEW in 2.0).keyboardShortcut (NEW in 2.0).focusedValue and @FocusedBinding (NEW in 2.0).prefersDefaultFocus and .focusScope (NEW in 2.0).fullScreenCover (NEW in 2.0).defaultAppStorage (NEW in 2.0).appStoreOverlay (NEW in 2.0).toolbar (NEW in 2.0).previewContext (NEW in 2.0)
.userActivity, .onContinueUserActivity (NEW in 2.0).tabItem (Updated in 2.0).contextMenu (Updated in 2.0).navigationTitle and .navigationSubtitle (NEW in 2.0).navigationViewStyle (Updated in 2.0).navigationBarTitle (Deprecated in 2.0).navigationBarItems (Deprecated in 2.0)Styles on iOS, iPadOS, Mac Catalyst and tvOS (NEW in 2.0)Styles Only on macOS (NEW in 2.0)Next Steps

查看协议 (The View Protocol)

If you aren’t already aware, SwiftUI uses the View protocol to create reusable interface elements. Views are value types, which means they use a Struct instead of a Class definition.

如果您还不了解,SwiftUI会使用View协议来创建可重用的界面元素。 视图是值类型,这意味着它们使用Struct而不是Class定义。

What does this actually mean, in practice?


Structs do not allow inheritance. Although your structs conform to the View protocol, they do not inherit from a base class called View that Apple has provided.

结构不允许继承。 尽管您的结构符合View协议,但它们并不继承自Apple提供的称为View的基类。

This makes it different from UIView, from which almost everything in UIKit inherits. A UIView basically cannot be seen without being assigned a frame and being added as a subview of a UIViewController subclass.

这使其与UIView有所不同,后者几乎继承了UIKit中的所有内容。 没有分配框架并作为UIViewController子类的子视图添加,基本上看不到UIView

If you create a new Xcode project that uses SwiftUI instead of Storyboard as the basis of its user interface, you’ll automatically be given an example of a SwiftUI View called ContentView.

如果您创建一个新的Xcode项目,该项目使用SwiftUI而不是Storyboard作为其用户界面的基础,则会自动为您提供一个名为ContentView的SwiftUI View的示例。

You’ll notice that inside the ContentView struct, there is a variable called body. This is the sole requirement of the View protocol, and it makes use of the some keyword, which is brand new to Swift 5.1.

您会注意到,在ContentView结构中,有一个名为body的变量。 这是View协议的唯一要求,它使用了Swift 5.1全新的some关键字。

You can rely on a Stack Overflow thread to explain what this keyword means better than I ever could:

您可以依靠Stack Overflow线程来解释此关键字的含义比我能做到的更好:

“You can think of this as being a “reverse” generic placeholder. Unlike a regular generic placeholder which is satisfied by the caller… An opaque result type is an implicit generic placeholder satisfied by the implementation… The main thing to take away from this is that a function returning some P is one that returns a value of a specific single concrete type that conforms to P.”

“您可以将其视为“反向”通用占位符。 不像调用方满意的常规通用占位符…不透明的结果类型是实现满足的隐式通用占位符…要摆脱的主要问题是,返回some P的函数是返回特定值的函数。符合P单一混凝土类型。”

@ViewBuilder (@ViewBuilder)

This is a kind of function builder that allows you to construct a single View from multiple Views. If you add this attribute above your View body, cmd-click it, and choose Jump to definition, you’ll see a bunch of interesting stuff. Perhaps the most important part is this:

这是一种函数生成器,允许您从多个视图构造一个视图。 如果将此属性添加到View主体上方,请cmd单击它,然后选择“跳转到定义”,您会看到很多有趣的东西。 也许最重要的部分是:

public static func buildBlock<C0, C1, C2, C3, C4, C5, C6, C7, C8, C9>(_ c0: C0, _ c1: C1, _ c2: C2, _ c3: C3, _ c4: C4, _ c5: C5, _ c6: C6, _ c7: C7, _ c8: C8, _ c9: C9) -> TupleView<(C0, C1, C2, C3, C4, C5, C6, C7, C8, C9)> where C0 : View, C1 : View, C2 : View, C3 : View, C4 : View, C5 : View, C6 : View, C7 : View, C8 : View, C9 : View

This is the function that runs when you put ten Views inside a VStack to lay them out vertically. The fact that there isn’t a buildBlock that takes eleven Views is the reason you can’t keep adding children to a VStack indefinitely. There’ll be more about this in the View Layout and Presentation chapter of this documentation, but I’m mentioning it for a specific reason.

当您在VStack放置十个视图以垂直放置它们时,将运行此功能。 没有需要11个Views的buildBlock的事实是您无法继续无限期地将子级添加到VStack的原因。 在本文档的“视图布局和表示”一章中将有更多关于此的信息,但是出于特定原因而提及它。

In Xcode 12, the body property is assumed to be @ViewBuilder.

在Xcode 12中,body属性假定为 @ViewBuilder

Why does this matter? Now you can put up to ten Views as children directly in your body property. Previously, this was only possible by putting your Views inside a Group, which is a way to get the benefits of @ViewBuilder without the layout implications of VStack, HStack, or ZStack. This is no longer necessary in the new version of SwiftUI, and the uses of Group will become more niche as a result.

为什么这么重要? 现在,您可以直接在您的身体属性中放置多达十个“视图”作为子视图。 以前,这只能通过将视图放置在一个Group ,这是一种在不涉及VStackHStackZStack布局的情况下获得@ViewBuilder好处的@ViewBuilder 。 在新版本的SwiftUI中,这不再是必须的,因此使用Group会更加利基。

You should still probably lay your Views out in a VStack or HStack, as putting them directly in a body property is ambiguous when you come to use it.

您仍然应该将视图放置在VStackHStack ,因为使用它们时将它们直接放在body属性中是模棱两可的。

查看修改器 (View Modifiers)

Before we dive into the Views that are new in 2020, let’s do a refresher on what View Modifiers are. They’ll be shown alongside the new Views, so it doesn’t really make sense to wait to explain what they are until later.

在深入探讨2020年新增的Views之前,让我们先回顾一下View Modifiers是什么。 它们将与新的Views一起显示,因此等到以后再解释它们并没有什么意义。

All Views can be modified by structs that conform to the ViewModifier protocol. All the protocol requires is a function called body (content: Content) that returns a generic View. Because View cannot be created directly, the type that we pass to the ViewModifier is unknown until the modifier is called. Content acts as a proxy for that concrete type. The return type, like the body of any View, is inferred from the implementation.

所有视图都可以通过符合ViewModifier协议的结构进行修改。 协议所需要的只是一个称为body(内容:内容)的函数,该函数返回通用View。 由于无法直接创建View,因此在调用修饰符之前,传递给ViewModifier的类型是未知的。 内容充当该具体类型的代理。 返回类型,就像任何View的主体一样,都是从实现中推断出来的。

Let’s see an example of a custom modifier so you can see what’s happening under the surface with these:


As you can see, it would be possible to add .modifier(YourModifier()) to call a ViewModifier, but it makes a lot more sense to use a View extension and give a clean call site.


The first method, blue(_:), does essentially what .modifier(YourModifier()) does. By constructing ModifiedContent, we end up with a View that has the modifier applied. But we can make this less complicated by calling the instance method .modifier(YourModifier()) on our View to get the same result.

第一种方法blue(_:)基本上执行.modifier(YourModifier())操作。 通过构造ModifiedContent ,我们最终得到一个应用了修饰符的View 。 但是我们可以通过在View上调用实例方法.modifier(YourModifier())来获得相同的结果,从而减少复杂性。

The second method, b(_:), removes the need to call .modifier(YourModifier()) every time we need it. This is the way the standard modifiers look, and it reduces the amount of code inside our View.

第二种方法b(_:)消除了我们每次需要调用.modifier(YourModifier()) 。 这是标准修饰符的外观,它减少了View内部的代码量。

Obviously b(_:) is a pretty terrible name for a property, method or ViewModifier, but I made the identifiers gradually shorter to indicate which one is the simplest.

显然, b(_:)对于属性,方法或ViewModifier是一个非常糟糕的名称,但是我使标识符逐渐变短以指示哪个是最简单的。

First we’ll see all of the Views that are completely new in 2020, then the Views from 2019 that have been updated this year.


Then, we’ll see what View Modifiers are new or updated at the end.


2.0中的新功能:ColorPicker (NEW in 2.0: ColorPicker)

There has never been a colour picker included for iOS developers. I’ve used third-party ones before, but dependencies lead to a reliance on other developers to ensure compatibility with all your future projects and deployment targets. In the final weeks before WWDC, I finally decided to create every kind of colour picker control I could think of in SwiftUI. This would allow me to create colour pickers from a Swift Package of these controls and make a colour picker for any project.

从来没有为iOS开发人员提供颜色选择器。 我以前使用过第三方开发人员,但是依赖导致依赖其他开发人员来确保与您所有未来的项目和部署目标兼容。 在WWDC之前的最后几周,我终于决定创建我可以在SwiftUI中想到的每种颜色选择器控件。 这将允许我这些控件的Swift包创建颜色选择器,并为任何项目创建颜色选择器。

Then WWDC came along, and now we have ColorPicker for iOS 14. This new control seems very capable and has features such as an eyedropper that allows you to pick colours from anywhere. You can even use the eyedropper to pick colours from the ColorPicker’s UI itself, so it seems clear that this is a powerful new capability that we get for free. But as far as I can work out, the new colour picker has a very rigid set of controls that cannot be changed. Unless the official documentation has not been updated yet, it would appear that there is no way to change what the ColorPicker offers, such as restricting it to only a canvas, a palette or only sliders.

然后WWDC出现了,现在我们有了iOS 14的ColorPicker。这个新控件似乎功能强大,并具有诸如吸管的功能,可让您从任何地方选择颜色。 您甚至可以使用吸管从ColorPicker的UI本身中选择颜色,因此很明显,这是我们免费获得的强大功能。 但据我所知,新的颜色选择器具有一组非常僵化的控件,无法更改。 除非官方文档尚未更新,否则似乎无法更改ColorPicker提供的功能,例如将其限制为仅画布,调色板或滑块。

Instead, these three options are selected with a segmented picker by the user.


For some useful screenshots and animated GIFs of how the ColorPicker will look in your app, check out Using a ColorPicker with SwiftUI.


2.0版的新功能:SpriteView (NEW in 2.0: SpriteView)

SpriteKit is Apple’s framework for making 2D games. Sprites are small bitmaps that are used to represent players, enemies, and projectiles, among other things. Since you may have many sprites on the screen at once in a game, it makes sense to use tools that are designed to do this with performance in mind. It is now possible to create a SwiftUI View that will show a SKScene from SpriteKit, allowing you to create a game and then put that game anywhere you would put a SwiftUI View.

SpriteKit是Apple制作2D游戏的框架。 子画面是小的位图,用于表示玩家,敌人和弹丸等。 由于游戏中可能一次在屏幕上显示许多精灵,因此使用专门为实现此目的而设计的工具是很有意义的。 现在可以创建一个SwiftUI查看,将显示一个SKScene从SpriteKit,允许您创建一个游戏,然后把那场比赛中的任何地方,你会放一个SwiftUI查看。

Image for post

In my example, I have a simple square sprite that fires projectiles at enemies that come from the right. If they get all the way to the left, you lose a life. If you take one of them down, your score increases.

在我的示例中,我有一个简单的方形精灵,可以向右边的敌人发射弹丸。 如果他们一路向左走,您将失去生命。 如果您将其中之一记下来,您的分数就会增加。

Let’s look at the SwiftUI first, which requires a SpriteKit scene that we’ll create later.


We have an ObservableObject called GameModel that stores our data, and a ContentView struct that displays our game. At the top of a VStack we’re displaying the HUDView, which tells us what the score currently is, how many lives we have, and what high score has been previously recorded. When we run out of lives, the restart button appears. All this does is alter a @Published property in the GameModel object that we are observing with the new .onChange modifier in ContentView.

我们有一个名为GameModelObservableObject存储我们的数据,还有一个显示我们的游戏的ContentView结构。 在VStack的顶部,我们将显示HUDView ,它告诉我们当前的分数,我们有多少生命以及之前已记录的最高分数。 当我们用尽生命时,将显示重新启动按钮。 这一切都是通过使用ContentView的新.onChange修饰符更改GameModel对象中的@Published属性。

Essentially we’re saying when restartGame is true, we want to send a message to our GameScene that it should unpause and reload the game from the beginning.


The high score is recorded using the @AppStorage property wrapper, which saves data to UserDefaults in a convenient way. I will give more information about the new property wrappers in another chapter of this documentation, but the important thing is that properties with this wrapper are saved persistently, and can be recalled easily the next time the app is loaded. When the player is out of lives, there is also a game over state, that requires the player to tap a restart button in order to begin the game again. This resets the score, removes the enemies, and restarts the spawning of enemies as the beginning of the game did.

高得分使用@AppStorage属性包装器记录,该包装器以方便的方式将数据保存到UserDefaults 。 我将在本文档的另一章中提供有关新属性包装器的更多信息,但重要的是该包装器的属性将永久保存,并且可以在下次加载应用程序时轻松调用。 当玩家失去生命时,还会出现游戏结束状态,该状态要求玩家点击重新启动按钮才能再次开始游戏。 这将重置分数,移除敌人,并像游戏开始时一样重新开始生成敌人。

Here’s the SpriteKit code:


Don’t worry too much about the logic of the game, as this is a SpriteKit game written in Swift. If you don’t know much about SpriteKit, as I clearly don’t, there are many tutorials that will help you to get started.

不必太担心游戏的逻辑,因为这是用Swift编写的SpriteKit游戏。 如果您不太了解SpriteKit,而我显然不了解,那么有很多教程可以帮助您入门。

The important thing to know about SpriteView is it gives you an easy way to embed 2D games in your SwiftUI.

要了解SpriteView ,重要的一点是它为您提供了一种将2D游戏嵌入您的SwiftUI的简便方法。

2.0中的新增功能:TextEditor (NEW in 2.0: TextEditor)

Before WWDC 2020, we were only able to handle text editing in iOS with TextField or SecureTextField. These are basically the same text field, only the SecureTextField obscures what you are typing by replacing the characters with black circles as any password field would. The important similarity between these text fields is that they only allow a single line. This meant that the only option for multiline editing was to use UIViewRepresentable to convert UITextView from UIKit:

在WWDC 2020之前,我们只能使用TextFieldSecureTextField在iOS中处理文本编辑。 这些基本上是相同的文本字段,只有SecureTextField可以像使用任何密码字段一样用黑眼圈替换字符,从而使您键入的内容SecureTextField难懂。 这些文本字段之间的重要相似之处在于它们仅允许一行。 这意味着多行编辑的唯一选择是使用UIViewRepresentable从UIKit转换UITextView

This is relatively complicated, but it also allows many more properties. UITextView allows changes to dataDetectorTypes, which create tappable URLs from the typed text. The current text can be replaced by new text using clearsOnInsertion, and we can scroll until a specified string is visible by calling scrollRangeToVisible. For more information, check out the UITextView documentation.

这是相对复杂的,但是它也允许更多的属性。 UITextView允许对dataDetectorTypes进行更改,这些操作可以从键入的文本创建可点击的URL。 目前的案文通过新的文本使用替代clearsOnInsertion ,我们可以滚动,直到一个指定的字符串是通过调用可见scrollRangeToVisible 。 有关更多信息,请查看UITextView文档

Although we don’t have access to these properties, the new TextEditor is a big upgrade from TextField.


We can now create a multiline TextEditor just as easily as we create a single line TextField.


Anything that can be applied to Text can be applied to TextEditor. When I tried to use the new Dynamic Type syntax with a custom font, the text didn’t seem to scale according to the TextStyle. This could be a bug with the first beta or a problem with the way I did it. Either way, the best example I could make of the capabilities of TextEditor was to allow changing the font size with a Stepper and the font-weight with a Picker. A ColorPicker can be used to select the foreground (font) colour, but be aware that the background does not seem to work at the moment.

可以应用于Text任何内容都可以应用于TextEditor 。 当我尝试使用带有自定义字体的新Dynamic Type语法时,文本似乎没有根据TextStyle缩放。 这可能是第一个测试版的错误,也可能是我的操作方式存在问题。 无论哪种方式,我都能利用TextEditor的功能的最佳示例是允许使用Stepper更改字体大小,并使用Picker更改字体粗细。 可以使用ColorPicker选择前景色(字体),但是请注意,当前背景似乎不起作用。

TextEditor seems to have an opaque background of the systemBackground colour, and adding a background to it just puts a background behind this.


Your background will not be visible at all.


There is a workaround, which is probably all we can do for now:


TextEditor does support a border, though, as shown in the screenshot below.


Image for post

2.0中的新增功能:SignInWithAppleButton (NEW in 2.0: SignInWithAppleButton)

Sign in with Apple was introduced in iOS 13 as a way to securely sign in to apps without having to give an email and password. Sign in with Apple uses biometrics to authenticate that you are the person who owns your Apple ID, and then sends the app an automatically generated forwarding address and password to the app.

iOS 13中引入了使用Apple登录,这是一种无需输入电子邮件和密码即可安全登录应用的方法。 通过Apple登录使用生物识别技术来验证您是拥有Apple ID的人,然后将自动生成的转发地址和密码发送给该应用程序。

Image for post
This new button streamlines the process of adopting Sign in with Apple in your app

The following example displays the SignInWthAppleButton at the maximum size its constraints allowed (width ≤ 375). You heard right: this View has constraints. It seems that Apple just wrapped the existing ASAuthorizationAppleIDButton inUIViewRepresentable, which is exactly what we needed to do to get the button into SwiftUI last year. But if you see what steps were required to implement sign in with Apple in iOS 13, we are spared the extra task of setting up the delegate protocols in the coordinator.

下面的示例以其限制允许的最大大小(宽度SignInWthAppleButton显示SignInWthAppleButton 。 您没听错:此视图有约束。 看来Apple只是将现有的ASAuthorizationAppleIDButton包装在UIViewRepresentable ,这正是去年我们将按钮放入SwiftUI所需要的。 但是,如果您看到在iOS 13中使用Apple实施登录需要采取什么步骤,那么我们就省去了在协调器中设置委托协议的额外任务。

My version, adapted from Apple’s example, outputs the result of the authentication to a Text in the app as well as printing it. You might notice that the sign-in button doesn’t do anything the first time you try; that was certainly my experience.

我的版本(从Apple的示例改编而来)将身份验证的结果输出到应用程序中的Text并进行打印。 您可能会注意到,登录按钮在您第一次尝试时没有任何作用。 那当然是我的经验。

Tapping a second time should show the output as expected.


ProgressView(2.0中的新增功能) (ProgressView (NEW in 2.0))

UIActivityIndicatorView is a UIKit control that allows you to show a spinner for a loading state that is indeterminate. We were unable to use this directly without wrapping it in UIViewRepresentable, but now we have an equivalent! Constructing ProgressView without any parameters causes it to display a spinner, but passing it a progress value allows it to be shown as a horizontal progress bar.

UIActivityIndi​​catorView是一个UIKit控件,可让您显示不确定的加载状态的微调器。 如果不将其包装在UIViewRepresentable中,我们将无法直接使用它,但是现在我们有了一个等效的产品! 构造没有任何参数的ProgressView会使它显示微调器,但是将进度值传递给它可以将其显示为水平进度条。

Image for post

The progress bar

  • 0
  • 0
    觉得还不错? 一键收藏
  • 0


  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助




当前余额3.43前往充值 >
领取后你会自动成为博主和红包主的粉丝 规则
钱包余额 0