版本记录
版本号 | 时间 |
---|---|
V1.0 | 2021.02.13 星期六 |
前言
今天翻阅苹果的API文档,发现多了一个框架SwiftUI,这里我们就一起来看一下这个框架。感兴趣的看下面几篇文章。
1. SwiftUI框架详细解析 (一) —— 基本概览(一)
2. SwiftUI框架详细解析 (二) —— 基于SwiftUI的闪屏页的创建(一)
3. SwiftUI框架详细解析 (三) —— 基于SwiftUI的闪屏页的创建(二)
4. SwiftUI框架详细解析 (四) —— 使用SwiftUI进行苹果登录(一)
5. SwiftUI框架详细解析 (五) —— 使用SwiftUI进行苹果登录(二)
6. SwiftUI框架详细解析 (六) —— 基于SwiftUI的导航的实现(一)
7. SwiftUI框架详细解析 (七) —— 基于SwiftUI的导航的实现(二)
8. SwiftUI框架详细解析 (八) —— 基于SwiftUI的动画的实现(一)
9. SwiftUI框架详细解析 (九) —— 基于SwiftUI的动画的实现(二)
10. SwiftUI框架详细解析 (十) —— 基于SwiftUI构建各种自定义图表(一)
11. SwiftUI框架详细解析 (十一) —— 基于SwiftUI构建各种自定义图表(二)
12. SwiftUI框架详细解析 (十二) —— 基于SwiftUI创建Mind-Map UI(一)
13. SwiftUI框架详细解析 (十三) —— 基于SwiftUI创建Mind-Map UI(二)
14. SwiftUI框架详细解析 (十四) —— 基于Firebase Cloud Firestore的SwiftUI iOS程序的持久性添加(一)
15. SwiftUI框架详细解析 (十五) —— 基于Firebase Cloud Firestore的SwiftUI iOS程序的持久性添加(二)
16. SwiftUI框架详细解析 (十六) —— 基于SwiftUI简单App的Dependency Injection应用(一)
17. SwiftUI框架详细解析 (十七) —— 基于SwiftUI简单App的Dependency Injection应用(二)
18. SwiftUI框架详细解析 (十八) —— Firebase Remote Config教程(一)
19. SwiftUI框架详细解析 (十九) —— Firebase Remote Config教程(二)
20. SwiftUI框架详细解析 (二十) —— 基于SwiftUI的Document-Based App的创建(一)
21. SwiftUI框架详细解析 (二十一) —— 基于SwiftUI的Document-Based App的创建(二)
22. SwiftUI框架详细解析 (二十二) —— 基于SwiftUI的AWS AppSync框架的使用(一)
23. SwiftUI框架详细解析 (二十三) —— 基于SwiftUI的AWS AppSync框架的使用(二)
开始
首先看下主要内容:
主要就是学习基于SwiftUI的编辑占位符
(redacted placeholders)
,内容来自翻译。
下面看下写作版本:
Swift 5, iOS 14, Xcode 12
接着就是正文啦。
您是否曾经使用过需要一段时间才能加载的移动应用或网站?慢的连接速度不是很好吗?更糟的是,如果您无法确定内容是正在加载还是在加载过程中失败。
幸运的是,当花费的时间比预期更长时,有几种方法可以通知用户。最现代的方法之一是使用编辑过的占位符(redacted placeholders)
。这些是在iOS 14
中引入到SwiftUI
的。
在本教程中,您将学习:
- 如何在
SwiftUI
中利用占位符 - 为什么加载状态如此重要
- 隐藏私人用户信息的最佳做法
- 如何创建小部件
(widget)
占位符是一种更现代的方法,可以显示UI
预览。此设计模式通常在文本字段(text field)
中使用,在文本字段中,该字段显示提示,以帮助用户知道要输入的内容。
占位符的另一个优势是隐藏私人信息的能力。当应用进入后台时,金融应用通常会执行此操作。在SwiftUI
中,显示占位符比创建单独的视图来隐藏敏感信息要容易得多。
因此,事不宜迟,现在该学习如何做到这一点了!
打开入门项目。
在ZIP
中,您会找到两个文件夹,即final
和starter
。打开starter
文件夹。该项目由一个应用程序组成,该应用程序显示带有应用程序名称Quotation
的标题。
该项目包含一个包含激励引号的JSON
文件。该文件位于Supporting Files / quotes.json
。每个quote
都有ID
,日期和图标名称,以及quote
本身。您可以在Shared / Quote.swift
中找到此数据的数据模型。该数据集中的quote
来自motivationping.com。
本教程的目的是展示软件中加载状态的重要性。它将显示如何在应用程序和iOS 14 widget
中执行此操作。
Requesting Quotes
您需要做的第一件事就是将quotes
加载到应用程序中。打开位于App / QuotesViewModel.swift
的视图模型。这是您加载quotes
的地方。将以下属性添加到QuotesViewModel
的顶部:
@Published var isLoading = false
@Published var quotes: [Quote] = []
第一个属性确定内容是否正在加载。 第二个是应用程序将显示的quotes
数组。 由于该应用将包含一个小widget
,因此最好共享加载逻辑。
打开Shared / ModelLoader.swift
并将bundledQuotes
的内容更改为以下内容:
// 1
guard let url = Bundle.main
.url(forResource: "quotes", withExtension: "json")
else {
return []
}
// 2
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
decoder.dateDecodingStrategy = .secondsSince1970
do {
// 3
let data = try Data(contentsOf: url)
return try decoder.decode([Quote].self, from: data)
} catch {
print(error)
return []
}
上面的代码正在执行以下操作:
- 1) 这是
JSON
文件的路径。 - 2) 这将创建一个用于解析
quotes
的JSONdecoder
。 - 3) 解码器
(decoder)
尝试从文件中读取数据,然后将解码后的quote
数组返回到bundleQuotes
。
现在,您可以读取bundled
的数据。 回到QuotesViewModel.swift
并将以下内容添加到类的末尾:
init() {
withAnimation {
self.quotes = ModelLoader.bundledQuotes
}
}
该初始化程序负责通过调用新方法来从磁盘加载quotes
。
由于没有用于显示quotes
的界面,因此什么也不会显示。 不用担心,接下来,您将继续进行。
打开App / QuotesView.swift
并在body
下添加以下内容:
private func row(from quote: Quote) -> some View {
// 1
HStack(spacing: 12) {
// 2
Image(systemName: quote.iconName)
.resizable()
.aspectRatio(nil, contentMode: .fit)
.frame(width: 20)
// 3
VStack(alignment: .leading) {
Text(quote.content)
.font(
.system(
size: 17,
weight: .medium,
design: .rounded
)
)
Text(quote.createdDate, style: .date)
.font(
.system(
size: 15,
weight: .bold,
design: .rounded
)
)
.foregroundColor(.secondary)
}
}
}
通过代码:
- 1) 该行视图在左侧显示一个图标,在右侧显示文本。
- 2) 包的
JSON
文件中的quote
数据包含 SF Symbols。 该图像Image
将显示所需的符号。 - 3)
quote
及其日期显示在一个上方。
现在,将以下内容添加到body
中的List
块中:
ForEach(viewModel.quotes) { quote in
row(from: quote)
}
这将遍历quotes
并将其加载到视图中。 构建并运行。
太好了,您现在可以看到bundled quote
了!
Showing Progress
在理想情况下,信息会立即加载,并且不会发生任何错误。 但是,在蜂窝网络和复杂的服务器端代码的作用下,可能会出错,因此,请务必使这种情况对用户来说尽可能的平稳。
甚至本地信息也可能需要一些时间来加载。 数据库查询导致超过100,000
个项目至少需要几秒钟的时间。 当前,quotes
加载没有延迟或问题。 但是,就本教程而言,您将添加人为延迟来模拟缓慢的网络连接。
打开QuotesViewModel.swift
并在init()
下面添加以下内容:
private func delay(interval: TimeInterval, block: @escaping () -> Void) {
DispatchQueue.main.asyncAfter(deadline: .now() + interval) {
block()
}
}
此帮助程序方法在指定的时间段后使用 Grand Central Dispatch运行block
。 您将使用它来延迟加载过程的某些部分。
接下来,用以下内容替换init()
的内容:
isLoading = true
let simulatedRequestDelay = Double.random(in: 1..<3)
delay(interval: simulatedRequestDelay) {
withAnimation {
self.quotes = ModelLoader.bundledQuotes
}
let simulatedIngestionDelay = Double.random(in: 1..<3)
self.delay(interval: simulatedIngestionDelay) {
self.isLoading = false
}
}
此代码在mix
中增加了两个延迟。 您在此处更新isLoading
属性以添加额外的进度。 两个随机数可帮助模拟真实的工作场景。
构建并运行
现在,在quotes
加载之前,您将看到一个空视图。 在生产应用程序中,这样的行为会使用户感到困惑。 目前尚不清楚是否正在发生或失败。
spinner
是用于传达正在加载数据的最常见UI模式之一。 在引入iPhone X
之前,显示加载spinner
的最简单方法是UIApplication.isNetworkActivityIndicatorVisible
。 其他流行的样式包括加载条,模糊和占位符。
您要做的第一个改进是添加一个加载指示器。 在UIKit
中称为UIActivityIndicatorView
,在SwiftUI
中称为ProgressView
。 视图模型已经设置完成。
打开QuotesView.swift
,然后在QuotesView
中用以下内容替换body
的内容:
ZStack {
NavigationView {
List {
ForEach(viewModel.quotes) { quote in
row(from: quote)
}
}
.navigationTitle("Quotation")
}
if viewModel.quotes.isEmpty {
ProgressView()
}
}
这里的主要区别是使用ZStack
将progress view
定位在列表上方。 如果发生错误,进度视图将停止,表示发生了意外情况。
并非如此,通常需要大量网络请求才能检索视图所需的所有数据。 以加载购物车内容的视图为例。 该视图需要针对每个产品图片或用户评论提出要求。 到页面完全加载时,可能已经发出了数十个请求。
构建并运行。
这个ProgressView
可以很长的向用户展示正在发生的事情!
Redacted Placeholders
之前,您在加载quotes
时添加了两个延迟。 第一个延迟模拟对网络的初始请求。 您将使用第二个来显示较慢的视图数据加载。 这是redaction
可以起作用的地方。
在QuotesView
中,在body
内部ForEach
的右花括号后立即添加以下内容:
.redacted(
reason: viewModel.isLoading ? .placeholder : []
)
当isLoading
为true
时,这将使List
中的每一行都显示为redacted
。
构建并运行。
在SwiftUI
中隐藏视图的部分很容易:编辑修饰符(redacted modifier)
将隐藏标签,直到加载完成。 此修饰符可为您创建出色的占位符视图。
在某些情况下,自动占位符视图不是最有效的,因为您可能希望使某些视图始终显示。 对于您来说幸运的是,Apple想到了这一点!
将row(from:)
中的Image
更改为以下内容:
Image(systemName: quote.iconName)
.resizable()
.aspectRatio(nil, contentMode: .fit)
.frame(width: 20)
.unredacted()
构建并运行
现在,图标图像始终显示。
.unredacted()
与.redacted()
完美互补。 但是在此示例中,如果quote
需要额外的网络请求来获取其数据,则quote
可能会花费更长的时间。
Concealing User Data
虽然大多数应用程序使用帐户来存储有关使他们受益的用户的信息,但信息是私人的,未经同意不得共享。
例如,跟踪您的投资的股票交易应用程序应该是安全的。 在防止敏感信息被窥视时也应该考虑周全。 这些应用程序使用的一种常见技巧是在关闭应用程序时隐藏您的信息。
这将是您应用程序的一个不错的补充。 quotes
并不像您的财务记录那么重要,但暂时,假装它们是关键。
为了达到这种效果,您将重用一些现有的逻辑。
打开QuotesViewModel.swift
并在类顶部添加一个新属性:
@Published var shouldConceal = false
之后,将这三个新方法添加到类中:
private func beginObserving() {
// 1
let center = NotificationCenter.default
center.addObserver(
self,
selector: #selector(appMovedToBackground),
name: UIApplication.willResignActiveNotification,
object: nil
)
center.addObserver(
self,
selector: #selector(appMovedToForeground),
name: UIApplication.didBecomeActiveNotification,
object: nil
)
}
@objc private func appMovedToForeground() {
// 2
shouldConceal = false
}
@objc private func appMovedToBackground() {
// 3
shouldConceal = true
}
这是在做的:
- 1) 两个
NotificationCenter
观察者将侦听应用程序状态的通知。 - 2) 当应用程序移至前台时,它会显示
quotes
。 - 3) 当应用关闭时,它会隐藏
quotes
。
在init()
的顶部调用beginObserving
:
beginObserving()
继续,在QuotesViewModel
的顶部,添加以下计算的属性:
var shouldHideContent: Bool {
return shouldConceal || isLoading
}
现在,您将使用此新属性shouldConceal
以及现有的isLoading
属性来更新视图。 如果任何一个为true
,则视图将隐藏用户的内容。
返回QuotesView.swift
,更改redacted modifier
以使用新的计算属性:
.redacted(
reason: viewModel.shouldHideContent ? .placeholder : []
)
构建并运行
现在,当您的应用进入后台时,没有人可以看到您的宝贵quotes
!当您使应用再次进入前台时,您的quotes
将被恢复。
在许多情况下,使用编辑功能redacted
创建占位符视图效果很好。它不仅看起来不错,而且易于使用和自定义。它也适用于模板视图和加载指示器。
苹果公司创建编辑视图(redacted view)
的意图是将其用于经过改进的主屏幕小部件(Home Screen widgets)
。这就是您接下来要介绍的内容!
Creating a Widget
Apple在iOS 14
中重新引入了widgets
。以前,它们仅在Today View
中显示。小部件的旧实现没有良好的加载状态。他们开始空白,通常需要一段时间才能加载。
现在,小部件位于主屏幕(Home Screen)
的前面和中央,因此,需要更好的加载状态。该解决方案是适用于任何视图View
的直接修饰符。由于任何第三方应用程序都可以提供自己的窗口小部件widget
,因此这种通用解决方案是完美的。
此应用程序中的小部件每小时显示一个新报价(quote)
。该设计将像主应用程序一样工作。在系统显示占位符的情况下,您仍希望显示该图标。
导航到Widget / QuoteOfTheHour.swift
并将getTimeline(in:completion :)
的实现更改为以下内容:
var entries: [QuoteEntry] = []
// 1
var quotes = ModelLoader.bundledQuotes
let calendar = Calendar.current
let currentDate = Date()
for hourOffset in 0..<24 {
// 2
guard let entryDate = calendar.date(
byAdding: .hour,
value: hourOffset,
to: currentDate)
else {
continue
}
// 3
guard let randomQuote = quotes.randomElement() else {
continue
}
// 4
if let index = quotes.firstIndex(of: randomQuote) {
quotes.remove(at: index)
}
entries.append(QuoteEntry(model: randomQuote, date: entryDate))
}
// 5
let timeline = Timeline(entries: entries, policy: .atEnd)
completion(timeline)
在上面的代码中:
- 1)
Quotes
是从共享模型加载器中提取的。 - 2) 计划在接下来的24小时内为每个小时提供新的
quote
。 - 3)
quote
是随机选择的。 - 4) 所选
quote
被删除,因此计划quote
是唯一的。 - 5) 使用24个选定的
quote
设置时间线。
现在已经有数据可以使用,下一步就是设计小部件。
用以下内容替换QuoteOfTheHour.swift
中QuoteOfTheHourEntryView
的主体:
VStack(alignment: .leading) {
HStack {
Image(systemName: entry.model.iconName)
.resizable()
.aspectRatio(nil, contentMode: .fit)
.frame(width: 12)
Spacer()
Text(entry.model.createdDate, style: .date)
.font(
.system(
size: 12,
weight: .bold,
design: .rounded
)
)
.foregroundColor(.secondary)
.multilineTextAlignment(.trailing)
}
Text(entry.model.content)
.font(
.system(
size: 16,
weight: .medium,
design: .rounded
)
)
Spacer()
}
.padding(12)
这类似于主应用程序中使用的行,不同之处在于字体大小和图标较小。
构建并运行。 关闭应用程序,然后将小部件添加到主屏幕,如下所示:
最后一件事是确保小部件中的图标始终显示。 由于此应用使用SF Symbols
作为图标,因此始终可以访问它们。 操作系统会从时间轴中加载小部件的quote
和日期,因此在此期间将对其进行编辑。
与主应用程序一样,单个修改器将确保显示图标。
将Image
替换为以下内容:
Image(systemName: entry.model.iconName)
.resizable()
.aspectRatio(nil, contentMode: .fit)
.frame(width: 12)
.unredacted()
构建并运行
系统加载小部件时,它将显示一个带有图标的占位符。
注意:窗口小部件应立即加载到模拟器中。 一个编辑修饰符
(redacted modifier)
已添加到此屏幕快照的小部件中。
现在,该小部件在所有情况下都看起来不错。
回顾一下您学到的知识:
- 空白的加载视图会造成混乱。
- 简单的微调器
spinners
在大多数情况下是有用的。 - 占位符甚至更好,看起来很棒。
- 占位符不仅可以很好地用于加载,而且对于隐藏数据也非常有用。
有关更多信息,请参阅Apple的redaction文档。 此外,WWDC2020中的小部件视频Build SwiftUI views for widgets。
要了解有关SwiftUI的更多信息,请查看other raywenderlich.com tutorials。
后记
本篇主要讲述了基于SwiftUI的编辑占位符的使用,感兴趣的给个赞或者关注~~~