Dock

Apple Watch 有两个物理按钮。 除了数字表冠,下方还有一个旁边面按钮。 假设用户按一下按钮,Dock 将发起。Dock 闪现两组使用程序之一:最近工作、收藏夹。可在设置中进行更改:

「Apple Watch 使用开发系列」Dock 快照

「Apple Watch 使用开发系列」Dock 快照

大多数用户通过快速按两次以调出 Apple Pay 来与侧边按钮进行交互。Apple Watch 上的 Dock 可让佩戴者查看最近工作的或最喜欢的 App。已然 watchOS 已经在恰当的时分闪现这些 App,我们为什么要关心 Dock?

Dock 提供多种优点:

  • 点击会当即发起 App。

  • 在使用程序之间快速切换。

  • 一目了然地闪现其时的 App 情况。

  • 多个工作过的使用的组织。

虽然 watchOS 会将 App 推入后台时的外观刺进 Dock,但它只会闪现其时屏幕包含的内容,关于大多数使用程序来说,没有其他必要了。但在 Timer 相关 App 的情况下,单个快照是不可的,或许不是最佳的用户领会。

测验运用“计时器”App,当我们发起一个倒计时并将其推到后台,查看 Dock,我们将会看到计时任在 Doker 中继续计时。下面,我们将测验结束相似的功用。

「Apple Watch 使用开发系列」Dock 快照

快照 API

作为开发者,我们有责任奉告 watchOS 在使用程序移至后台后时是否需求实行额定的快照。我们首先将学习一些技巧,假设我们想优化你的 App 快照,我们应该考虑到这些内容。

标准优化

快照是 App 全标准的缩小图像。请有必要仔细查看我们在快照中捕获的内容。文本是否依然可读? 图像在较小标准下是否有含义?需求记住的是:

  • 在较小的标准下,较粗的字体更易于阅读。

  • 关于重要信息,我们或许希望运用粗体或更大的字体。

  • 我们或许还需求考虑从屏幕上删去不太重要的元素。

自界说界面

快照仅仅使用程序其时闪现的图像。考虑到这一点,我们或许完全重新设计 UI 时制作自界说视图,但请三思。

在某些情况下具有自界说视图或许很有含义,尤其是当我们需求从快照中删去某些元素时。假设我们制作自界说视图,我们需求留意保证快照看起来与正常闪现没有很大的不同。用户希望快照代表我们的 App,假设快照看起来不同比较大,就很难辨认和找到他们正在寻觅的 App。

假设我们承认需求不同的视觉效果,请记住以下几点:

  • 专注于重要信息。

  • 隐藏在 Dock 中查看时不相关的对象。

  • 为了便于阅读,扩展或缩小某些视图的巨细。

  • 不要让界面看起来完全不同。

进展或情况

进展、计时器等是快照的绝佳用例。我们或许想一同展现一切这些内容,但需求需求考虑用户是否想运用我们的杂乱功用。

在线订购使用程序也是自界说快照的完美示例。当用户订购食物时,会看到一个视图。 餐厅收到用户的订单后,屏幕或许会发生改动,以闪现食物准备好取货的时间。 当司机拿到食物时,屏幕会闪现间隔送货还有多长时间。

改动场景

尽或许保持其时活动视图与用户前次交互时的相同。假设 App 的情况以不可预知的办法发生改动,或许会运用户感到困惑。Dock 和 App 之间的交互应该无缝,避免用户不了解正在发生的任何特殊情况。

猜想用户希望

我们应该猜想用户在查看 Dock 时希望看到的内容。考虑一场体育赛事,根据事情发生的时间,用户或许想要这样的东西:

  • 在竞赛开端前不久,用户希望看到时间和方位。

  • 在游戏过程中,用户希望看到其时比分。

  • 竞赛完毕后,用户希望看到终究得分。

  • 其他时分,用户或许希望查看本赛季的时间表。

一同,并非每个用户都希望看到相同的内容。需求考虑 App 能否提供进一步自界说视图的或许性?只关心一支球队的球迷需求与想要注重整个联盟的体育狂热者不同的领会。

快照发生时机

watchOS 在许多不同的场景中主动组织快照来进行更新:

  • Apple Watch 发起时。

  • 杂乱功用更新时。

  • App 从前台转化到后台时。

  • 当用户查看 long look 长视图奉告时。

  • 用户终究一次与 App 交互后的一小时。

  • Dock 中的使用程序至少每小时一次。

  • 在可选的预定时间,运用后台改写 API。

developer.apple.com/documentati…

结束 Timer Demo

结构代码

新建 WatchOnlyApp Timer:

「Apple Watch 使用开发系列」Dock 快照

「Apple Watch 使用开发系列」Dock 快照

修正 ContentView,让我们的时间闪现出来:

struct ContentView: View {
    @State var timeText = ""
    var timer: Timer {
        get {
            Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
                let formatter = DateFormatter()
                formatter.dateFormat = "hh:mm:ss"
                self.timeText = formatter.string(from: Date())
            }
        }
    }
    var body: some View {
        VStack {
            Text(timeText)
                .onAppear {
                    _ = timer
                }
        }
    }
}
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

「Apple Watch 使用开发系列」Dock 快照

快照 handler

接着来到 TimerApp.swift,调整以下代码:

import SwiftUI
final class ExtensionDelegate: NSObject, WKExtensionDelegate {
    func handle(_ backgroundTasks: Set<WKRefreshBackgroundTask>) {
        backgroundTasks.forEach { task in
            guard let snapshot = task as? WKSnapshotRefreshBackgroundTask else {
                task.setTaskCompletedWithSnapshot(false)
                return
            }
            print("Taking a snapshot")
            task.setTaskCompletedWithSnapshot(true)
        }
    }
}
@main
struct Timer_Watch_AppApp: App {
    @WKExtensionDelegateAdaptor(ExtensionDelegate.self) private var extensionDelegate
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

每逢 watchOS 从后台唤醒 App 时,它都会调用 handle(_:):

  1. 首先,它会组织一个或多个任务供我们处理,我们循环遍历每一个。

  2. 我们只关心快照,因此假设不是快照,则将任务标记为已结束。传递 false 奉告体系不要主动组织另一个快照。

  3. 终究,你将快照任务标记为结束并指定 true,以便 watchOS 后续主动组织另一个快照。

当 watchOS 调用 handle(_:) 时,我们有有限的时间(大约几秒)来结束任务。假设我们疏忽将任务标记为结束,体系将继续在后台工作它。这些的任务将一向工作,直到耗尽一切可用时间,这会糟蹋电量。

在第二步中,将 false 传递给 setTaskCompletedWithSnapshot,这儿不指定 true。因为我们没有对任务实行任何操作,因此没有理由强制当即生成快照。

模拟器工作 App 后,切换到主屏幕。请耐性等待——在一段不承认但或许很短的时间之后,你会看到你的使用摄影快照的消息。

「Apple Watch 使用开发系列」Dock 快照

强制快照

当我们切换到主屏幕时,快照任务并没有当即工作,因为 watchOS 正忙于实行其他任务。这关于正常的出产部署或许没问题,但是在构建使用程序时,我们会希望可以奉告模拟器当即摄影快照。

请记住,仅当 App 不在前台时才会发生快照。模拟器或物理设备有必要闪现任何其他 App 或表盘。当然,该 App 有必要通过 Xcode 工作才能让我们看到。

一旦我们的 App 在后台工作,在 Xcode 的菜单栏中,选择 Debug ▸ Simulate UI Snapshot。

「Apple Watch 使用开发系列」Dock 快照

我们将再次在 Debug 区域中看到该消息,让我们知道 watchOS 摄影了快照。请留意表盘上的其他任何内容都没有改动。快照是一项后台任务,这意味着 watchOS 没有理由让 App 引起用户的留意。

查看快照

我们可以通过拜访 Dock 查看快照的外观。调出 Dock 后,会看到 App 是列表中的第一个。但这并不能让我们很好地查看快照, 工作任何其他使用程序,然后跳回 Dock。 我们将更好地查看快照:

「Apple Watch 使用开发系列」Dock 快照

「Apple Watch 使用开发系列」Dock 快照

自界说快照

如前所述,App 的快照默认为可见的终究一个屏幕。

切换到 ExtensionDelegate.swift 并添加以下办法:

private func nextSnapshotDate() -> Date {
    let twoDaysLater = Calendar.current.date(
        byAdding: .day,
        value: 2,
        to: Date()
    )!
    return Calendar.current.startOfDay(for: twoDaysLater)
}

运用日历核算,我们可以承认两天后的日期。此核算不会失利,因此强制解包返回值是安全的。终究,我们承认发生快照的一天的开端时间。

快照 API 根据字典类型的 Userinfo。虽然我们可以运用字典,但有更好的办法来处理数据。我们不希望有必要对 key 的字符串进行硬编码或界说大局 let 类型常量。相反,创建一个名为 SnapshotUserInfo.swift 的新 Swift 文件。当快照发生时,我们有必要根据之前界说的规则将使用程序切换到恰当的屏幕。

import Foundation
struct SnapshotUserInfo {
    let handler: () -> Void
    let content: String
}

我们有必要在结束时奉告快照,稍后会详细介绍。ContentView.Destination 是一个枚举,标识哪个视图将被推送到导航仓库上。

接下来,结束 SnapshotUserInfo 的初始化程序:

init(
    handler: @escaping () -> Void,
    content: String
) {
    self.handler = handler
    self.content = content
}

虽然不是必需的,但结束初始化程序可以让我们初始化结构实例,而无需为匹配显式指定 nil。

现在我们需求一种办法来生成 Snapshot API 需求的字典。 继续添加代码:

private enum Keys: String {
    case handler, content
}
func encode() -> [AnyHashable: Any] {
    return [
        Keys.handler.rawValue: handler,
        Keys.content.rawValue: content,
    ]
}

我们界说一个枚举来存储字典的 key。 创建一个将结构编码为 Snapshot API 所需的字典类型的办法。

然后在 struct 内部,结束从 notification API 的字典信息转化的办法:

static func from(notification: Notification) throws -> Self {
  guard let userInfo = notification.userInfo else {
    throw SnapshotError.noUserInfo
  }
  guard let handler = userInfo[Keys.handler.rawValue] as? () -> Void else {
    throw SnapshotError.noHandler
  }
  guard
    let content = userInfo[Keys.content.rawValue] as? String
  else {
    throw SnapshotError.badDestination
  }
  return .init(
    handler: handler,
    content: content
  )
}

在 handle(_:) 办法中,删去结束快照的终究一行并将其替换为:

let nextSnapshotDate = nextSnapshotDate()
let handler = {
    snapshot.setTaskCompleted(
        restoredDefaultState: false,
        estimatedSnapshotExpiration: nextSnapshotDate,
        userInfo: nil
    )
}

当 watchOS 摄影快照时,我们奉告它结束任务。我们将 false 传递给第一个参数,因为你让使用程序处于与最初不同的情况。第二个参数 estimatedSnapshotExpiration 奉告 watchOS 其时快照何时不再有用。 本质上,我们提供的日期是它需求摄影新快照的时间。终究,关于 userInfo 参数,只需传递 nil,因为我们不需求下一个快照来了解有关使用程序其时情况的任何信息。不然 userInfo 就是你可以传递一些东西的当地。

继续添加到 handle(_:):

let snapshotUserInfo = SnapshotUserInfo(
    handler: handler,
    content: "我被快照啦!"
  )
NotificationCenter.default.post(
    name: Notification.Name("snapshot"),
  object: nil,
  userInfo: snapshotUserInfo.encode()
)

我们创建了一个 SnapshotUserInfo 类型的常量,运用该对象发布奉告。

回到 ContentView,调整代码,我们接受前文的奉告,若收到奉告,则展现新文本,更新代码:

struct ContentView: View {
    @State var timeText = ""
    @State var snapshotText: String?
    private let pushViewForSnapshotPublisher = NotificationCenter
      .default
      .publisher(for: Notification.Name("snapshot"))
    var timer: Timer {
        get {
            Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
                let formatter = DateFormatter()
                formatter.dateFormat = "hh:mm:ss"
                self.timeText = formatter.string(from: Date())
            }
        }
    }
    var body: some View {
        VStack {
            Text(timeText)
                .onAppear {
                    _ = timer
                }
            if snapshotText != nil {
                Text(snapshotText!)
            }
        }
        .onReceive(pushViewForSnapshotPublisher) { notification in
            guard let info = try? SnapshotUserInfo.from(notification: notification) else {
              return
            }
            self.snapshotText = info.content
            info.handler()
        }
    }
}

工作并触发快照,我们的视图被更新了:

「Apple Watch 使用开发系列」Dock 快照

你可以从这儿获取文章中的源码。