SwiftUI框架详细解析 (二十四) —— 基于SwiftUI的编辑占位符的使用(一)

版本记录

版本号 时间
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中,您会找到两个文件夹,即finalstarter。打开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) 这将创建一个用于解析quotesJSONdecoder
  • 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()
  }
}

这里的主要区别是使用ZStackprogress view定位在列表上方。 如果发生错误,进度视图将停止,表示发生了意外情况。

并非如此,通常需要大量网络请求才能检索视图所需的所有数据。 以加载购物车内容的视图为例。 该视图需要针对每个产品图片或用户评论提出要求。 到页面完全加载时,可能已经发出了数十个请求。

构建并运行。

这个ProgressView可以很长的向用户展示正在发生的事情!


Redacted Placeholders

之前,您在加载quotes时添加了两个延迟。 第一个延迟模拟对网络的初始请求。 您将使用第二个来显示较慢的视图数据加载。 这是redaction可以起作用的地方。

QuotesView中,在body内部ForEach的右花括号后立即添加以下内容:

.redacted(
  reason: viewModel.isLoading ? .placeholder : []
)

isLoadingtrue时,这将使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.swiftQuoteOfTheHourEntryView的主体:

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的编辑占位符的使用,感兴趣的给个赞或者关注~~~

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,830评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,992评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,875评论 0 331
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,837评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,734评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,091评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,550评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,217评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,368评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,298评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,350评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,027评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,623评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,706评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,940评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,349评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,936评论 2 341

推荐阅读更多精彩内容