SwiftUI 中如何实现推送功能

手把手教你在 SwiftUI 中实现推送通知。

第一步:请求推送权限

在 SwiftUI 中,我们需要先导入必要的框架,然后创建一个自定义的 AppDelegate。是的,即使是 SwiftUI,推送通知还是需要用到 AppDelegate(希望苹果以后能改进这点)。

import SwiftUI
import UserNotifications

// 自定义 AppDelegate
class CustomAppDelegate: NSObject, UIApplicationDelegate, ObservableObject {
    var app: YourApp?

    func application(_ application: UIApplication,
                     didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
        // 注册远程通知
        application.registerForRemoteNotifications()

        // 设置通知代理
        UNUserNotificationCenter.current().delegate = self

        returntrue
    }
}

然后在你的主 App 文件中集成这个 AppDelegate:

@main
struct YourApp: App {
    @UIApplicationDelegateAdaptor private var appDelegate: CustomAppDelegate

    var body: some Scene {
        WindowGroup {
            ContentView()
                .onAppear {
                    appDelegate.app = self
                }
        }
    }
}

第二步:处理设备令牌

当用户同意接收推送后,苹果会给我们一个设备令牌。这就像是这台设备的"收货地址":

extension CustomAppDelegate {
    func application(_ application: UIApplication,
                     didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
        // 把设备令牌转换成字符串
        let token = deviceToken.map { String(format: "%02.2hhx", $0) }.joined()
        print("设备令牌:\(token)")

        // TODO: 把这个令牌发送给你的服务器
    }

    func application(_ application: UIApplication,
                     didFailToRegisterForRemoteNotificationsWithError error: Error) {
        print("注册失败:\(error)")
    }
}

第三步:处理收到的通知

这里有两种情况需要处理:App 在前台时和 App 在后台时。

extension CustomAppDelegate: UNUserNotificationCenterDelegate {
    // App 在前台时收到通知
    func userNotificationCenter(_ center: UNUserNotificationCenter,
                                willPresent notification: UNNotification) async -> UNNotificationPresentationOptions {
        // 即使 App 在前台也显示通知
        return [.badge, .banner, .list, .sound]
    }

    // 用户点击通知时
    func userNotificationCenter(_ center: UNUserNotificationCenter,
                                didReceive response: UNNotificationResponse) async {
        print("用户点击了通知:\(response.notification.request.content.title)")

        // 可以在这里处理深度链接
        iflet pageLink = response.notification.request.content.userInfo["pageLink"] as? String {
            // 导航到特定页面
        }
    }
}

第四步:在 ContentView 中请求权限

现在让我们在主界面添加一个按钮来请求推送权限:

struct ContentView: View {
    @Stateprivatevar notificationEnabled = false

    var body: some View {
        VStack(spacing: 20) {
            Text(notificationEnabled ? "推送已开启 ✅" : "推送未开启 ❌")
                .font(.title2)

            Button("开启推送通知") {
                requestNotificationPermission()
            }
            .buttonStyle(.borderedProminent)
        }
        .padding()
    }

    func requestNotificationPermission() {
        UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { granted, error in
            DispatchQueue.main.async {
                self.notificationEnabled = granted

                if granted {
                    // 用户同意了,注册远程通知
                    UIApplication.shared.registerForRemoteNotifications()
                }
            }
        }
    }
}

测试推送通知

开发阶段测试推送通知有几种方法:

1、创建测试文件(.apns)

创建一个 test.apns 文件:

{
    "aps": {
        "alert": {
            "title": "测试通知",
            "body": "这是一条测试消息 🎉"
        },
        "badge": 1,
        "sound": "default"
    },
    "pageLink": "View2",
    "Simulator Target Bundle": "你的Bundle ID"
}

然后直接拖到模拟器上就能看到通知了!

2、使用终端命令

xcrun simctl push booted 你的Bundle包名 test.apns

进阶技巧:深度链接

推送通知最强大的功能之一就是深度链接。你肯定有这种需求,比如点击推送后直接跳转到订单详情页,或者跳转到某个页面。

struct ContentView: View {
    @Environment(CustomAppDelegate.self) privatevar appDelegate
    @Stateprivatevar navigationPath: [String] = []

    var body: some View {
        @Bindablevar appDelegate = appDelegate
        NavigationStack(path: $appDelegate.mainPageNavigationPath) {
            List {
                NavigationLink("订单列表", value: "orders")
                NavigationLink("消息中心", value: "messages")
                NavigationLink("个人中心", value: "profile")
            }
            .navigationTitle("推送通知示例")
            .navigationDestination(for: String.self) { value in
                DetailView(page: value)
            }
        }
    }
}

从服务器发送推送

如果你用 Node.js 做后端,可以这样发送推送:

const apn = require('apn');

// 配置 APNs
const apnProvider = new apn.Provider({
    token: {
        key: 'path/to/APNsAuthKey.p8',
        keyId: '你的KeyID',
        teamId: '你的TeamID',
    },
    production: false// 开发环境
});

// 创建通知
const notification = new apn.Notification({
    alert: '你有新订单啦!',
    sound: 'default',
    badge: 1,
    payload: {
        pageLink: 'orders'
    }
});

// 发送
apnProvider.send(notification, deviceToken).then(result => {
    console.log('发送成功:', result);
});

常见坑点和解决方案

1、模拟器 vs 真机

iOS 11.4 之后模拟器支持推送了,但功能还是有限。如果要测试所有功能,还是得用真机。

2、Safari 不支持 new Notification()

必须用服务工作者的 showNotification() 方法:

// ❌ 错误方式
new Notification("标题", options)

// ✅ 正确方式
await registration.showNotification("标题", options)

3、证书配置

这可能是最让人头疼的部分。记得在 Apple Developer 后台:

• 开启 Push Notifications 功能

• 创建 APNs 密钥(记得保存好,只能下载一次!)

• 在 Xcode 项目设置中启用推送通知

一些实用的第三方服务

如果你不想自己搭建推送服务器,也可以选择这些服务:

• Firebase Cloud Messaging (FCM):Google 的免费服务,功能强大

• OneSignal:有免费额度,支持 A/B 测试

• 极光推送:国内用得比较多,文档是中文的

推送通知虽然看起来步骤多,但其实每一步都很简单。关键是要理解整个流程,然后一步步实现就好了。

记住几个要点:

• 一定要处理用户拒绝权限的情况

• 不要滥用推送,用户会卸载你的 App 的

• 推送内容要有价值,别发"今天天气真好"这种废话

我的笔记