在 Apple Watch 上的 SwiftUI 中,更新描述自具有 Always On 状态的日期以来的间隔的字符串的最佳方法是啥?
Posted
技术标签:
【中文标题】在 Apple Watch 上的 SwiftUI 中,更新描述自具有 Always On 状态的日期以来的间隔的字符串的最佳方法是啥?【英文标题】:In SwiftUI on Apple Watch, what is the best way to update a String that describes the interval since a Date with Always On State?在 Apple Watch 上的 SwiftUI 中,更新描述自具有 Always On 状态的日期以来的间隔的字符串的最佳方法是什么? 【发布时间】:2021-09-29 21:22:33 【问题描述】:我正在构建一个使用 SwiftUI 的 Apple Watch 应用。
新数据会定期从外部来源进入应用程序,我会使用 ObservableObject
(store
) 中的 @Published
Date
(称为 lastUpdatedDate
)跟踪上次发生的时间。
在应用程序中,我想使用 SwiftUI Text
结构向用户指示数据是多久前更新的。
我正在权衡不同的选项,我想知道这样的最佳做法是什么。
解决方案 #1 - Text.DateStyle
我尝试的一个非常简单的方法是使用Text.DateStyle.relative
:
Text(store.lastUpdatedDate, style: .relative)
这在某种程度上达到了我想要的效果,因为它使文本在日期发生后的相对间隔内保持最新,但 the text is not customizable。我不希望它显示秒数,只显示分钟或小时。
解决方案 #2 - 计算属性
在我想要显示Text
的View
内,我可以访问ObservableObject
,并添加了一个计算属性,该属性使用一个函数将Date
转换为String
的相对值日期(例如,5 分钟前)在 Date
上使用 extension
和 RelativeDateTimeFormatter
计算属性:
private var lastUpdatedText: String
store.lastUpdatedDate.timeAgoDisplay()
日期扩展:
extension Date
func timeAgoDisplay() -> String
let formatter = RelativeDateTimeFormatter()
formatter.unitsStyle = .full
return formatter.localizedString(for: self, relativeTo: Date())
这似乎是迄今为止我找到的最佳解决方案,因为它会在每次打开应用程序时自动更新。然而,缺点是它似乎只在应用程序进入前台时更新(当另一个应用程序或钟面之前处于活动状态时)。这可能很好,但在具有常亮显示屏的 5 系列、6 系列和 7 系列上可能会更好。在 watchOS 8 上,使用 Always On State,当手腕放下和再次抬起手腕时,此文本会保留在屏幕上。文本可能会过时,因为这些事件不会导致 lastUpdatedText
计算属性再次更新。
更新:这实际上不是一个很好的解决方案。通过更多测试,我发现每次打开应用程序时都会更新它是侥幸。它只是在应用打开时重新计算属性,因为视图中的其他项目正在刷新,并且它本身不会在每次打开应用时实际重新计算。
其他可能的解决方案
我考虑在ObservableObject
中添加第二个@Published
变量,这是一个包含相对时间间隔的String
。这样做的缺点是我必须手动初始化和更新该变量。如果这样做,我可以根据 ExtensionDelegate 中的生命周期函数手动更新文本,例如 applicationWillEnterForeground()
,但这仅在应用程序首次进入前台时处理(与计算属性的更新频率相同)。我还没有找到任何方法来检测手腕何时放下或再次抬起,所以我可以让它保持最新的唯一方法是设置一个或多个已发布的Timer
s,并更新@Published
String
每分钟,每次应用程序进入后台并返回前台时禁用和设置计时器。这似乎是一个很好的解决方案?有没有更好的解决方案我忽略了?
解决方案
根据接受的答案,我可以使用TimelineView
定期更新。
尽管我只对显示的文本使用了分钟粒度,但我希望文本在实际刷新数据时更新到秒。为了完成这部分,我首先为Date
添加了一个新的extension
:
extension Date
func withSameSeconds(asDate date: Date) -> Date?
let calendar = Calendar.current
var components = calendar.dateComponents([.era, .year, .month, .day, .hour, .minute, .second], from: self)
let secondsDateComponents = calendar.dateComponents([.era, .year, .month, .day, .hour, .minute, .second], from: date)
components.second = secondsDateComponents.second
return calendar.date(from: components)
然后,我创建了我的视图,使用TimelineView
在数据刷新后每分钟更新一次文本:
struct LastUpdatedTextView: View
@ObservedObject var store: DataStore
var body: some View
if let nowWithSameSeconds = Date().withSameSeconds(asDate: store.lastUpdatedDate)
TimelineView(PeriodicTimelineSchedule(from: nowWithSameSeconds,
by: 60))
context in
Text(store.lastUpdatedText(forDate: lastUpdatedDate))
else
EmptyView()
【问题讨论】:
【参考方案1】:Apple 在 WWDC21 的 Build A Workout App 中解决了这个问题
他们使用TimelineView
和context.cadence == .live
告诉他们的格式化程序在手表处于始终开启状态时不显示毫秒。
您可以使用该代码来确定要显示的内容。
struct TimerView: View
var date: Date
var showSubseconds: Bool
var fontWeight: Font.Weight = .bold
var body: some View
if #available(watchOSApplicationExtension 8.0, watchOS 8.0, ios 15.0, *)
//The code from here is mostly from https://developer.apple.com/wwdc21/10009
TimelineView(MetricsTimelineSchedule(from: date)) context in
ElapsedTimeView(elapsedTime: -date.timeIntervalSinceNow, showSubseconds: context.cadence == .live)
else
Text(date,style: .timer)
.fontWeight(fontWeight)
.clipped()
@available(watchOSApplicationExtension 8.0, watchOS 8.0, iOS 15.0,*)
private struct MetricsTimelineSchedule: TimelineSchedule
var startDate: Date
init(from startDate: Date)
self.startDate = startDate
func entries(from startDate: Date, mode: TimelineScheduleMode) -> PeriodicTimelineSchedule.Entries
PeriodicTimelineSchedule(from: self.startDate, by: (mode == .lowFrequency ? 1.0 : 1.0 / 30.0))
.entries(from: startDate, mode: mode)
struct ElapsedTimeView: View
var elapsedTime: TimeInterval = 0
var showSubseconds: Bool = false
var fontWeight: Font.Weight = .bold
@State private var timeFormatter = ElapsedTimeFormatter(showSubseconds: false)
var body: some View
Text(NSNumber(value: elapsedTime), formatter: timeFormatter)
.fontWeight(fontWeight)
.onChange(of: showSubseconds)
timeFormatter.showSubseconds = $0
.onAppear(perform:
timeFormatter = ElapsedTimeFormatter(showSubseconds: showSubseconds)
)
【讨论】:
以上是关于在 Apple Watch 上的 SwiftUI 中,更新描述自具有 Always On 状态的日期以来的间隔的字符串的最佳方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章
使用 SwiftUi 为 Apple Watch 构建一个基本的高度计
在 SwiftUI 中使用 WCSession 向 Apple Watch 发送消息
带有 SwiftUI 的 Apple Watch 上列表项的背景 [重复]