SwiftUI学习100天(Day87 - 项目 17,第二部分)

原创链接:https://www.hackingwithswift.com/100/swiftui
以下内容仅供学习参考:

Cory House 曾经说过,“代码就像幽默。当你不得不解释它时,这很糟糕。” 我之前已经谈到过类似的事情——需要编写清晰的代码来有效地传达我们的意图是良好编程的标志,它将在未来节省许多维护和测试时间。
今天你将学习如何使用 Apple 的 Combine 框架监控通知,你会发现代码非常简单,几乎不需要任何解释——尽管它让我们可以对系统事件进行各种监控。
这并非偶然发生:Apple 花费大量时间进行API 审查,这是跨职能的开发人员团队聚在一起讨论我们所说的API表面区域——它对我们最终用户开发人员的看法根据它们采用的参数、返回的内容、命名方式、是否抛出错误以及它们如何在上下文中组合在一起。API 审查比你想象的要难,但最终结果是我们用非常少的 Swift 和 SwiftUI 代码获得了强大的功能,所以这对我们来说是一个巨大的胜利!
今天你完成三个主题,你将在其中了解 Combine 框架、Timer
阅读特定的辅助功能设置。

使用计时器重复触发事件
iOS 带有一个内置Timer
类,可以让我们定期运行代码。这使用了一个来自名为 Combine 的 Apple 框架的发布者系统。实际上,我们已经在本系列的许多应用程序中使用了 Combine 的某些部分,尽管你不太可能注意到它。例如,@Published
属性包装器和ObservableObject
协议都来自 Combine,但我们不需要知道这一点,因为当你导入 SwiftUI 时,我们也会隐式导入 Combine 的一部分。
Apple 的核心系统库称为 Foundation,它为我们提供了Data
, Date
, SortDescriptor
,UserDefaults
等等。它还为我们提供了Timer
类,该类旨在在一定秒数后运行一个函数,但它也可以重复运行代码。Combine 对此添加了一个扩展,以便计时器可以成为发布者,这是当它们的值发生变化时宣布的事情。这是@Published
属性包装器的名称来源,计时器发布者的工作方式相同:达到你的时间间隔时,Combine 将发出包含当前日期和时间的公告。
创建计时器发布者的代码如下所示:
这会同时做几件事:
它要求计时器每 1 秒触发一次。
它说计时器应该在主线程上运行。
它说计时器应该在公共运行循环上运行,这是你大部分时间都想使用的循环。(运行循环让 iOS 在用户主动做某事时处理正在运行的代码,例如在列表中滚动。)
它立即连接定时器,这意味着它将开始计时。
它将整个事物分配给常量
timer
,
以便它保持活力。
如果你还记得的话,在项目 7 中我说过“@Published
或多或少是一半@State
”——它发送其他东西可以监控的变更公告。对于像这样的常规发布者,我们需要使用一种名为onReceive()
. 这接受一个发布者作为它的第一个参数和一个运行的函数作为它的第二个参数,并且它将确保每当发布者发送其更改通知时调用该函数。
对于我们的计时器示例,我们可以像这样接收它的通知:
这将每秒打印一次时间,直到计时器最终停止。
说到停止计时器,需要一点点挖掘才能停止我们创建的计时器。你看,我们做的这个timer
属性是一个autoconnected publisher(自动连接的发布者),所以我们需要去它的upstream publisher(上游发布者)去寻找timer定时器本身。从那里我们可以连接到计时器发布者,并要求它取消自己。老实说,如果不是为了代码完成,这将很难找到,但这是它在代码中的样子:
例如,我们可以更新现有示例,使其仅触发计时器五次,如下所示:
在我们完成之前,我想向你展示一个更重要的计时器概念:如果你不介意你的计时器有一点浮动,你可以指定一些容差。这允许 iOS 执行重要的能量优化,因为它可以在其计划的触发时间和其计划的触发时间加上你指定的容差之间的任何时间点触发计时器。在实践中,这意味着系统可以执行计时器合并:它可以将你的计时器推迟一点,以便它与一个或多个其他计时器同时触发,这意味着它可以使 CPU 保持空闲状态并节省电池电量。
例如,这为我们的计时器增加了半秒的容差:
如果你需要严格计时,则不使用该tolerance
参数将使你的计时器尽可能准确,但请注意,即使没有任何容差,该类Timer
仍然是“尽力而为”——系统不保证它会准确执行。



当你的 SwiftUI 应用移至后台时如何收到提示
SwiftUI 可以检测你的应用程序何时移至后台(即,用户何时返回主屏幕),以及何时返回前台,如果你将这两者放在一起,我们就可以确保我们的应用程序暂停和恢复工作取决于用户是否可以立即看到它。
这是通过三个步骤完成的:
添加一个新属性来监视名为
scenePhase
的环境值
。用于
onChange()
观察场景相位变化。以某种方式响应新场景阶段。
你可能想知道为什么它被称为场景阶段而不是与你当前的应用程序状态有关,但请记住,在 iPad 上用户可以同时运行你的应用程序的多个实例 - 他们可以有多个窗口,称为场景,每个处于不同的状态。
要查看实际的各个场景阶段,请尝试以下代码:
当你返回时,尝试转到模拟器的主屏幕、锁定虚拟设备和其他常见活动,以查看场景相位如何变化。
可以看到,我们需要关心三个场景阶段:
活动场景正在运行,这在 iOS 上意味着它们对用户可见。在 macOS 上,一个应用程序的窗口可能会被另一个应用程序的窗口完全隐藏,但这没关系——它仍然被认为是活动的。
非活动场景正在运行并且可能对用户可见,但用户无法访问它们。例如,如果你向下滑动以部分显示控制中心,那么下方的应用程序将被视为处于非活动状态。
背景场景对用户不可见,这在 iOS 上意味着它们可能会在将来的某个时候终止。



使用 SwiftUI 支持特定的可访问性需求
SwiftUI 为我们提供了许多描述用户自定义辅助功能设置的环境属性,值得花时间阅读并遵守这些设置。
回到项目 15,我们查看了可访问性标签和提示、特征、组等,但这些设置是不同的,因为它们是通过环境提供的。这意味着 SwiftUI 会自动监视它们的变化,并会body
在其中一个发生变化时重新调用我们的属性。
例如,辅助功能选项之一是“不区分颜色”,这对 12 分之一的色盲男性很有帮助。启用此设置后,应用程序应尝试使用形状、图标和纹理而不是颜色来使其 UI 更清晰。
要使用它,只需添加一个像这样的环境属性:
这将是真或假,你可以相应地调整你的 UI。例如,在下面的代码中,我们为常规布局使用简单的绿色背景,但是当启用“无色区分”时,我们使用黑色背景并添加一个复选标记:
你可以在模拟器中进行测试,方法是转到“设置”应用并选择“辅助功能”>“显示和文本大小”>“无色区分”。
另一个常见选项是 Reduce Motion,它在模拟器中的 Accessibility > Motion > Reduce Motion 下再次可用。启用此功能后,应用程序应限制导致屏幕移动的动画量。例如,iOS 应用程序切换器使视图淡入和淡出,而不是按比例放大和缩小。
withAnimation()
对于 SwiftUI,这意味着我们应该在涉及移动时限制使用,如下所示:
我不了解你,但我发现它使用起来很烦人。幸运的是,我们可以添加一个withAnimation()
直接使用 UIKit数据的包装函数UIAccessibility
,让我们自动绕过动画:
因此,当 Reduce Motion Enabled 为真时,传入的闭包代码会立即执行,否则会使用withAnimation()
传递
. 整个throws
/rethrows
过程是更高级的 Swift 操作,但它是withAnimation()
函数签名的直接副本,因此
两者可以互换使用。
像这样使用它:
使用这种方法,你不需要每次都重复你的动画代码。
你应该考虑支持的最后一个选项是减少透明度,当启用该选项时,应用程序应该减少其设计中使用的模糊和半透明的数量,以加倍确保一切都清晰。
例如,此代码在启用“减少透明度”时使用纯黑色背景,否则使用 50% 的透明度:
这是我希望你在构建真实项目之前学习的最后一项技术,因此请将你的项目重置回其原始状态,以便我们可以从头开始。


