版本记录
版本号 | 时间 |
---|---|
V1.0 | 2019.06.14 星期五 |
前言
SwiftUI是2019年WWDC新推出的UI相关的API,相信大家都已经知道并想体验一下,下面我们就去一起了解和学习了。
开始
首先看下写作环境
Swift 5, iOS 13, Xcode 11
在这个SwiftUI
教程中,您将学习如何通过声明和修改视图来布局UI,以及如何使用状态变量来更新UI。 您将使用Xcode的新预览和实时预览,体验保持代码和WYSIWYG
布局同步的乐趣。
自Apple于2014年宣布推出Swift以来,SwiftUI
是最激动人心的消息。这是迈向Apple实现每个人都可以编码,简化基础知识的目标迈出了一大步,因此您可以将更多时间花在满足用户需求的自定义功能上。一些开发人员开玩笑说他们可能被SwiftUI
Sherlocked了!
SwiftUI
允许您忽略Interface Builder(IB)
和storyboards
,而无需编写详细的逐步说明来布置UI。 IB
和Xcode
在Xcode 4
之前是单独的应用程序,每次编辑IBAction
或IBOutlet
的名称或从代码中删除它时,接缝仍会显示,并且您的应用程序崩溃,因为IB
没有看到代码更改。或者你已经对你必须在你的代码中使用的segues
或表格视图单元格的字符串类型标识符感兴趣,但Xcode无法检查你,因为它们是字符串。而且,虽然在WYSIWYG
编辑器中设计新UI可能更快更容易,但在用代码编写时,复制或编辑UI会更有效率。
SwiftUI
来了可以做好这点!您可以与其代码并排预览SwiftUI
视图 - 对一侧的更改将更新另一侧,因此它们始终保持同步。没有任何标识符字符串出错。它是代码,但比你为UIKit
编写的要少得多,因此它更容易理解,编辑和调试。
SwiftUI
不会取代UIKit
- 比如Swift
和Objective-C
,你可以在同一个应用程序中使用它们。您将无法在macOS
上运行SwiftUI iOS
应用程序 - 这就是Catalyst。但是,SwiftUI API
在不同平台上是一致的,因此在多个平台上使用相同的源代码开发相同应用程序将更容易。
在本教程中,您将使用SwiftUI
从iOS Apprentice
构建我们著名的BullsEye
游戏的变体。您将学习如何通过声明和修改视图来布局UI,以及如何使用状态变量来更新UI。您将使用Xcode的一些新工具,尤其是预览和实时预览,并体验保持代码和WYSIWYG
布局同步的乐趣。
注意:本教程假设您习惯使用Xcode开发iOS应用程序。 你需要
Xcode 11 beta
。 要查看SwiftUI
预览,您需要macOS 10.15 beta
。 因此,使用beta
版软件也需要一定的舒适度。如果您没有备用Mac,则可以 install Catalina beta on a separate APFS volume。
下载项目,并在RGBullsEye-Starter
文件夹中构建并运行UIKit应用程序。 此游戏使用三个滑块sliders
- RGB颜色空间中的红色,绿色和蓝色值 - 以匹配目标颜色。
我为RWDevCon 2016
编写了这个应用程序,并在本教程中将代码更新为Swift 5
。 它运行在Xcode 10
和Xcode 11 beta
中。 在本教程中,您将使用SwiftUI
创建此游戏的基本版本。
在Xcode 11 beta
中,创建一个新的Xcode项目(Shift-Command-N)
,选择iOS▸Single View App
,将项目命名为RGBullsEye
,然后选中Use SwiftUI
复选框:
将项目保存在RGBullsEye-Starter
文件夹之外的某个位置。
Entering the New World of SwiftUI
在项目导航器中,打开RGBullsEye
组以查看您的内容:旧的AppDelegate.swift
现在拆分为AppDelegate.swift
和SceneDelegate.swift
,SceneDelegate
具有window
:
SceneDelegate
不是特定于SwiftUI
,但这一行是:
window.rootViewController = UIHostingController(rootView: ContentView())
UIHostingController
为SwiftUI
视图ContentView
创建一个视图控制器。
注意:
UIHostingController
使您可以将SwiftUI
视图集成到现有应用程序中。 您可以在storyboard
中添加一个Hosting View Controller
,并从UIViewController
创建一个segue
。 然后按住Ctrl键从segue
拖动到视图控制器代码中以创建IBSegueAction
,您可以在其中指定hosting controller
的rootView
值 -SwiftUI
视图。
当应用程序启动时,window
会显示ContentView
的一个实例,该实例在ContentView.swift
中定义。 它是一个符合View
协议的struct
:
struct ContentView: View {
var body: some View {
Text("Hello World")
}
}
这是SwiftUI
声明ContentView
的主体body
包含显示Hello World
的Text
视图。
在DEBUG
块中,ContentView_Previews
生成一个包含ContentView
实例的视图。
#if DEBUG
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
#endif
您可以在此处为预览指定样本数据。 但是这个预览在哪里好像听过?
代码旁边有一个很大的空白区域,位于顶部:
单击Resume
,稍等片刻以查看预览:
注意:如果没有看到
Resume
按钮,请单击editor options
按钮,然后选择Editor and Canvas
:
如果您仍然没有看到
Resume
按钮,请确保您正在运行macOS 10.15 beta
。
Outlining Your UI
您没有看到的一个文件是Main.storyboard
- 您将在SwiftUI
代码中创建您的UI,密切关注预览以查看它的外观。 但不要担心 - 您不会编写数百行代码来创建视图!
SwiftUI
是声明性的:您声明了UI的外观,SwiftUI将您的声明转换为可以完成工作的高效代码。 Apple鼓励您根据需要创建任意数量的视图,以使代码易于阅读和维护。 特别推荐使用可重用的参数化视图 - 就像将代码提取到函数中一样,您将在本教程的后面创建一个。
RGBullsEye
的UI有很多子视图,所以你首先要创建一个outline
,使用Text
视图作为占位符。
首先用以下代码替换Text(“Hello World”)
:
Text("Target Color Block")
如有必要,请单击Resume
以刷新预览。
现在Command-Click
预览中的Text
视图,然后选择Embed in HStack
:
请注意您的代码更新以匹配:
HStack {
Text("Target Color Block")
}
你在这里使用horizontal stack
是因为你想要显示目标并且并排猜测颜色块。
复制并粘贴Text
语句,然后对其进行编辑,使HStack
如下所示。 请注意,您不要使用逗号分隔两个语句 - 只需在各自的行中写入:
HStack {
Text("Target Color Block")
Text("Guess Color Block")
}
它在预览中:
现在准备通过在VStack
中嵌入HStack
来添加滑块占位符 - 这次,在代码中按命令单击HStack
:
选择Embed in VStack
,出现新代码,但预览不会更改 - 您需要在颜色块下面添加视图。
在HStack
闭包下面打开一个新行,单击工具栏中的+
按钮打开Library
,然后将Vertical Stack
拖到新行中:
正如您所期望的那样,代码和预览更新:
注意:迷你地图不会出现在我的屏幕截图中,因为我隐藏了它:
Editor ▸ Hide Minimap
。
现在完成大纲,看起来像这样:
VStack {
HStack {
Text("Target Color Block")
Text("Guess Color Block")
}
Text("Hit me button")
VStack {
Text("Red slider")
Text("Green slider")
Text("Blue slider")
}
}
新的VStack
将包含三个滑块sliders
,颜色块和滑块之间会有一个按钮。
注意:
Xcode 11
是测试版软件,在编写本教程时,很难弄清楚我做错了什么。它有一些无用的错误消息,包括Unable to infer complex closure return type; add explicit type to disambiguate, ‘(LocalizedStringKey) -> Text’ is not convertible to ‘(LocalizedStringKey, String?, Bundle?, StaticString?) -> Text’ and Function declares an opaque return type, but has no return statements in its body from which to infer an underlying type, which basically meant “huh??”
。这些消息很少出现在我刚刚添加的代码附近。如果您看到的消息似乎没有帮助,或者指向以前完全正常的代码,请尝试注释掉刚才添加的内容。还要检查拼写并确保您的大括号和括号匹配。最后,在beta
期间重新启动Xcode总是一个好主意!
Filling in Your Outline
现在,练习你的新SwiftUI-fu
技能,开始填充HStack
颜色块,所以它看起来像这样:
HStack {
// Target color block
VStack {
Rectangle()
Text("Match this color")
}
// Guess color block
VStack {
Rectangle()
HStack {
Text("R: xxx")
Text("G: xxx")
Text("B: xxx")
}
}
}
每个颜色块都有一个Rectangle
。 目标颜色块在其Rectangle
下方有一个Text
视图,而guess color block
有三个Text
视图 - 在本教程的后面,您将替换每个xxx
以显示当前滑块值。
不要担心黑色矩形占据场景 - 它们会为滑块腾出空间,你现在就可以设置它们的前景颜色了。
Using @State Variables
您可以在SwiftUI
中使用normal
常量和变量,但是如果UI在其值发生更改时应该更新,则将变量指定为@State
变量。 这个游戏都是关于颜色的,所以影响guess rectangle
颜色的所有变量都是@State
变量。
在struct ContentView
的顶部,在body
闭包上方添加这些行:
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
R,G和B值介于0和1之间。将目标值初始化为随机值。 您也可以将猜测值初始化为0.5,但如果您没有初始化某些变量,我会将它们保留为未初始化以显示您必须执行的操作。
向下滚动到DEBUG
块,该块实例化要在预览中显示的ContentView
。 初始化程序现在需要猜测值的参数值。 将ContentView()
更改为:
ContentView(rGuess: 0.5, gGuess: 0.5, bGuess: 0.5)
创建滑块时,它们将以居中显示在预览中。
您还必须修改SceneDelegate
中的初始值,在scene(_:willConnectTo:options:)
,将ContentView()
替换成下面这行:
window.rootViewController = UIHostingController(rootView:
ContentView(rGuess: 0.5, gGuess: 0.5, bGuess: 0.5))
当应用加载其根场景时,滑块将居中。
现在将前景色修改器添加到目标Rectangle
:
Rectangle()
.foregroundColor(Color(red: rTarget, green: gTarget, blue: bTarget, opacity: 1.0))
修改器.foregroundColor
创建一个新的Rectangle
视图,现在使用由随机生成的RGB值指定的前景颜色。
同样,修改guess Rectangle
:
Rectangle()
.foregroundColor(Color(red: rGuess, green: gGuess, blue: bGuess, opacity: 1.0))
当R,G和B值均为0.5时,您会得到灰色。
单击Resume
,稍等片刻以进行预览更新。
注意:预览会定期刷新,以及单击
Resume
或“实时预览”按钮时,所以不要惊讶地看到目标颜色经常变化。
Making Reusable Views
我向几个人展示了这个游戏,他们发现它很容易让人上瘾 - 特别是平面设计师。 然后他们会要求我实现其他颜色空间之一,比如YUV。 但RGB是本教程的不错选择,因为滑块基本相同,因此您将定义一个滑块视图,然后将其重用于其他两个滑块。
首先,假装你没有考虑重用,只需创建红色滑块。 在滑块VStack
中,用这个HStack
替换Text(“Red slider”)
占位符:
HStack {
Text("0").color(.red)
Slider(value: $rGuess, from: 0.0, through: 1.0)
Text("255").color(.red)
}
您可以修改Text
视图以将文本颜色更改为红色。 并且您使用值 - thumb
的位置 - 在from
和through
值之间的范围内初始化Slider
。
但是什么是$
? 你刚刚适应了吗?
和!
对于期权,现在是$
?
对于这样一个小符号来说,它实际上非常酷而且非常强大。rGuess
本身就是值 - 只读。 $ rGuess
是一个读写绑定 - 你需要它,在用户更改滑块的值时更新猜测矩形的前景色。
要查看差异,请在猜测矩形下方的三个Text
视图中设置值:
HStack {
Text("R: \(Int(rGuess * 255.0))")
Text("G: \(Int(gGuess * 255.0))")
Text("B: \(Int(bGuess * 255.0))")
}
在这里,您只使用值而不是更改它们,因此您不需要$
前缀。
注意:您和我知道滑块从0变为1,但
255
结束标签和0到255 RGB值适用于您的用户,他们可能会觉得更喜欢在0到255之间的RGB值,如十六进制颜色的表示。
等待预览刷新,看到你的第一个滑块:
为了腾出空间,颜色块略有缩小,但是滑块看起来仍然有点局促 - 末端标签太靠近窗口边缘 - 所以在HStack
中添加一些padding
(另一个修改器):
HStack {
Text("0").color(.red)
Slider(value: $rGuess, from: 0.0, through: 1.0)
Text("255").color(.red)
}.padding()
这就好多了
现在,如果您要复制粘贴编辑此HStack
以创建绿色滑块,则将.red
更改为.green
,将$ rGuess
更改为$ gGuess
。 这就是参数的所在。
Command-Click
红色滑块HStack
,然后选择Extract Subview
:
这与Refactor ▸ Extract to Function
的工作方式相同,但适用于SwiftUI
视图。
不要担心出现的所有错误消息 - 当您编辑完新的子视图后,它们会消失。
命名ExtractedView
ColorSlider
,然后在body
闭合之前在顶部添加这些行:
@Binding var value: Double
var textColor: Color
然后用$ value
替换$ rGuess
,用textColor
替换.red
:
Text("0").color(textColor)
Slider(value: $value, from: 0.0, through: 1.0)
Text("255").color(textColor)
然后返回到VStack
中对ColorSlider()
的调用,并添加您的参数:
ColorSlider(value: $rGuess, textColor: .red)
检查预览是否显示红色滑块,然后复制粘贴编辑此行以将Text
占位符替换为其他两个滑块:
ColorSlider(value: $gGuess, textColor: .green)
ColorSlider(value: $bGuess, textColor: .blue)
单击Resume
以更新预览:
注意:您可能已经注意到您经常单击
Resume
。 如果您不想从键盘上把手拿开,Option-Command-P
将是您学习的最有用的键盘快捷键之一!
这就是整个应用程序的完成!
现在有趣的事情:在预览设备的右下角,点击实时预览(live preview)
按钮:
实时预览可让您与预览进行交互,就像您的应用程序在模拟器中运行一样 - 太棒了!
等待预览微调器(Preview spinner)
停止,如有必要,请单击Try Again
。
现在移动那些滑块以匹配颜色!
精彩! 您是否喜欢Goodnight Developers video from the WWDC Keynote中的程序员? 太令人满意了!
Presenting an Alert
在使用滑块获得良好的颜色匹配后,您的用户可以点击Hit Me
按钮,就像在原始的BullsEye
游戏中一样。 就像在BullsEye
中一样,会出现一个Alert
,显示分数。
首先,向ContentView
添加一个方法来计算分数。 在@State
变量和body
之间,添加以下方法:
func computeScore() -> Int {
let rDiff = rGuess - rTarget
let gDiff = gGuess - gTarget
let bDiff = bGuess - bTarget
let diff = sqrt(rDiff * rDiff + gDiff * gDiff + bDiff * bDiff)
return Int((1.0 - diff) * 100.0 + 0.5)
}
diff
值只是三维空间中两点之间的距离 - 用户的错误。 要获得分数,请从1减去diff
,然后将其缩放到100
以外的值。较小的diff
会产生较高的分数。
接下来,使用Button
视图替换Text(“Hit me button”)
占位符:
Button(action: {
}) {
Text("Hit Me!")
}
Button
有一个动作和一个标签,就像一个UIButton
。 您希望发生的操作是提供Alert
视图。 但是如果你只是在Button
动作中创建一个Alert
,它将不会做任何事情。
而是将Alert
创建为ContentView
的子视图之一,并添加Bool
类型的@State
变量。 然后,在希望显示Alert
时将此变量的值设置为true
- 在这种情况下,在Button
操作中。 当用户移除Alert
时,该值将重置为false
。
所以添加这个@State
变量,初始化为false
:
@State var showAlert = false
然后将此行添加为Button
操作:
self.showAlert = true
你需要self
,因为showAlert
在一个闭包内。
最后,向Button
添加一个presentation
修饰符,这样你的Button
视图如下所示:
Button(action: {
self.showAlert = true
}) {
Text("Hit Me!")
}
.presentation($showAlert) {
Alert(title: Text("Your Score"), message: Text("\(computeScore())"))
}
您传递绑定$ showAlert
,因为当用户移除alert
时,其值将更改。
SwiftUI
具有用于Alert
视图的简单初始化器,就像许多开发人员在UIAlertViewController
扩展中为自己创建的初始化器一样。 这个有一个默认的OK
按钮,所以你甚至不需要将它作为参数包含在内。
关闭live preview
,单击Resume
以刷新预览,然后启用实时预览live preview
,并尝试匹配目标颜色:
嘿,当你有实时预览时,谁需要iOS模拟器? 尽管如果您在模拟器中运行应用程序,可以将其旋转为横向:
本教程几乎没有涉及SwiftUI
的表面,但您现在已经了解了如何使用一些新的Xcode工具来布局和预览视图,以及如何使用@State
变量来更新UI。更不用说那个惊人的Alert
了!
您现在已做好充分准备,深入了解Apple的丰富资源 - 其教程和WWDC会议。教程tutorials和WWDC会议通过不同的示例项目。例如,Introducing SwiftUI
(#204)为会议构建了一个列表应用程序 - 它比教程的Landmarks
应用程序更简单。 SwiftUI Essentials
(#216)向您展示了如何使用Form
容器视图轻松获取iOS
表单的外观。
为了简化本教程,我没有为RGB颜色创建数据模型。但大多数应用程序将其数据建模为结构体或类。如果需要SwiftUI
来跟踪模型实例中的更改,则必须通过实现发布更改事件的didChange
属性来符合BindableObject
。看看Apple的示例项目,特别是Data Flow Through SwiftUI
(#226)
为了使自己更容易进入SwiftUI,您可以将SwiftUI
视图添加到现有应用程序,或者在新的SwiftUI应用程序中使用现有视图 - 观看Integrating SwiftUI
(#231)以查看这样做的快捷方式。
另外,浏览SwiftUI documentation以查看其他可用内容 - 有很多内容!
后记
本篇主要讲述了SwiftUI相关,感兴趣的给个赞或者关注~~~