iOS开发基础152 - 添加快捷方式到主屏幕

iOS 添加快捷方式到主屏幕

iOS 16 引入的 App Intents 框架让 App 的核心功能可以被系统深度整合——用户无需打开 App,就能通过 Siri、Spotlight、快捷指令 App,甚至直接在主屏幕添加快捷方式图标来一键执行。本文从"添加快捷方式到主屏幕"的用户流程讲起,深入解析 App Intents 的实现原理、完整代码(Swift + OC 方案)、底层执行机制,以及 iOS 18 的控制中心新特性。


一、什么是"添加快捷方式到主屏幕"

用户把 App 的某个功能(如"打开收藏"、"开始导航")做成一个像 App 图标一样的快捷方式放在主屏幕,点击后直接执行对应功能,无需打开 App 再层层点击。

用户操作流程

1. 打开「快捷指令」App
       ↓
2. 找到 App 提供的快捷指令(或自己创建)
       ↓
3. 点击快捷指令右上角「...」→ 「添加到主屏幕」
       ↓
4. 自定义图标和名称 → 点击「添加」
       ↓
5. 自动跳转到 Safari → 再次确认「添加到主屏幕」
       ↓
6. 主屏幕出现快捷方式图标 ✅

点击图标后发生什么

用户点击主屏幕快捷方式图标
       ↓
系统通过 URL Scheme 打开「快捷指令」App
       ↓
快捷指令 App 执行对应的快捷指令
       ↓
如果包含 App Intent → 系统调用你的 App 处理
       ↓
App 执行功能(打开页面/执行操作)
       ↓
返回结果给用户

二、三种实现方案对比

iOS 上实现"快捷方式到主屏幕"有三种技术方案,适用场景不同:

方案 最低系统 语言 说明
App Intents iOS 16 Swift-only 最新推荐,系统自动生成快捷指令,支持 Siri/Spotlight/主屏幕/控制中心
NSUserActivity + Siri Shortcuts iOS 12 OC + Swift 旧方案,通过捐赠用户活动让 Siri 建议快捷指令
Web Clip / PWA iOS 8+ 前端技术 Safari"添加到主屏幕",本质是网页快捷方式,不是原生 App 功能

重要说明:App Intents 是 Swift-only 框架(iOS 16 引入时就只支持 Swift),OC 项目无法直接使用。如果你的项目是 OC,可以用 NSUserActivity 方案实现类似的 Siri Shortcuts 功能;或者用 Swift 编写 Intent 代码,通过桥接在 OC 项目中调用。本文会分别提供两种方案的完整代码。


三、方案一:App Intents(iOS 16+,推荐)

用 Swift 定义一个实现 AppIntent 协议的结构体,告诉系统"这个功能叫什么、做什么",再通过 AppShortcutsProvider 注册快捷指令短语,系统就会自动在快捷指令 App、Spotlight、Siri 中显示这个功能,用户可以添加到主屏幕。

3.1 定义 AppIntent

import AppIntents
import UIKit

// 定义一个"打开收藏页"的 Intent
struct OpenFavoritesIntent: AppIntent {
    
    // 快捷指令的标题(显示在快捷指令 App 和主屏幕图标下方)
    static var title: LocalizedStringResource = "打开收藏"
    
    // 描述(可选,帮助用户理解这个快捷指令做什么)
    static var description = IntentDescription(
        "直接打开 App 的收藏页面,无需先启动 App。",
        categoryName: "导航"
    )
    
    // 可选:打开 App 时执行(需要在 Info.plist 中配置)
    static var openAppWhenRun: Bool = true
    
    // 执行逻辑
    func perform() async throws -> some IntentResult {
        // 在这里执行你的功能
        // 例如:发送通知让 App 跳转到收藏页
        NotificationCenter.default.post(name: .init("OpenFavorites"), object: nil)
        
        // 返回结果(可以附带值)
        return .result()
    }
}

3.2 带参数的 AppIntent

// 定义一个"搜索文章"的 Intent,带搜索关键词参数
struct SearchArticlesIntent: AppIntent {
    
    static var title: LocalizedStringResource = "搜索文章"
    static var description = IntentDescription("在 App 中搜索指定关键词的文章。")
    
    // 参数:搜索关键词
    @Parameter(title: "关键词", description: "要搜索的内容")
    var keyword: String
    
    // 参数提供:如果需要从 App 数据中动态提供选项
    static var parameterSummary: some ParameterSummary {
        Summary("搜索 \(\.$keyword) 的文章")
    }
    
    func perform() async throws -> some IntentResult {
        // 执行搜索
        print("搜索关键词:\(keyword)")
        return .result()
    }
}

3.3 定义 AppShortcutsProvider

import AppIntents

// 注册 App 提供的所有快捷指令
// 注意:每个 App 只能有一个 AppShortcutsProvider
struct MyAppShortcuts: AppShortcutsProvider {
    
    // 可选:快捷指令磁贴的主题色
    static var shortcutTileColor: ShortcutTileColor = .blue
    
    // 必须:定义所有快捷指令
    @AppShortcutsBuilder
    static var appShortcuts: [AppShortcut] {
        
        // 快捷指令 1:打开收藏
        AppShortcut(
            intent: OpenFavoritesIntent(),
            // Siri 唤醒短语(必须包含 .applicationName,即你的 App 名称)
            phrases: [
                "打开收藏 in \(.applicationName)",
                "用 \(.applicationName) 看收藏"
            ],
            // 短标题(显示在有限空间内)
            shortTitle: "收藏",
            // 系统图标(SF Symbols)
            systemImageName: "star.fill"
        )
        
        // 快捷指令 2:搜索文章(带参数)
        AppShortcut(
            intent: SearchArticlesIntent(),
            phrases: [
                "在 \(.applicationName) 搜索 \(\.$keyword)",
                "用 \(.applicationName) 搜 \(\.$keyword)"
            ],
            shortTitle: "搜索",
            systemImageName: "magnifyingglass"
        )
    }
}

3.4 处理 Intent 打开 App

如果 openAppWhenRun = true,点击快捷方式后会打开 App。在 App 中接收并处理:

// AppDelegate 或 SceneDelegate
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
    // 监听来自 Intent 的通知
    NotificationCenter.default.addObserver(
        forName: .init("OpenFavorites"),
        object: nil,
        queue: .main
    ) { _ in
        // 跳转到收藏页
        print("跳转到收藏页")
    }
}

3.5 完整的 App 内跳转处理

更好的方式是在 perform() 中直接处理,而不是通过通知:

struct OpenFavoritesIntent: AppIntent {
    
    static var title: LocalizedStringResource = "打开收藏"
    static var openAppWhenRun: Bool = true
    
    @MainActor
    func perform() async throws -> some IntentResult {
        // 获取当前的 SceneDelegate,直接跳转
        if let sceneDelegate = UIApplication.shared.connectedScenes
            .first?.delegate as? SceneDelegate {
            sceneDelegate.navigateToFavorites()
        }
        return .result()
    }
}

// SceneDelegate 中
extension SceneDelegate {
    func navigateToFavorites() {
        // 执行跳转逻辑
        guard let nav = window?.rootViewController as? UINavigationController else { return }
        let favoritesVC = FavoritesViewController()
        nav.pushViewController(favoritesVC, animated: true)
    }
}

四、方案二:NSUserActivity + Siri Shortcuts(iOS 12+,OC 可用)

一句话原理

在用户使用某个功能时,创建一个 NSUserActivity 对象并"捐赠"给系统,系统学习用户习惯后会在 Siri 建议和锁屏中推荐这个快捷操作,用户可以把它添加到主屏幕。

4.1 Info.plist 配置

首先在 Info.plist 中注册 Activity 类型:

<key>NSUserActivityTypes</key>
<array>
    <string>com.myapp.openFavorites</string>
    <string>com.myapp.searchArticles</string>
</array>

4.2 OC 完整实现

// 定义 Activity 类型常量
NSString *const kActivityTypeOpenFavorites = @"com.myapp.openFavorites";
NSString *const kActivityTypeSearchArticles = @"com.myapp.searchArticles";

@interface ViewController ()
@property (nonatomic, strong) NSUserActivity *favoritesActivity;
@end

@implementation ViewController

- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    // 用户进入收藏页时,捐赠 Activity
    [self donateFavoritesActivity];
}

// 捐赠"打开收藏"Activity
- (void)donateFavoritesActivity {
    // 1. 创建 NSUserActivity
    self.favoritesActivity = [[NSUserActivity alloc] initWithActivityType:kActivityTypeOpenFavorites];
    
    // 2. 设置标题(显示在 Siri 建议中)
    self.favoritesActivity.title = @"打开收藏";
    
    // 3. 设置用户信息(恢复时用)
    self.favoritesActivity.userInfo = @{@"page": @"favorites"};
    
    // 4. 关键:允许被 Siri 预测为快捷指令
    self.favoritesActivity.isEligibleForPrediction = YES;
    self.favoritesActivity.isEligibleForSearch = YES;
    self.favoritesActivity.isEligibleForPublicIndexing = NO;
    
    // 5. 成为当前 Activity(系统会自动捐赠)
    [self.favoritesActivity becomeCurrent];
}

// 搜索时捐赠 Activity
- (void)donateSearchActivityWithKeyword:(NSString *)keyword {
    NSUserActivity *activity = [[NSUserActivity alloc] initWithActivityType:kActivityTypeSearchArticles];
    activity.title = [NSString stringWithFormat:@"搜索 %@", keyword];
    activity.userInfo = @{@"keyword": keyword};
    activity.isEligibleForPrediction = YES;
    activity.isEligibleForSearch = YES;
    [activity becomeCurrent];
}

- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
    // 离开页面时,使 Activity 失效
    [self.favoritesActivity resignCurrent];
    self.favoritesActivity = nil;
}

@end

4.3 处理快捷指令启动 App

当用户通过主屏幕快捷方式或 Siri 打开 App 时,系统会通过 NSUserActivity 回调:

// AppDelegate 中
- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler {
    
    if ([userActivity.activityType isEqualToString:kActivityTypeOpenFavorites]) {
        // 跳转到收藏页
        NSLog(@"从快捷指令打开收藏页");
        [self navigateToFavorites];
        return YES;
    }
    
    if ([userActivity.activityType isEqualToString:kActivityTypeSearchArticles]) {
        NSString *keyword = userActivity.userInfo[@"keyword"];
        NSLog(@"从快捷指令搜索:%@", keyword);
        [self navigateToSearchWithKeyword:keyword];
        return YES;
    }
    
    return NO;
}

- (void)navigateToFavorites {
    // 执行跳转逻辑
}

- (void)navigateToSearchWithKeyword:(NSString *)keyword {
    // 执行搜索跳转
}

4.4 SceneDelegate 中的处理(iOS 13+)

- (void)scene:(UIScene *)scene continueUserActivity:(NSUserActivity *)userActivity {
    if ([userActivity.activityType isEqualToString:kActivityTypeOpenFavorites]) {
        // 跳转到收藏页
    }
}

五、方案三:Web Clip / PWA(网页添加到主屏幕)

一句话原理

网页通过 HTML meta 标签声明图标、名称和启动方式,用户在 Safari 中选择"添加到主屏幕",系统就会在主屏幕创建一个网页快捷方式,点击后全屏打开网页(类似原生 App)。

5.1 网页配置

<!DOCTYPE html>
<html>
<head>
    <!-- 全屏模式(隐藏 Safari 工具栏,像 App 一样) -->
    <meta name="apple-mobile-web-app-capable" content="yes">
    
    <!-- 状态栏样式 -->
    <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
    
    <!-- 主屏幕图标名称 -->
    <meta name="apple-mobile-web-app-title" content="我的应用">
    
    <!-- 主屏幕图标(不同尺寸) -->
    <link rel="apple-touch-icon" href="icon-180.png" sizes="180x180">
    <link rel="apple-touch-icon" href="icon-152.png" sizes="152x152">
    
    <!-- 启动画面 -->
    <link rel="apple-touch-startup-image" href="splash.png">
    
    <title>我的应用</title>
</head>
<body>
    <h1>Hello PWA</h1>
</body>
</html>

5.2 与原生 App 快捷方式的区别

对比 App Intents 快捷方式 Web Clip
本质 原生 App 功能快捷方式 网页快捷方式
点击后 打开原生 App 执行功能 全屏打开网页
离线可用 取决于 App 取决于 PWA 缓存
审核 App Store 审核 无需审核
功能限制 原生功能全支持 受浏览器限制

六、底层原理:快捷指令如何添加到主屏幕

一句话原理

快捷指令 App 的"添加到主屏幕"功能,本质是生成一个特殊的 Web Clip(网页快捷方式),通过 Safari 的"添加到主屏幕"机制创建图标,图标点击后通过 URL Scheme 打开快捷指令 App 并执行对应指令。

详细流程

用户点击「添加到主屏幕」
       ↓
快捷指令 App 生成一个 data: URL
(包含 base64 编码的 HTML,HTML 中包含重定向到快捷指令的 URL Scheme)
       ↓
用 Safari 打开这个 data: URL
       ↓
Safari 显示一个提示页,引导用户点击分享 → 添加到主屏幕
       ↓
系统创建 Web Clip 图标(包含自定义图标和名称)
       ↓
用户点击主屏幕图标
       ↓
Web Clip 打开内置的 HTML 页面
       ↓
HTML 页面通过 URL Scheme 跳转到快捷指令 App
(如:shortcuts://run-shortcut?name=xxx)
       ↓
快捷指令 App 执行对应指令
       ↓
如果指令包含 App Intent → 调用你的 App
       ↓
App 执行功能 ✅

为什么需要 Safari 中转

iOS 没有提供"直接在主屏幕创建图标"的公开 API(第三方 App 无法直接创建主屏幕图标)。唯一的公开方式是通过 Safari 的"添加到主屏幕"功能(Web Clip)。所以快捷指令 App 必须:

  1. 生成一个网页(data: URL)
  2. 用 Safari 打开
  3. 让用户手动点击"添加到主屏幕"

这就是为什么添加快捷方式到主屏幕时会跳转到 Safari 的原因。


七、iOS 18 新特性:控制中心(Control Center)

一句话原理

iOS 18 重新设计了控制中心,第三方 App 可以通过 App Intents + ControlWidget 把自己的功能添加到控制中心,用户下拉控制中心就能一键执行,比主屏幕快捷方式更快捷。

7.1 实现 ControlWidget

import AppIntents
import SwiftUI
import WidgetKit

// 定义一个控制中心控件
struct MyAppControl: ControlWidget {
    
    // 控件的主体
    var body: some ControlWidgetConfiguration {
        StaticControlConfiguration(
            kind: "com.myapp.favoritesControl"
        ) {
            // 控件显示的内容
            ControlWidgetButton(action: OpenFavoritesIntent()) {
                Label("收藏", systemImage: "star.fill")
            }
        }
        .displayName("打开收藏")
        .description("一键打开收藏页")
    }
}

7.2 用户添加到控制中心

1. 下拉打开控制中心
       ↓
2. 长按空白区域进入编辑模式
       ↓
3. 点击「+」打开控件库
       ↓
4. 找到你的 App,选择控件
       ↓
5. 添加到控制中心 ✅

7.3 控制中心 vs 主屏幕快捷方式

对比 主屏幕快捷方式 控制中心控件
位置 主屏幕图标 下拉控制中心
访问速度 需要回到主屏幕 任意界面下拉即可
技术 App Intents + Web Clip App Intents + ControlWidget
最低系统 iOS 16 iOS 18
自定义外观 自定义图标 系统统一风格

八、Swift 版本对照

App Intents 完整示例(Swift)

import AppIntents
import UIKit

// MARK: - 1. 定义 Intent
struct OpenFavoritesIntent: AppIntent {
    static var title: LocalizedStringResource = "打开收藏"
    static var description = IntentDescription("直接打开收藏页面")
    static var openAppWhenRun: Bool = true
    
    @MainActor
    func perform() async throws -> some IntentResult {
        if let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
           let nav = scene.windows.first?.rootViewController as? UINavigationController {
            let favoritesVC = FavoritesViewController()
            nav.pushViewController(favoritesVC, animated: true)
        }
        return .result()
    }
}

// MARK: - 2. 带参数的 Intent
struct SearchArticlesIntent: AppIntent {
    static var title: LocalizedStringResource = "搜索文章"
    
    @Parameter(title: "关键词")
    var keyword: String
    
    func perform() async throws -> some IntentResult {
        print("搜索:\(keyword)")
        return .result()
    }
}

// MARK: - 3. 注册快捷指令
struct MyAppShortcuts: AppShortcutsProvider {
    static var shortcutTileColor: ShortcutTileColor = .blue
    
    @AppShortcutsBuilder
    static var appShortcuts: [AppShortcut] {
        AppShortcut(
            intent: OpenFavoritesIntent(),
            phrases: ["打开收藏 in \(.applicationName)"],
            shortTitle: "收藏",
            systemImageName: "star.fill"
        )
        AppShortcut(
            intent: SearchArticlesIntent(),
            phrases: ["在 \(.applicationName) 搜索 \(\.$keyword)"],
            shortTitle: "搜索",
            systemImageName: "magnifyingglass"
        )
    }
}

NSUserActivity 完整示例(Swift)

import UIKit

class FavoritesViewController: UIViewController {
    
    private var userActivity: NSUserActivity?
    
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        donateActivity()
    }
    
    private func donateActivity() {
        let activity = NSUserActivity(activityType: "com.myapp.openFavorites")
        activity.title = "打开收藏"
        activity.userInfo = ["page": "favorites"]
        activity.isEligibleForPrediction = true
        activity.isEligibleForSearch = true
        activity.becomeCurrent()
        self.userActivity = activity
    }
    
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        userActivity?.resignCurrent()
        userActivity = nil
    }
}

// AppDelegate 中处理
func application(_ application: UIApplication, continue userActivity: NSUserActivity,
                 restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
    if userActivity.activityType == "com.myapp.openFavorites" {
        // 跳转到收藏页
        return true
    }
    return false
}

九、常见问题与坑

Q1:App Intents 可以用 Objective-C 写吗?

不能。App Intents 是 Swift-only 框架(iOS 16 引入时就只支持 Swift)。OC 项目有两个选择:

  1. 用 NSUserActivity 方案(iOS 12+,OC 完全支持)。
  2. 用 Swift 编写 Intent 代码,通过桥接在 OC 项目中调用(需要混编配置)。

Q2:为什么我的 App Shortcuts 没有出现在快捷指令 App 中?

检查以下几点:

  1. 是否实现了 AppShortcutsProvider 协议。
  2. appShortcuts 是否用 @AppShortcutsBuilder 标注。
  3. phrases 中是否包含 \(.applicationName)(必须包含 App 名称)。
  4. 重新编译运行 App(系统需要扫描 App 中的 Intent 定义)。
  5. 打开快捷指令 App,在"App"分类中找到你的 App。

Q3:添加到主屏幕后,点击图标没反应?

可能的原因:

  1. 快捷指令本身有问题,先在快捷指令 App 中测试能否正常运行。
  2. openAppWhenRun 没有设置为 true,导致 App 没有被打开。
  3. perform() 方法中有错误,检查是否抛出异常。
  4. 确保快捷指令 App 没有被卸载(主屏幕快捷方式依赖快捷指令 App)。

Q4:主屏幕快捷方式和快捷指令 App 是什么关系?

主屏幕上的快捷方式图标不是独立的,它依赖快捷指令 App:

  • 点击图标 → 打开快捷指令 App → 执行指令。
  • 如果卸载了快捷指令 App,主屏幕快捷方式就无法使用。
  • 快捷指令 App 是系统预装的,用户无法卸载(iOS 13+)。

Q5:可以直接在 App 内调用"添加到主屏幕"吗?

不能。iOS 没有提供公开 API 让第三方 App 直接在主屏幕创建图标。唯一的方式是引导用户:

  1. 打开快捷指令 App
  2. 找到快捷指令
  3. 手动点击"添加到主屏幕"

你可以在 App 内添加引导提示,告诉用户如何操作,但无法自动完成。

Q6:App Shortcuts 最多可以定义多少个?

每个 App 最多 10 个 App Shortcut(通过 AppShortcutsProvider 注册)。超过的部分不会被系统识别。建议只提供最核心、最高频的功能。

Q7:Siri 唤醒短语有什么要求?

  • 必须包含 \(.applicationName)(即你的 App 名称)。
  • 短语要自然,符合口语习惯。
  • 每个 App Shortcut 可以提供多个短语。
  • 示例:"打开收藏 in 我的App"、"用我的App看收藏"。

Q8:如何让快捷指令在 Spotlight 中显示?

实现了 AppShortcutsProvider 后,系统会自动在 Spotlight 中索引你的快捷指令。用户在 Spotlight 搜索关键词时,相关的快捷指令会出现在搜索结果中,点击即可执行。

Q9:NSUserActivity 捐赠后多久会出现在 Siri 建议中?

不确定。系统会根据用户的使用习惯、时间、地点等因素智能推荐。通常需要用户多次使用该功能后,系统才会开始推荐。可以在 设置 → Siri 与搜索 → 你的 App 中手动管理建议。

Q10:iOS 18 控制中心控件和主屏幕快捷方式可以同时用吗?

可以。两者是独立的:

  • 主屏幕快捷方式:通过 App Intents + 快捷指令 App 实现。
  • 控制中心控件:通过 App Intents + ControlWidget 实现。
  • 可以用同一个 AppIntent 同时支持两种入口。

十、总结

  • 三种方案
    1. App Intents(iOS 16+,Swift-only):最新推荐,系统自动生成快捷指令,支持 Siri/Spotlight/主屏幕/控制中心。
    2. NSUserActivity(iOS 12+,OC/Swift):旧方案,通过捐赠用户活动让 Siri 建议快捷指令。
    3. Web Clip/PWA:网页添加到主屏幕,本质是网页快捷方式。
  • App Intents 核心步骤
    1. 定义 AppIntent 结构体(标题、描述、perform 执行逻辑)。
    2. 定义 AppShortcutsProvider(注册快捷指令短语、图标)。
    3. perform() 中处理跳转或执行功能。
    4. 用户在快捷指令 App 中添加到主屏幕。
  • 底层原理:快捷指令 App 通过生成 Web Clip(data: URL + Safari)实现主屏幕图标创建,点击后通过 URL Scheme 打开快捷指令 App 执行指令。
  • iOS 18 新特性:控制中心支持第三方 App 控件(ControlWidget),比主屏幕快捷方式更快捷。
  • 关键限制:App Intents 是 Swift-only;每个 App 最多 10 个快捷指令;无法直接在 App 内创建主屏幕图标;主屏幕快捷方式依赖快捷指令 App。

posted @ 2026-09-08 15:34  Mr.陳  阅读(26)  评论(0)    收藏  举报