应用使用快捷的主屏幕链接是常用的快速进入到某一个功能所必备的方式。那么如何让应用支持主屏幕快捷操作呢?

本文为翻译内容,来自Jeeva Tamilselvan

本文有删减,推荐访问英文原文地址:Home Screen Quick Actions — SwiftUI 2.0

翻译: 张洪Heo(转载注明出处)

非原创内容须知

长按应用图标

iOS 12中引入了主屏幕快速操作。这是一个快捷按钮,可以将用户导航到应用程序中的特定位置。如今,许多应用程序都带有这种快速行动功能。在这篇文章中,我们将学习如何在SwiftUI应用生命周期(2020)中实现“主屏快速操作”。

我们开始吧

创建动态菜单

动态快速操作在运行时添加到应用程序。这些快速操作是分配给UIApplication的共享实例的UIApplicationShortcutItem数组。

让我们在App struct(也就是SwiftUI生命周期中应用的入口点struct)中创建一个函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@main
struct Quick_ActionsApp: App {
@Environment(\.scenePhase) var phase

var body: some Scene {
WindowGroup {
ListView()
}
.onChange(of: phase) { (newPhase) in
switch newPhase {
case .active :
print("App in active")
case .inactive:
print("App is inactive")
case .background:
print("App in Back ground")
addQuickActions() // add quick action when app is going to background
@unknown default:
print("default")
}
}
}

func addQuickActions() {
UIApplication.shared.shortcutItems = [
UIApplicationShortcutItem(type: "Call", localizedTitle: "Call"),
UIApplicationShortcutItem(type: "Chat", localizedTitle: "Chat"),
UIApplicationShortcutItem(type: "Status", localizedTitle: "Status"),
UIApplicationShortcutItem(type: "Contacts", localizedTitle: "Contacts"),
]
}
}

在这里,我们创建了四个快捷方式-呼叫、聊天、状态、联系人。

当应用程序进入后台状态时,我们需要调用此方法。因此,我们使用@Environment(\.sceneProgress)环境变量和onChange(of:)修饰符来捕获应用程序的状态。

在转换到后台状态期间是更新任何动态快速操作的好时机,因为此代码总是在用户返回到主屏幕之前执行。
-苹果公司

苹果官方文档指示我们可以在应用程序进入后台阶段时添加动态快速动作。因此,我们在.back中调用addQuickActions()方法

参考这篇文章可以更好地理解SwiftUI应用程序生命周期中的场景管理。

动态菜单

实际上,我们可以添加任意数量的快捷操作,但苹果最多只能显示四个快捷键。

到目前为止,我们已经看到了如何添加动态快速操作按钮。下一步是如何在选择快速操作按钮时传递数据?

让我们看看这个。

通过快速操作传递数据

要通过快速操作将数据传递到应用程序,我们需要稍微修改addQuickActions()方法。我们将使用下面的UIApplicationShortcutItem构造函数。

1
UIApplicationShortcutItem(type: String, localizedTitle: String, localizedSubtitle: String?, icon: UIApplicationShortcutIcon?, userInfo: [String : NSSecureCoding]?)

在这里,我们通过userInfo字典将数据传递给应用程序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
func addQuickActions() {
var calluserInfo: [String: NSSecureCoding] {
return ["name" : "call" as NSSecureCoding]
}
var chatuserInfo: [String: NSSecureCoding] {
return ["name" : "chat" as NSSecureCoding]
}
var statususerInfo: [String: NSSecureCoding] {
return ["name" : "status" as NSSecureCoding]
}
var contactuserInfo: [String: NSSecureCoding] {
return ["name" : "contact" as NSSecureCoding]
}

UIApplication.shared.shortcutItems = [
UIApplicationShortcutItem(type: "Call", localizedTitle: "Call", localizedSubtitle: "", icon: UIApplicationShortcutIcon(type: .message), userInfo: calluserInfo),
UIApplicationShortcutItem(type: "Chat", localizedTitle: "Chat", localizedSubtitle: "", icon: UIApplicationShortcutIcon(type: .message), userInfo: chatuserInfo),
UIApplicationShortcutItem(type: "Status", localizedTitle: "Status", localizedSubtitle: "", icon: UIApplicationShortcutIcon(type: .captureVideo), userInfo: statususerInfo),
UIApplicationShortcutItem(type: "Contacts", localizedTitle: "Contacts", localizedSubtitle: "", icon: UIApplicationShortcutIcon(type: .contact), userInfo: contactuserInfo),
]
}

现在,我已经为每个快速操作创建了四个变量,并将它们传递给UIApplicationShortcutItem。如果你仔细观察,会发现每件物品都添加了图标。

下一步如何根据快速动作执行动作。我们走吧👇

执行快速操作操作

当用户点击快速操作时,我们需要处理两个地方。

  1. 如果应用程序完全关闭,则会全新打开

  2. 如果应用程序在后台,请点击快速操作。

为此,我们需要实现两个方法,

  1. application(_:configurationForConnecting:options:) — AppDelegate
  2. application(_:performActionFor:completionHandler:) — SceneDelegate

我们知道SwiftUI应用程序生命周期没有AppDelegate和SceneDelegate。我们先添加AppDelegate,

在SwiftUI应用生命周期中添加AppDelegate和SceneDelegate

1
2
class AppDelegate: NSObject, UIApplicationDelegate {
}

创建一个名为AppDelegate的类,然后在App struct的开头添加下面的代码片段。紧随其后的是“@Environment(.scenePhase) var phase”

1
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate

现在,我们已将AppDelegate添加到SwiftUI生命周期。我们可以在AppDelegate类中添加任何AppDelegate方法。
将SceneDelegate添加到生命周期的时间到了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
if let shortcutItem = options.shortcutItem {
shortcutItemToProcess = shortcutItem
}

let sceneConfiguration = UISceneConfiguration(name: "Custom Configuration", sessionRole: connectingSceneSession.role)
sceneConfiguration.delegateClass = CustomSceneDelegate.self

return sceneConfiguration
}
}

class CustomSceneDelegate: UIResponder, UIWindowSceneDelegate {
func windowScene(_ windowScene: UIWindowScene, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void) {
shortcutItemToProcess = shortcutItem
}
}

application(_:configurationForConnecting:options:)方法内部,使用UISceneConfiguration(name: sessionRole:)配置SceneDelegate类(CustomSceneDelegate)

在类外部创建UIApplicationShorcutItem变量。因为,我们需要在App structAppDelegateCustomSceneDelegate类中使用此变量。

使用options.shourtcutItemapplication(_:configurationForConnecting:options:)方法中和shourtcutItemapplication(_:performActionFor:completionHandler:)方法中赋值ShourtcutItem

现在,我们已经将分接的快速操作分配给一个变量ShorkutItemToProcess。接下来,我们需要执行基于快捷方式项的操作。
每一次,用户点击一个快速动作应用程序都会打开。换句话说,将触发活动场景阶段。因此,我们需要在App struct内的.active开关中执行操作。修改App struct,如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var body: some Scene {
WindowGroup {
ListView()
}
.onChange(of: phase) { (newPhase) in
switch newPhase {
case .active :
print("App in active")
guard let name = shortcutItemToProcess?.userInfo?["name"] as? String else {
return
}
case .inactive:
// inactive
print("App is inactive")
case .background:
print("App in Back ground")
addQuickActions()
@unknown default:
print("default")
}
}
}

从快捷方式的ItemToProcess变量中获取“name”值。(第10行)

导航到选定的快速操作视图

在本部分中,我们将在用户选择快速操作时导航相应的视图。为此,我们必须创建视图。

我已经创建了ListView,而不是ContentView。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import SwiftUI

struct ListView: View {
@EnvironmentObject var quickActionSettings: QuickActionSettings
@State var selectedAction: Int?

var body: some View {
NavigationView {
List {
ForEach(0..<allQuickActions.count) { index in
NavigationLink(destination: DetailView(name: allQuickActions[index].name), tag: allQuickActions[index].tag, selection: $quickActionSettings.quickAction) {
Text(allQuickActions[index].name)
}
}
}
.listStyle(SidebarListStyle())
.navigationBarTitle("Quick Actions")
}
}
}

struct ListView_Previews: PreviewProvider {
static var previews: some View {
ListView()
}
}

struct QuickActionModel : Identifiable {
let id = UUID()
let name: String
let tag: QuickActionSettings.QuickAction
}

let allQuickActions = [
QuickActionModel(name: "Contacts", tag: .details(name: "contact")),
QuickActionModel(name: "Chats",tag: .details(name: "chat")),
QuickActionModel(name: "Calls", tag: .details(name: "call")),
QuickActionModel(name: "Status", tag: .details(name: "status")),
]

我已经创建了简单的DetailView。

1
2
3
4
5
6
7
8
9
10
11
import SwiftUI

struct DetailView: View {

var name: String

var body: some View {
Text("\(name) View")
.navigationBarTitle(name)
}
}

和ObservableObject类QuickActionSettings

1
2
3
4
5
6
7
8
9
10
11
import Foundation

class QuickActionSettings: ObservableObject {

enum QuickAction: Hashable {
case home
case details(name: String)
}

@Published var quickAction: QuickAction? = nil
}

因此,逻辑是我们使用QuickActionSettings的QuickAction枚举作为ListView中NavigationLink的Selection:Value。如果QuickAction枚举值(IckAction)更改,那么NavigationLink将被激发,因为它是ListView中的绑定变量。

现在,我们需要做的就是根据选定的Quick Action更改Quick Action值。让我们执行App struct中的最后一步。

在App Struct外部为QuickActionSettings创建一个对象,该对象与ShorkutItemToProcess变量相同。

1
let quickActionSettings = QuickActionSettings()

然后在App StructListView中添加QuickActionSettings对象作为Environment Object。最终的App struct将如下所示,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
var body: some Scene {
WindowGroup {
ListView()
.environmentObject(quickActionSettings)
}
.onChange(of: phase) { (newPhase) in
switch newPhase {
case .active :
print("App in active")
guard let name = shortcutItemToProcess?.userInfo?["name"] as? String else {
return
}
switch name {
case "call":
print("call is selected")
quickActionSettings.quickAction = .details(name: name)
case "chat":
print("chat is selected")
quickActionSettings.quickAction = .details(name: name)
case "status":
print("status is selected")
quickActionSettings.quickAction = .details(name: name)
case "contact":
print("contct is selected")
quickActionSettings.quickAction = .details(name: name)
default:
print("default ")
}
case .inactive:
// inactive
print("App is inactive")
case .background:
print("App in Back ground")
addQuickActions()
@unknown default:
print("default")
}
}
}

在这里,当应用程序进入.active阶段时,我们分配的是ickAction值。这将触发ListView中的NavigationLink。

让我们构建并运行该应用程序。

项目地址

原作者仓库QuickActions

备用仓库