美文网首页iOS机制
3D Touch (一) —— Home Screen Quic

3D Touch (一) —— Home Screen Quic

作者: 刀客传奇 | 来源:发表于2022-04-05 22:19 被阅读0次

    版本记录

    版本号 时间
    V1.0 2022.04.05 星期二 清明节

    前言

    3D TouchiPhone 6s+iOS9+之后新增的功能。其最大的好处在于不启动app的情况下,快速进入app中的指定界面,说白了,就是一个快捷入口。接下来几篇我们就一起看下相关的内容。

    开始

    首先看下主要内容:

    了解如何将静态和动态主屏幕快速操作(Static and Dynamic Home Screen Quick Actions)集成到您的 SwiftUI iOS 应用中。内容来自翻译

    接着看下写作环境:

    Swift 5.5, iOS 15, Xcode 13

    快捷操作(Quick actions)是让您的用户在主屏幕中快速访问您应用的常用功能的好方法。 iOS 13 引入了Quick actions的概念,用户可以在其中触摸并按住应用程序图标以显示一组快捷方式或操作(shortcuts or actions),以便直接从主屏幕执行。

    默认情况下,所有应用程序都具有编辑主屏幕或删除应用程序的快捷操作:

    开发者还可以提供自己的快捷操作,为用户提供常用应用功能的强大快捷方式。 iOS 相机应用程序具有拍摄不同类型的照片或录制视频的操作。购物应用程序可能会让您直接跳转到您的订单或愿望清单,而消息传递应用程序可能会显示您最喜欢的联系人,以便您轻松访问它们。

    我相信您可以想出快捷操作可以使您的用户受益的方式。在本教程中,您将了解:

    • 静态快速操作(Static quick actions,),始终可用于您的应用。
    • 动态快速操作(Dynamic quick actions),您的应用可以在运行时定义。
    • 如何在示例项目中支持这两种类型的快速操作,一个名为 Note Buddy 的笔记应用程序。
      入门

    打开下载好的程序,Note Buddy 是一款具有一些基本功能的笔记应用程序:

    • 添加、编辑或删除带有标题和正文的笔记。
    • 收藏一个笔记。
    • 在启动之间存储您的笔记。
    • 按上次修改日期自动排序笔记。

    打开项目,然后构建并运行。你会看到这个笔记页面:

    为了增强 Note Buddy 的功能,您可以在 Models 组中找到 Note 模型及其关联的 NoteStore 类。

    转到 SwiftUI,在 Views 组中,您有:

    • NoteList:显示按上次修改日期排序的所有笔记列表。
    • NoteRow:笔记列表中笔记行的视图。它显示笔记的标题、正文以及用户是否将其标记为收藏。
    • EditNote:用于修改笔记标题、正文或更改收藏状态的编辑器。
    • NavigationLink+Value:一个辅助扩展,当你有一个关联的数据模型要Push时,使编程导航更容易一些。

    最后,AppMain 使用对 NoteStore 的引用和在 WindowGroup 中设置 NoteList 视图并正确注入适当环境值的主体来描述您的应用程序。

    在你开始编码之前,是时候仔细看看这两种类型的快速操作是什么,以及它们是如何工作的。


    Static vs. Dynamic Quick Actions

    您可以使用两种类型的快速操作:静态和动态。

    您将静态操作用于在您的应用程序中永远不会更改的操作,例如Mail应用程序的New Message操作。

    如果您的操作可能在某些条件下发生变化或取决于特定数据或状态,请使用dynamic actions。例如,Messages app将为您所有固定的对话添加快速操作。

    在这两种情况下,您都添加代码来处理触发的特定操作。由于添加静态快速操作更快,您将从这些开始。


    Creating Static Quick Actions

    静态快速操作是让您的用户创建新笔记的好方法。

    首先,添加模型代码以帮助您处理触发的操作。 右键单击 Models 组,然后单击 New File... ▸ Swift File。 将新文件命名为 Action 并单击 Create

    将文件内容替换为:

    import UIKit
    
    // 1
    enum ActionType: String {
      case newNote = "NewNote"
    }
    
    // 2
    enum Action: Equatable {
      case newNote
    
      // 3
      init?(shortcutItem: UIApplicationShortcutItem) {
        // 4
        guard let type = ActionType(rawValue: shortcutItem.type) else {
          return nil
        }
    
        // 5
        switch type {
        case .newNote:
          self = .newNote
        }
      }
    }
    
    // 6
    class ActionService: ObservableObject {
      static let shared = ActionService()
    
      // 7
      @Published var action: Action?
    }
    

    很多事情正在发生。这是您添加的内容:

    • 1) 您创建一个由String支持的名为 ActionType 的枚举。稍后您将使用字符串值来帮助识别您的应用将执行的不同类型的操作。 newNote case将识别创建新笔记的操作。
    • 2) 您创建另一个名为 Action 的枚举,它看起来相似,但只是 Equatable。这可能看起来有点重复,但是当您稍后添加其他操作时它会很有意义。
    • 3) 然后创建一个可失败的初始化程序,它接受 UIApplicationShortcutItem 的实例。系统使用这种类型来描述不同的快速动作。
    • 4) 在这里,您确保为已知的 ActionType 创建一个 Action,否则返回 nil
    • 5) 打开您的应用已知的不同可能的 ActionType 值。然后,您可以使用可用信息来描述正确的Action
    • 6) 定义一个 ObservableObject 类,您可以稍后将其传递到 SwiftUI 环境中,并为以后使用 UIKit 代码时提供一个单例访问器。
    • 7) 定义一个@Published 属性,该属性可以表示您的应用应执行的操作。

    Action 概念的目的是让您对应用程序支持的快速操作进行建模,并在 UIApplicationShortcutItem 之间进行映射时安全地使用它们。

    现在,打开 AppMain.swift。在 noteStore 上方,为 ActionService 添加一个新属性:

    private let actionService = ActionService.shared
    

    然后更新 body 实现以确保服务被注入到视图层次结构中并且 NoteList 可以访问它:

    var body: some Scene {
      WindowGroup {
        NoteList()
          .environmentObject(actionService)
          .environmentObject(noteStore)
          .environment(\.managedObjectContext, noteStore.container.viewContext)
      }
    }
    

    ActionServiceSwiftUI 环境中可用,是时候使用它了。 在 Views 组中打开 NoteList.swift 并在 noteStore 下添加以下属性:

    @EnvironmentObject var actionService: ActionService
    @Environment(\.scenePhase) var scenePhase
    

    您不仅要访问 ActionService 类,还需要访问 ScenePhase,该属性会在您的应用程序变为活动状态以及进入后台时通知您。

    在视图的底部,添加以下方法:

    func performActionIfNeeded() {
      // 1
      guard let action = actionService.action else { return }
    
      // 2
      switch action {
      case .newNote:
        createNewNote()
      }
    
      // 3
      actionService.action = nil
    }
    

    这个方法做了三件事:

    • 1) 检查 ActionService 中是否有动作。
    • 2) 打开动作类型并为 Action.newNote 调用 createNewNote() 动作。
    • 3) 从 ActionService 中删除操作,因为它已被执行。

    每当您的应用程序处于活动状态时,您都需要触发此代码。 您将使用 onChange(of:perform:) 视图修饰符和之前添加的 scenePhase 属性。

    toolbar修饰符的右大括号后添加以下代码:

    // 1
    .onChange(of: scenePhase) { newValue in
      // 2
      switch newValue {
      case .active:
        performActionIfNeeded()
      // 3
      default:
        break
      }
    }
    

    在这里,代码:

    • 1) 向 List添加一个修改器,该修改器将在 scenePhase 更改时触发其闭包。
    • 2) 使用提供的参数,它会打开该值。 如果它是 .active,它会调用你的新 performActionIfNeeded()方法。
    • 3) 由于您不关心其他状态,例如 .inactive.background,它不会做任何事情。

    1. Modifying the Info Property List File

    您将逻辑添加到处理操作的代码中。 但在运行应用程序之前,您仍然需要告诉系统您的静态操作。 这很简单。

    Resources组中,打开 Info.plist。 右键单击顶部的Information Property List,然后单击Add Row

    Key 列中,键入 Home Screen Shortcut Items,然后按回车键完成编辑。 Xcode 自动将 Type 设置为 Array 并为您添加一个项目。 通过单击 V 形展开元素,您将看到两个嵌套键:

    此数组中列出的每个项目代表您的应用支持的单个静态快速操作。使用以下值更新占位符项:

    • Shortcut Item Type: NewNote
    • Title: New Note

    除了默认键之外,您还需要添加一个。将鼠标悬停在标题上,然后单击出现的 + 图标。键入 UIApplicationShortcutItemIconSymbolName(暂时忽略弹出选项,您必须键入完整的字符串)作为键和 square.and.pencil 作为值。

    以下是每个键的细分:

    • Shortcut Item Type - 快捷方式项目类型:表示唯一类型操作的字符串标识符。您可能会注意到这与您之前添加的 ActionType.newNote 枚举案例的原始值相匹配。这很重要。
    • Title - 标题:当用户点击并按住您的应用程序图标时显示的用户友好标题。
    • UIApplicationShortcutItemIconSymbolName:用于此操作的 SF 符号。您还可以使用 UIApplicationShortcutItemIconFile用于您的包中可用的图标文件,或UIApplicationShortcutItemIconType 用于一组可用于快速操作的预定义选项。编辑 Info.plist 条目时,可以从下拉列表中选择图标文件和图标类型,但符号选项不是。

    您可以在 Apple documention中阅读 Info.plist 中的快捷方式项目还有其他可用的键。

    2. Building and Running Your App

    构建并运行您的应用程序。 返回主屏幕并长按 Note Buddy 应用程序图标以查看列表中现在可用的快速操作:

    点击New Note以查看会发生什么:

    没有?!?

    嗯,这很奇怪。 您添加了一个模型来处理应用程序中的操作,并在 Info.plist中定义了一个静态操作。

    这是出了什么问题?

    当用户与快速操作进行交互时,系统会告诉您的应用程序,但您还没有对这些信息做任何事情。 是时候这样做了!

    3. Handling Static Quick Actions

    SwiftUI 还没有提供任何原生机制来响应快速操作的启动事件。 因此,这部分需要依赖 UIKit

    右键单击 AppMain.swift 并选择 New File... ▸ Swift File。 将文件命名为 AppDelegate 并单击Create。 将文件内容替换为:

    // 1
    import UIKit
    
    // 2
    class AppDelegate: NSObject, UIApplicationDelegate {
      private let actionService = ActionService.shared
    
      // 3
      func application(
        _ application: UIApplication,
        configurationForConnecting connectingSceneSession: UISceneSession,
        options: UIScene.ConnectionOptions
      ) -> UISceneConfiguration {
        // 4
        if let shortcutItem = options.shortcutItem {
          actionService.action = Action(shortcutItem: shortcutItem)
        }
    
        // 5
        let configuration = UISceneConfiguration(
          name: connectingSceneSession.configuration.name,
          sessionRole: connectingSceneSession.role
        )
        configuration.delegateClass = SceneDelegate.self
        return configuration
      }
    }
    
    // 6
    class SceneDelegate: NSObject, UIWindowSceneDelegate {
      private let actionService = ActionService.shared
    
      // 7
      func windowScene(
        _ windowScene: UIWindowScene,
        performActionFor shortcutItem: UIApplicationShortcutItem,
        completionHandler: @escaping (Bool) -> Void
      ) {
        // 8
        actionService.action = Action(shortcutItem: shortcutItem)
        completionHandler(true)
      }
    }
    

    如果你对 UIKit 不太熟悉,别担心!以下是上述代码的概述:

    • 1) 导入 UIKit 框架以访问此文件中所需的符号和类型。
    • 2) 创建一个名为 AppDelegate 的类,它继承自 NSObject并符合 UIApplicationDelegate 协议。
    • 3) 实现 application(_:configurationForConnecting:options:)hook进入应用程序准备启动主 UI 时触发的事件。
    • 4) 解包随选项提供的shortcutItem项。如果存在,则表明用户正在通过快速操作启动您的应用程序。使用您之前添加的初始化程序将数据映射到 Action 并将其分配给 ActionService
    • 5) 通过创建适当的 UISceneConfiguration对象并返回它来满足该方法的要求。
    • 6) 与第二步类似,创建另一个符合 UIWindowSceneDelegate 协议的类。
    • 7) 实现 windowScene(_:performActionFor:completionHandler:)hook进入在您的应用程序启动后用户与快速操作交互时触发的事件,例如,当它在后台时。
    • 8) 与第四步类似,尝试将 UIApplicationShortcutItem 转换为 Action 并将其传递给 ActionService

    最后,您的 SwiftUI 应用需要使用新的 AppDelegateSceneDelegate。因此,在 AppMain.swift 中的 actionService 下方,在 AppMain 中添加以下属性:

    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
    

    是时候再次构建和运行您的应用程序了。 返回主屏幕并再次尝试使用您的快速操作:

    极好的! 您添加了您的第一个静态快速操作。 虽然这为许多可能性打开了大门,但您无法使用运行时使用的信息对其进行自定义。 这就是动态操作的用武之地。


    Creating Dynamic Quick Actions

    如果用户想要编辑他们最近的笔记,他们仍然需要打开应用程序,滚动列表以找到它,点击它然后进行编辑。

    简化和加快流程会很棒!

    Info.plist 中进行静态操作时,您可以在代码中添加所有动态操作。 在 Note Buddy 的情况下,可能对于您的许多应用程序,当您的场景的阶段变为background时,将添加这些动态操作。 因此,在前往主屏幕之前,您可以设置一些操作以在最近修改日期之前编辑最新的笔记。

    1. Handling Dynamic Quick Actions

    要添加动态操作,首先将一个新case添加到您的 ActionTypeAction 枚举中。

    打开 Action.swift 并将以下内容添加到 ActionType 中:

    case editNote = "EditNote"
    

    将以下新case添加到Action

    case editNote(identifier: String)
    

    这就是为什么 ActionTypeAction 需要是两个独立的枚举。 ActionType 代表不同快速动作类型的标识符,而 Action 代表动作本身。 在某些情况下,例如 .editNotecase,它可以包含其他关联值。

    添加新的枚举case后,您会注意到编译器友好地告诉您 init?(shortcutItem:) 中的 switch 语句不再详尽。 通过将以下case添加到开关来解决此问题:

    case .editNote:
      if let identifier = shortcutItem.userInfo?["NoteID"] as? String {
        self = .editNote(identifier: identifier)
      } else {
        return nil
      }
    

    在这里,您将检查 shortcutItemuserInfo 字典以获取要编辑的笔记的 ID。 如果成功,则将操作初始化为具有关联值的 .editNote。 否则,初始化程序将通过返回 nil 失败。

    如果您现在尝试构建项目,您将在 NoteList.swift 中看到另一个编译失败。 转到它,你会发现你还需要在 performActionIfNeeded()中实现新的动作处理。

    将以下代码添加到 switch 语句中:

    case .editNote(let identifier):
      selectedNote = noteStore.findNote(withIdentifier: identifier)
    

    构建并运行您的应用程序。 然后前往主屏幕,长按 Note Buddy 图标并查看结果:

    再一次……什么都没有?

    虽然您已经确保快速操作正确传递到您的应用程序,但您仍然没有告诉您的应用程序显示任何动态项目。 接下来你会这样做。

    2. Adding Your Dynamic Quick Actions

    如前所述,添加动态操作的好地方是应用程序进入后台时。 此时,您拥有运行代码所需的所有信息,该代码确定要添加哪些操作,然后添加它们。

    由于您已经在 NoteList 中观察了 ScenePhase,因此这是更新动作的好地方。 但在此之前,您需要创建自己的 UIApplicationShortcutItem 实例来描述您的动态操作。

    前往 Note.swift 并在文件顶部添加以下import

    import UIKit
    

    Note 扩展中,添加以下属性:

    // 1
    var shortcutItem: UIApplicationShortcutItem? {
      // 2
      guard !wrappedTitle.isEmpty || !wrappedBody.isEmpty else { return nil }
    
      // 3
      return UIApplicationShortcutItem(
        type: ActionType.editNote.rawValue,
        localizedTitle: "Edit Note",
        localizedSubtitle: wrappedTitle.isEmpty ? wrappedBody : wrappedTitle,
        icon: .init(systemImageName: isFavorite ? "star" : "pencil"),
        userInfo: [
          "NoteID": identifier as NSString
        ]
      )
    }
    

    下面是代码分解:

    • 1) 您在 Note 上定义了一个名为 shortcutItem 的新计算属性。该属性是可选的,因为您不需要将每个笔记都表示为快速操作。
    • 2) 如果注释没有标题或正文,则返回 nil 以便不会显示该注释。
    • 3) 然后初始化 UIApplicationShortcutItem 的新实例并返回它。
      • 使用 ActionType 定义的类型,以便在您稍后读回它时与预期值匹配。
      • 提供一个用户友好的title,就像您在 Info.plist 中所做的那样。
      • 要进一步个性化操作,您可以将笔记的标题或正文作为快速操作的副标题。
      • 使用适当的 SF 符号来表示该项目是否是收藏。
      • userInfo 中包含 Note 的唯一标识符,以便您在响应快速操作时知道要编辑哪个笔记。

    现在您已经创建了一个属性来公开给定 Note 的适当快捷方式,请导航回 NoteList.swift。在 performActionIfNeeded()下添加以下方法:

    func updateShortcutItems() {
      UIApplication.shared.shortcutItems = notes.compactMap(\.shortcutItem)
    }
    

    在这里,您压缩映射 notes 数组以生成仅包含非nilUIApplicationShortcutItem 的数组。 然后将该数组分配给 UIApplicationshortcutItems 属性,该属性又与您在主屏幕菜单中的静态操作一起可用。

    现在,您只需在应用程序进入后台时调用此方法。 向上滚动到您之前添加的 onChange(of: perform:)修饰符,并在 switch 语句中添加以下内容:

    case .background:
      updateShortcutItems()
    

    构建并运行。 然后看看有哪些项目可用:

    Yeah,你做到了。

    对您的笔记进行一些编辑,收藏一些并重复该过程。 显示的笔记以及顺序在您后台运行应用程序时保持完美同步:

    您可能已经注意到,并非所有笔记都显示出来。 iOS 将操作数量限制为仅显示适合屏幕的操作。但是,文档建议您不要专门限制动态操作的计数。而是,像 Note Buddy那样做。

    这样,iOS 将显示它可以适应的所有快速操作。如果未来的更新能够实现更多功能,那就太好了,因为您无需在后台应用程序之前推出应用程序更新来支持添加更多操作。

    如果您想了解更多信息,请查看有关 UIKit 中 Menus and Shortcuts 的 Apple 文档,以及Human Interface Guidelines for Home Screen Quick Actions

    后记

    本篇主要讲述了Home Screen Quick Actions for SwiftUI App,感兴趣的给个赞或者关注~~~

    相关文章

      网友评论

        本文标题:3D Touch (一) —— Home Screen Quic

        本文链接:https://www.haomeiwen.com/subject/ouwksrtx.html