• iOS 18中的Control Action到底是个啥玩意儿?
  • 发布于 1天前
  • 14 热度
    0 评论
  • 雾月
  • 0 粉丝 27 篇博客
  •   
前言
前两天晚上刷 B 站时,看到一个博主在演示 iOS 18 的新功能,顺手滑到控制中心时,我突然发现了一个之前完全没注意到的东西 — 那些第三方 App 的快捷按钮!卧槽,什么时候控制中心还能加自定义按钮了?

立马打开自己的 iPhone,果然在控制中心左上角发现了个加号按钮。点进去一看,好家伙,原来这就是传说中的 Control Action!虽然苹果在 WWDC 2024 上有介绍过这个功能(说实话当时我差点睡着了),但真正上手摸索了一番后,发现这玩意儿简直是用户体验提升的神器啊!想象一下:用户再也不用在桌面上翻半天找你的 App 图标,下拉控制中心轻轻一点,直接就能跳到你 App 的核心功能页面。这种便利程度,简直让我有种当年第一次用 3D Touch 的感觉!

Control Action 到底是个啥玩意儿?
说白了,Control Action 就是让你能在 iOS 18 的控制中心里加个自己 App 的快捷按钮。用户可以通过控制中心左上角那个加号(我之前一直以为是装饰品),添加各种系统和第三方应用的控制项。

我第一次看到这功能的想法是:"这不就是安卓的快捷方式吗?"但仔细研究后发现,苹果把它做得更优雅、更系统化。这不是简单的快捷方式,而是能让你的应用功能真正融入系统级操作的利器。
整个逻辑其实很简单:
1.用户不用打开你的 App,就能执行某些操作
2.可以是简单的功能切换(比如开关某个设置)
3.也可以直接跳转到特定页面(这是我最常用的)

4.操作还能有视觉反馈,用户体验特棒


实现思路大拆解
要搞 Control Action,主要就两步:
1.先创建一个 App Intent(告诉系统你想干啥)

2.然后通过 Widget Extension 把这个操作"暴露"给控制中心


听起来挺复杂,但代码量真的不多。我们以一个图书管理 App 为例,做个"一键添加新书"的功能 — 就是用户点了控制中心的按钮后,直接跳转到添加书籍的页面。
第一步:先搞定导航逻辑
假设你有个图书管理 App,想在控制中心加个按钮,点了就直接跳到"添加新书"页面。这时候首先要把导航逻辑理顺。以前我做项目时总喜欢在视图里直接用 @State 管理弹窗,但这次为了配合 Intent,得把这部分逻辑抽出来单独管理:
final class NavigationManager: ObservableObject {
    @Published var navigationPath = NavigationPath()
    @Published var isAddBookOpen: Bool = false
    
    func openAddBook() {
        // 先重置导航路径,确保从根页面开始
        navigationPath = NavigationPath()
        isAddBookOpen = true
    }
}
然后在主视图里绑定这个状态:
struct BooksListView: View {
    @ObservedObject var navigation: NavigationManager
    var body: some View {
        NavigationStack(path: $navigation.navigationPath) {
            // 你的书籍列表内容
            // ...
        }
        .sheet(isPresented: $navigation.isAddBookOpen) {
            AddBookView()
        }
    }
}
这一步的关键就是把原本散落在各个视图里的导航逻辑统一起来,这样后面的 Intent 才能正确控制页面跳转。

第二步:创建 App Intent
App Intent 是 iOS 16 开始有的东西,专门用来定义应用能执行的动作。新建个文件叫 GoToAddBookIntent.swift:
import AppIntents

struct GoToAddBookIntent: AppIntent {
    // 1. 这个标题会显示在控制中心的选择界面
    staticvar title: LocalizedStringResource = "添加新书到书架"
    
    // 2. 注入我们的导航管理器
    @Dependency
    var navigationManager: NavigationManager
    // 3. 重点来了:这里设true表示这个操作会打开应用
    staticvar openAppWhenRun: Bool = true
    @MainActor
    func perform() async throws -> some IntentResult {
        // 4. 执行我们的导航动作
        navigationManager.openAddBook()
        return .result()
    }
}
这几个点要注意:
1. title 就是用户在添加控制项时看到的文字,要写得通俗易懂
2. @Dependency 让你能用应用里现有的对象,超方便
3. openAppWhenRun = true 告诉系统这个动作需要打开应用

4. perform() 方法里写具体要执行的逻辑


第三步:创建 Widget Extension
这步是重头戏。在 Xcode 里添加新的 Target:

1.File → New → Target
2.选择 Widget Extension
3.一定要勾选 "Include Control" 选项(我第一次忘了勾,找了半天问题)

系统会自动生成一些文件,我们主要改这个 Control Widget 的配置:
import AppIntents
import SwiftUI
import WidgetKit

struct BookShelfControlWidget: ControlWidget {
    var body: some ControlWidgetConfiguration {
        StaticControlConfiguration(
            kind: "com.yourapp.BookShelf.AddBookControl"
        ) {
            // 定义按钮的外观和要执行的动作
            ControlWidgetButton(action: GoToAddBookIntent()) {
                Label("添加新书", systemImage: "plus.circle.fill")
            }
        }
        .displayName("快速添加书籍")
        .description("直接跳转到添加新书页面")
    }
}
第四步:解决文件共享问题
这里是新手最容易卡住的地方!Widget Extension 是独立的 Target,默认访问不到主应用的文件。你需要把相关文件"共享"给 Widget Target。
具体操作:
1.选中 GoToAddBookIntent.swift 文件
2.在右侧 Inspector 面板找到 "Target Membership"
3.勾选你的 Widget Extension Target

对于 Intent 用到的所有文件(NavigationManager、数据模型等),都要重复这个步骤。我第一次就是忘了这步,结果编译时满屏的 "Cannot find xxx in scope" 错误,调试了老半天才恍然大悟。有时候这种基础错误反而最让人头疼。

第五步:上真机测试
这步只能在真机上测(模拟器测不了控制中心),步骤是:
1.Build 并运行到真机
2.下拉打开控制中心
3.点击左上角的加号按钮
4.选择 "添加控制"
5.在列表里找到你的应用,应该能看到刚才定义的控制项

6.添加后,控制中心就能看到这个按钮了


点击按钮,应用会立即打开并跳转到添加书籍页面。整个过程不到 1 秒,丝滑得不行!我第一次测试成功时,忍不住连续点了好几次,那种感觉就像小时候第一次玩电子游戏一样兴奋。

进阶玩法(高手才会的操作)
基础功能掌握后,还可以尝试一些更酷的特性:
1. 带参数的 Intent
struct OpenBookIntent: AppIntent {
    staticvar title: LocalizedStringResource = "打开指定书籍"
    @Parameter(title: "书籍")
    var book: BookEntity
    
    @Dependency
    var navigationManager: NavigationManager
    // 堆代码 duidaima.com
    staticvar openAppWhenRun: Bool = true
    func perform() async throws -> some IntentResult {
        navigationManager.openBook(book.id)
        return .result()
    }
}
2. 不打开应用的后台操作
struct ToggleReadingModeIntent: AppIntent {
    staticvar title: LocalizedStringResource = "切换阅读模式"
    
    // 设成 false,在后台默默执行
    staticvar openAppWhenRun: Bool = false
    func perform() async throws -> some IntentResult {
        // 执行后台操作,比如切换某个设置
        UserDefaults.shared.set(!UserDefaults.shared.bool(forKey: "readingMode"), 
                               forKey: "readingMode")
        return .result(value: "阅读模式已切换")
    }
}
3. 动态切换按钮
ControlWidgetToggle(
    isOn: UserDefaults.shared.bool(forKey: "readingMode"),
    action: ToggleReadingModeIntent()
) {
    Label("阅读模式", systemImage: "book.circle")
}
这种切换按钮特别适合设置类的功能,用户一眼就能看到当前状态。

实战经验分享(都是血泪教训)
搞了几个项目后,总结了一些实用技巧和容易踩的坑:
1. 别啥功能都做成控制项
不是所有功能都适合放控制中心。最适合的是:

用户经常用的功能(比如快速记录、扫码)
一步到位的操作(比如切换设置、开始计时)
用户想快速进入的页面(比如购物车、收藏夹)
我之前脑子一热,把"查看隐私政策"也做成了控制项,结果数据显示从来没人用过😂。

2. 图标和文字要简洁
控制中心的空间有限,千万别整那些花里胡哨的:
.用 SF Symbols,和系统风格保持一致
.文字控制在 4 个汉字以内
.图标意思要一目了然
3. 处理应用冷启动
如果应用没在后台运行,从控制中心启动会有冷启动过程。要确保:
.NavigationManager 等依赖能正确初始化
.数据加载有合适的 loading 状态
.别依赖那些视图生命周期才执行的初始化代码
去年我就因为这个问题被用户投诉,说点了按钮应用打开了但页面是空白的。后来发现是数据初始化没做好。

4. 记得测试各种场景
要测试这些情况:
.应用完全关闭时的启动
.应用在后台时的恢复
.系统内存紧张时的情况
.不同设备和系统版本
我有次在 iPhone 15 Pro 上测试得好好的,结果用户反馈在老 iPhone 上卡得要死,差点翻车。

需要注意的几个坑
权限问题
如果你的 Intent 需要特殊权限(比如相机、定位),要在 Widget Extension 的 Info.plist 里也添加相应的权限说明:
<key>NSCameraUsageDescription</key>
<string>快速拍照功能需要使用相机</string>
数据同步
Widget Extension 和主应用是独立进程,数据同步要用 App Groups 或者 UserDefaults.suiteName。我之前就因为这个问题,控制项显示的状态和应用内的不一致,用户体验很差。
性能考虑
Intent 的执行要快,特别是那些 openAppWhenRun = false 的操作。避免网络请求或者耗时计算,不然用户会觉得控制项"坏了"。
用户体验的思考
从用户角度看,Control Action 最大的价值就是减少操作步骤。以前用户要:
.在桌面找应用图标(有时候还得翻页)
.点击打开应用
.等应用启动
.在应用里找对应功能

.点击进入功能页面


现在只要:
.下拉控制中心

.点击控制项


从 5 步减少到 2 步!而且控制中心比桌面更容易访问,这种体验提升是质的飞跃。有个用户跟我说:"就这一个小功能,让我觉得你们 App 比其他同类产品高级好多。"说实话,听到这种反馈真的很有成就感。

几个实际场景
分享几个我能想到的比较好的的 Control Action 场景:
记账应用:快速记录支出(这个用户反馈超好!)
健身应用:开始今日锻炼计时
音乐应用:播放"每日推荐"歌单
笔记应用:新建速记
外卖应用:查看购物车
可以说每个都极大提升了用户的使用频率。

总结
App Intents + Control Action 这个组合,让我们真正有机会把应用的核心功能提升到系统级别。用户不用再在桌面翻找图标,不用等应用启动,一切都变得行云流水。虽然实现过程需要创建 Widget Extension、处理文件共享等步骤,但一旦掌握了套路,整个流程其实挺简单的。关键是要选对功能,并且确保用户体验的流畅性。最重要的是,这个功能能让你的应用在用户心中的地位直线上升。想想看,当用户愿意把你的应用功能添加到控制中心时,基本就是在说:"这个功能我经常用,值得占据一个宝贵的快捷位置。"这种认可,比任何好评都珍贵。

你觉得你的应用有哪些功能适合做成 Control Action?欢迎评论区分享你的想法!如果大家反响不错,下期我可以深入聊聊 App Intents 的其他高级用法,比如语音助手集成什么的。
用户评论