首页 iOS.& Swift Books Swifui由教程

2
入门 由Audrey Tam撰写

Swifui是自2014年首次宣布SWIFT以来的一些最令人兴奋的消息。迈向苹果让每个人的目标的目标是一个巨大的一步;它简化了基础知识,以便您可以花更多时间在您的用户喜悦的自定义功能上。

如果您正在阅读这本书,那么你就像我用这个新框架开发应用程序一样兴奋。本章会让您满足创建SWIFTUI应用程序的基础知识和(LIVE-)在Xcode中预览它。您将创建一个小型颜色匹配的游戏,灵感来自我们的着名 靶心 从我们的书中的应用程序 iOS.Apprentice。应用程序的目标是通过从RGB颜色空间中选择颜色来尝试和随机生成的颜色:

玩游戏
玩游戏

在本章中,您将:

  • 了解如何使用Xcode Canvas与其代码并排创建UI,并查看它们如何保持同步 - 将更改为一侧始终更新另一侧。
  • 为图像中看到的滑块创建可重复使用的视图。
  • Learn about @State variables and use them to update your UI whenever a state value changes.
  • 提醒显示用户的分数。

是时候开始了!

入门

打开 rgbullseye. 从章节材料中启动项目,并建立和运行:

uikit rgbullseye starter app
uikit rgbullseye starter app

此应用程序显示随机生成的红色,绿色和蓝色值的目标颜色。用户移动滑块使左侧颜色块匹配右侧。你即将创建一个完全相同的Swifui应用程序,但更迅速!

创建一个新的SWIFTUI项目

要启动,请创建新的Xcode项目(shift-command-n), 选择 iOS▸单视图应用程序,名称项目 rgbullseye.,然后选择 Swifui. 在里面 用户界面 menu:

选择用户界面:SWIFTUI
选择用户界面:SWIFTUI

将您的项目保存在某个地方 外部rgbullseye-starter. folder.

在项目导航器中,打开 rgbullseye. 小组看看你得到了什么: appdelegate.swift.,您可以习惯看到的,现在分成了 appdelegate.swift. Scenedelegate.swift.. The latter creates the app’s window:

Scenedelegate.swift.
Scenedelegate.swift.

SceneDelegate 本身不具体到Swifui,但这条线是:

window.rootViewController = UIHostingController(
  rootView: contentView)

UIHostingController creates a view controller for the SwiftUI view contentView, created a few lines above as an instance of ContentView.

笔记: UIHostingController enables you to 整合 Swifui视图浏览到现有应用程序。你会知道如何 第4章:“整合SWIFTUI”. When the app starts, window displays this instance of ContentView, which is defined in ContentView.swift.. It’s a struct 那 conforms to the View protocol:

struct ContentView: View {
  var body: some View {
    Text("你好世界")
  }
}

This is SwiftUI declaring that the body of ContentView contains a Text view that displays 你好世界.

预览ContentView.

Below the ContentView struct, ContentView_Previews contains a view that contains an instance of ContentView:

struct ContentView_Previews : PreviewProvider {
  static var previews: some View {
    ContentView()
  }
}

这是您可以指定预览的示例数据的位置,您可以比较不同的屏幕和字体大小。但是哪里 这 preview?

代码旁边有一个大的空白空间,顶部有这个:

预览恢复按钮
预览恢复按钮

默认情况下,预览使用当前活动方案。

点击 恢复 等待一段时间查看预览:

您好世界预览
您好世界预览

笔记:如果你没有看到 恢复 按钮,单击 编辑选项 按钮和选择 帆布:

编辑选项
编辑选项

如果你还没有看到 恢复 按钮,确保您运行MacOS Catalina(10.15)。

笔记:而不是点击 恢复 按钮,您可以使用非常有用的键盘快捷键 option-command-p。它即使是工作 恢复 在查看视图中更改某些内容后,请勿立即显示按钮。

在景观中预览

RGBullseye看起来最好的景观方向。但是,在撰写本文时,Xcode 11无法提供一种在横向中预览的简单方法。目前,您必须指定固定宽度和高度值。

Inside the static previews property, add a previewLayout modifier to ContentView():

ContentView()
  .previewLayout(.fixed(width: 568, height: 320))

这些值在横向方向上显示iPhone SEIZ窗口。

您可以在“iPhone解析”中的其他iPhone型号中找到其他iPhone型号的尺寸 bit.ly/29ce3ip..

笔记:要在此处保存一些显示空间,我将编辑器布局设置为 帆布在底部.

在景观中预览iPhone SE
在景观中预览iPhone SE

创建你的UI.

您的SWIFTUI应用程序没有故事板或视图控制器 - ContentView.swift. 接管他们的工作。您可以使用任何代码和拖动从对象库的组合来创建您的UI,并且您可以直接在代码中执行类似的故事板操作!最好的,一切都一直保持同步!

Swifui是 宣言:您声明了您希望如何查看UI,SWIFTUI将声明转换为获得作业的有效代码。 Apple鼓励您创建尽可能多的视图,以便保持代码易于阅读。尤其建议可重用的参数化视图 - 它就像将代码提取到函数中一样,您将在本章稍后创建一个。

对于本章,您将主要使用画布,类似于您在接口构建器(IB)中的UI中的布局。

一些Swifui词汇

在您潜入创建您的观点之前,您必须学习有点词汇。

  • 帆布和剧集:为了获得完整的SWIFTUI体验,您需要 Xcode 11.Macos 10.15.。然后你将能够预览您的应用程序的视图 帆布,与代码编辑器一起。也有一个 剧集 你的代码:它不会出现在我的屏幕截图中,因为我隐藏了它: 编辑▸隐藏minimap .

  • 修饰符: 您可以调用的而不是设置uikit对象的属性或属性 修改器方法 用于前景色,字体,填充等。

  • 集装箱意见: 如果 you’ve previously used stack views, you’ll find it pretty easy to create this app’s UI in SwiftUI, using HStackVStack 集装箱意见. There are other container views, including ZStack团体. You’ll learn about them in 第8章:“引入堆栈和容器”.

In addition to container views, there are SwiftUI views for many of the UIKit objects you know and love, like Text, Button滑块. The + 工具栏中的按钮显示 图书馆 of SwiftUI views.

创建目标颜色块

In RGBullsEye, the target color block, which is the color your user is trying to match, is a Color view above a Text view. But body 是 a computed property that returns a single View, so you’ll need to embed them in a container view — a VStack (vertical stack) in this scenario.

工作流程如下:

  1. 嵌入Text view in a VStack 和 edit the text.
  2. Add a Color view to the stack.

Step 1: 命令单击你好世界 Text 在画布中查看 - 注意Xcode突出显示代码行 - 并选择 嵌入vstack.:

在vstack中嵌入文本视图
在vstack中嵌入文本视图

这 canvas looks the same, but there’s now a VStack in your code.

Change "你好世界" to "Match this color": You could do this directly in the code, but, just so you know you can do this, 命令单击Text view in the canvas, and select 显示SWIFTUI检查员......:

显示Swifui Inspector for Text View
显示Swifui Inspector for Text View

然后在检查器中编辑文本:

编辑检查员中的文本
编辑检查员中的文本

您的代码更新匹配!只需乐趣,更改代码中的文本并观看画布中的更改。然后改变它。高效,对吗?

Step 2: 点击 + 工具栏中的按钮打开 图书馆。确保所选的库是 意见 然后搜索 颜色. Drag this object onto the Text view in the canvas. While dragging, move the cursor down until you see the hint 在垂直堆栈中插入颜色不是 将颜色添加到新的垂直stac ... - 但保持光标附近 最佳 of the Text view because you want to insert it 以上 这 text. Then release the Color object.

将颜色插入vstack
将颜色插入vstack

And there’s your Color view inside the VStack, in both the canvas and your code!

在Vstack中的颜色视图
在Vstack中的颜色视图

笔记:在IB中,您可以将多个对象拖到视图上,然后选择它们所有物并将其嵌入堆栈视图中。但是这个swifui. 嵌入 命令只适用于 单身的 object.

创建猜测颜色块

猜测颜色块看起来很像目标颜色块,但具有不同的文本。它需要在 右边 of the target color block; that means using an HStack (horizontal stack) as the top-most view.

在Swifui中,更容易在代码中选择嵌套对象而不是画布中的嵌套对象。

在您的代码中, 命令单击VStack 和 select 嵌入hstack..

在hstack中嵌入颜色块vstack
在hstack中嵌入颜色块vstack

笔记: 如果 命令单击 jumps to the definition of VStack, use Control-command键单击 instead.

Now copy the VStack closure, paste it inside the HStack, and change the Text 在里面 第二 VStack to "R: 127 G: 127 B: 127". Your HStack now looks like this:

HStack {
  VStack {
    Color(red: 0.5, green: 0.5, blue: 0.5)
    Text("Match this color")
  }
  VStack {
    Color(red: 0.5, green: 0.5, blue: 0.5)
    Text("R: 127  G: 127  B: 127")
  }
}

创建按钮和滑块

在原始应用程序中, 打我! 按钮和彩色滑块去了 以下 这 color blocks; again a container view is needed. To achieve the desired result, you need to put your HStack with color blocks inside a VStack.

笔记:保持 图书馆 打开, 选项 - 单击+ button.

First, in your code, embed the HStack in a VStack, then drag a Button 来自 图书馆 进入你的 代码:徘徊 略低于HStack view’s closing brace until a new line opens for you to drop the object.

option-command-p 或点击 恢复 to see your button:

添加按钮到代码
添加按钮到代码

Now that the button makes it clear where the VStack bottom edge is, you can drag a 滑块 来自 图书馆 onto your canvas, just below the Button:

将滑块插入Vstack
将滑块插入Vstack

Change the 按钮Text to "打我!" 和 set the 滑块 value to .constant(0.5)。您将了解为什么这部分中不仅仅是0.5 绑定.

这是它的样子:

按钮& Slider in VStack
按钮& Slider in VStack

笔记:如果您的滑块拇指未居中,请刷新预览(option-command-p)直到它。

好吧,是的,你需要 滑块,但滑块值将更新UI,因此您将首先设置红色滑块,然后为其他两个滑块复制它。

更新UI.

You can use “normal” constants and variables in SwiftUI, but if the UI should update when its value changes, you designate a variable as a @State variable. In SwiftUI, when a @State variable changes, the view invalidates its appearance and recomputes the body. To see this in action, you’ll ensure the variables that affect the guess color are @State variables.

Using @State variables

Add these properties at the top of struct ContentView, above the body property:

let rTarget = Double.random(in: 0..<1)
let gTarget = Double.random(in: 0..<1)
let bTarget = Double.random(in: 0..<1)
@State var rGuess: Double
@State var gGuess: Double
@State var bGuess: Double

在RGB颜色空间中,R,G和B值在0到1之间。目标颜色在游戏期间不会改变,因此其值是常量,初始化为随机值。您也可以初始化 猜测 值为0.5,但我留下了未初始化的,如果您未初始化一些变量,请向您展示您必须执行的操作。

Scroll down to the ContentView_Previews struct, which instantiates a ContentView to display in the preview. The initializer now needs parameter values for the guess values. Change ContentView() to this:

ContentView(rGuess: 0.5, gGuess: 0.5, bGuess: 0.5)

这确保了在预览视图时居中滑块的拇指。

You must also modify the initializer in SceneDelegate, in scene(_:willConnectTo:options:) — add parameters to ContentView() in this line:

let contentView = ContentView(rGuess: 0.5, gGuess: 0.5, 
  bGuess: 0.5)

当应用程序加载其根景时,滑块拇指将居中。

Updating the Color views

早在 ContentView.swift., in the VStack containing Text("Match this color"), edit the Color view to use the target values:

Color(red: rTarget, green: gTarget, blue: bTarget)

option-command-p 看一个随机目标颜色。

随机目标颜色
随机目标颜色

笔记:预览定期刷新本身,以及点击时 恢复 或实时预览按钮(即将有关此问题),因此不要惊讶地看到目标颜色变化,所有这些都经常。

同样,修改 猜测 Color 要使用猜测值:

Color(red: rGuess, green: gGuess, blue: bGuess)

当R,G和B值都是0.5时,您会获得灰色。要检查这些猜测值正在工作,请在预览中更改它们。例如:

static var previews: some View {
  ContentView(rGuess: 0.7, gGuess: 0.3, bGuess: 0.6)
    .previewLayout(.fixed(width: 568, height: 320))
}

并将预览更新视为如此:

非灰色颜色来检查猜测值
非灰色颜色来检查猜测值

这 R, G and B values in the Text view are still 127, but you’ll fix that soon.

将预览值更改回 0.5.

制作可重复使用的观点

因为滑块基本上相同,所以你将定义 滑块视图,然后 重复使用 它为另外两个滑块 - 完全正如Apple推荐的那样。

制作红色滑块

First, pretend you’re not thinking about reuse, and just create the red slider. You should tell your users its minimum and maximum values with a Text view on either side of the 滑块. To achieve this layout, you’ll need an HStack.

嵌入 the 滑块 in an HStack, then insert Text views above and below (in code) or to the left and right (in canvas). Change the Placeholder text to 0255, then update the preview to see how it looks:

滑块从0到255
滑块从0到255

笔记:你和我知道滑块从0到1,但是 255 END标签和0到255个RGB值适用于您的用户,谁可能在0到255之间的RGB值之间感觉更舒适地思考,如在十六进制表示颜色的表示中。

数字看起来狭窄,所以你会解决这个问题,也使这个外观并表现得像红色滑块一样。

Edit the slider HStack 代码 to look like this:

HStack {
  Text("0")
    .foregroundColor(.red)
  Slider(value: $rGuess)
  Text("255")
    .foregroundColor(.red)
}
.padding(.horizontal)

You’ve modified the Text views to be red, set the 滑块 value to $rGuess — the position of its thumb — and modified the HStack with some horizontal padding. But what’s with the $? You’ll find out real soon, but first, check that it’s working.

Down in the preview code, change rGuess to something different from 0.5, like 0.8,然后按 option-command-p:

滑块值0.8
滑块值0.8

Awesome — rGuess0.8,滑块拇指是正确的,你希望它在哪里!这些数字是红色的,而不是靠在边缘上。

绑定

So back to that $. It’s actually pretty 8cool and ultra-powerful for such a little symbol. rGuess by itself is just the value — read-only. $rGuess 是 a 读写 捆绑。您需要它在此处更新猜测猜测颜色,而用户正在更改滑块的值。

To see the difference, set the values in the Text view below the guess Color view: Change Text("R: 127 G: 127 B: 127") to the following:

Text("R: \(Int(rGuess * 255.0))"
  + "  G: \(Int(gGuess * 255.0))"
  + "  B: \(Int(bGuess * 255.0))")

在这里,你只是 使用 (read-only) the guess values, not changing them, so you don’t need the $ prefix.

option-command-p:

r值204 = 255 * 0.8
r值204 = 255 * 0.8

现在R值是 204. That’s 255 * 0.8, as it should be!

提取子视图

Next, the purpose of this section is to create a reusable view from the red slider HStack. To be reusable, the view needs some parameters. If you were to 复制粘贴 - 编辑 this HStack to create the green slider, you’d change .red to .green, and $rGuess to $gGuess. So those are your parameters.

命令单击 这 red slider HStack, and select 提取子视图:

提取到子视图的HSTack
提取到子视图的HSTack

这与之相同 Refactor▸提取到功能,但对于Swifui观点。

命名提取的视图 Colorslider..

笔记:在您选择之后 提取子视图 来自 menu, ExtractedSubview 是 highlighted. If you rename it while it’s highlighted, the new name appears in two places: where you extracted it from and also in the extracted subview, down at the bottom of the file. If you don’t rename it in time, then you have to manually change the name of the extracted subview in these two places.

不要担心出现的所有错误消息。完成编辑新子视图时,他们会消失。

Now add these properties at the top of Colorslider., before the body property:

@Binding var value: Double
var textColor: Color

For the value variable, you use @Binding instead of @State, because the Colorslider. view doesn’t 自己的 此数据 - 它从其父视图和突变收到初始值。

Now, replace $rGuess with $value.red with textColor:

Text("0")
  .foregroundColor(textColor)
Slider(value: $value)
Text("255")
  .foregroundColor(textColor)

这n go back up to the call to Colorslider.() 在里面 VStack, and add your parameters:

Colorslider.(value: $rGuess, textColor: .red)

检查预览仍然显示红色滑块,然后 复制粘贴 - 编辑 这行创建了其他两个滑块:

Colorslider.(value: $gGuess, textColor: .green)
ColorSlider(value: $bGuess, textColor: .blue)

更改预览代码中的猜测值,然后更新预览:

猜测值适用于滑块和猜测文本
猜测值适用于滑块和猜测文本

一切都在工作!你迫不及待地想玩游戏?马上就来!

但首先,将猜测值设置为返回 0.5 在里面 preview code.

实时预览

您不必触发模拟器播放游戏:按预览设备的右下角向下,单击 实时预览 button:

实时预览按钮
实时预览按钮

等待 预览旋转器 停止;如有必要,请单击 再试一次.

现在移动那些滑块匹配颜色!

玩游戏
玩游戏

笔记:在撰写本文时,Xcode的实时预览不使用固定宽度和高度设置。相反,它使用在项目方案中选择的模拟器设备 - 在这种情况下,iPhone 11 Pro Max。

与Uikit App如何运作相比,停止并考虑此处发生的事情。 Swifui观点 更新自己 whenever the slider values change! The UIKit app puts all that code into the slider action. Every @State variable is a 事实来源和观点依赖 状态,不是一系列事件。

这是多么令人惊奇!继续前进,做一个胜利的搭配厨房,让你最喜欢的饮料和小吃,然后回来最后一步!你想知道你的分数,不是吗?

提出警报

使用滑块获得良好的颜色匹配后,您的用户可以轻拍 打我! button, just like in the original UIKit game. And just like in the original, an Alert should appear, displaying the score.

First, add a method to ContentView to compute the score. Between the @State variables and the body, add this method:

func computeScore() -> Int {
  let rDiff = rGuess - rTarget
  let gDiff = gGuess - gTarget
  let bDiff = bGuess - bTarget
  let diff = sqrt((rDiff * rDiff + gDiff * gDiff 
    + bDiff * bDiff) / 3.0)
  return lround((1.0 - diff) * 100.0)
}

diff value is just the normalized distance between two points in three-dimensional space. You subtract it from 1, then scale it to a value out of 100. Smaller diff yields a higher score.

Next, you’ll work on your Button view:

Button(action: {}) {
  Text("打我!")
}

A Button has an action and a label, just like a UIButton. The action you want to happen is the presentation of an Alert view. But if you just create an Alert 在里面 Button action, it won’t do anything.

Instead, you create the Alert as one of the subviews of ContentView, and add a @State variable of type Bool. Then you set the value of this variable to true when you want the Alert to appear — in the Button action, in this case. The value changes to false when the user dismisses the Alert, so the Alert disappears.

So add this @State variable, initialized to false:

@State var showAlert = false

这n add this line as the Button action:

self.showAlert = true

You need the self because showAlert 是 inside a closure.

Finally, add an alert modifier to the Button, so your Button view looks like this:

Button(action: { self.showAlert = true }) {
  Text("打我!")
}
.alert(isPresented: $showAlert) {
  Alert(title: Text("Your Score"),
    message: Text(String(computeScore())))
}
.padding()

你通过了 $showAlert 捆绑 因为当用户解除警报时,其值会发生变化,并且此更改值将更改UI。

Swifui. has simple initializers for Alert views, just like the ones that many developers have created for themselves, in a UIAlertViewController extension. This one has a default OK button, so you don’t even need to include it as a parameter.

最后,您添加了一些填充,使按钮脱颖而出。

实时预览, 点击 恢复 刷新预览,然后打开 实时预览,并尝试匹配目标颜色的手:

分数!
分数!

嘿,当你有一个需要模拟器的现场预览?

笔记:当您开发自己的应用程序时,您可能会发现预览并不总是工作。如果它看起来很奇怪,或崩溃,请尝试在模拟器中运行。如果 不起作用,在设备上运行它。

挑战

挑战:创建一个swiftui应用程序

挑战/起动 文件夹包含我们的书中的“着名”靶心应用程序的Uikit版本 iOS.Apprentice。您的挑战是创建一个具有相同UI和行为的Swifui应用程序。

uikit应用程序不会为滑块使用堆栈视图,但您会发现使用堆栈创建Swifui UI真的很容易。

解决方案是 挑战/决赛 本章文件夹。

关键点

  • Xcode Canvas允许您以其代码并排创建UI,并且它们保持同步:一侧的更改始终更新另一边。
  • 您可以在代码或画布中或使用工具的任何组合创建UI。
  • 您可以使用水平和垂直堆栈组织视图对象,就像在故事板中使用堆栈视图一样。
  • 预览 允许您查看应用程序的外观和行为与不同的初始数据以及 实时预览 允许您与您的应用程序交互,而不会向上启动模拟器。
  • 您应该旨在创建可重复使用的视图。 Xcode的 提取子视图 工具很容易。
  • Swifui. updates your UI whenever a @State variable’s value changes. You pass a reference to a subview as a @Binding, allowing read-write access to the @State variable.
  • 提出警报再次轻松。

有一个技术问题?想报告一个错误吗? 您可以向官方书籍论坛中的书籍作者提出问题和报告错误 这里.

有反馈分享在线阅读体验吗? 如果您有关于UI,UX,突出显示或我们在线阅读器的其他功能的反馈,您可以将其发送到设计团队,其中表格如下所示:

© 2021 Razeware LLC