You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

SwiftUI集成Azure NotificationHubs:无ViewController场景下推送通知弹窗实现问询

解决SwiftUI中Azure Notification Hubs前台通知弹窗问题

你遇到的问题在SwiftUI场景下很常见——因为SwiftUI摒弃了ViewController的层级结构,传统UIKit里通过ViewController弹窗的方式就不太适配了。我来帮你拆解两种可行的方案,从UIKit兼容到SwiftUI原生实现,一步步讲清楚:


方案一:优化你当前的UIKit兼容实现(快速见效)

你现在用UIApplication.shared.windows.first?.rootViewController!.present的思路是可行的,但有两个需要优化的地方:避免强制解包(防止崩溃)、适配iOS 13+的多窗口场景,以及确保UI操作在主线程执行。

修改后的代码可以这样写:

func notificationHub(_ notificationHub: MSNotificationHub, didReceivePushNotification message: MSNotificationHubMessage) {
    print("notificationHub...")
    let title = message.title ?? "新通知"
    let body = message.body ?? ""
    print("title: \(title)")
    print("body: \(body)")
    
    let userInfo = ["message": message]
    NotificationCenter.default.post(name: NSNotification.Name("MessageReceived"), object: nil, userInfo: userInfo)
    
    guard let aps = message.userInfo["aps"] as? [String: AnyObject] else { return }
    print("aps: \(aps)")

    if UIApplication.shared.applicationState == .background {
        print("Notification received in background")
    } else {
        print("Notification received in foreground")
        // 调用弹窗方法
        showForegroundAlert(title: title, body: body, message: message)
    }
}

// 封装弹窗逻辑
private func showForegroundAlert(title: String, body: String, message: MSNotificationHubMessage) {
    // UI操作必须在主线程执行
    DispatchQueue.main.async {
        let alertController = UIAlertController(title: title, message: body, preferredStyle: .alert)
        
        // 添加跳转按钮
        alertController.addAction(UIAlertAction(title: "跳转", style: .default) { _ in
            // 这里处理你的跳转逻辑,比如拿到message里的参数跳转到对应页面
            self.handleNotificationNavigation(message: message)
        })
        
        // 添加取消按钮
        alertController.addAction(UIAlertAction(title: "取消", style: .cancel))
        
        // 安全获取当前窗口的根ViewController
        guard let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
              let rootVC = scene.windows.first?.rootViewController,
              // 避免重复弹窗
              rootVC.presentedViewController == nil else {
            return
        }
        
        rootVC.present(alertController, animated: true)
    }
}

// 你的跳转逻辑(已实现的部分可以复用)
private func handleNotificationNavigation(message: MSNotificationHubMessage) {
    // 这里写跳转到对应页面的代码
}

这个方案的好处是不用改动太多现有代码,快速实现需求,但缺点是依赖UIKit的层级,不太符合SwiftUI的状态驱动设计理念。


方案二:SwiftUI原生实现(推荐,更符合框架设计)

你看到的用@State private var showAlert = false的思路是完全正确的,这是SwiftUI中处理UI状态的标准方式。核心思路是通过NotificationCenter传递通知事件,在SwiftUI的View中监听事件并更新状态,触发Alert弹窗。

步骤1:创建通知状态管理器(可选但推荐)

为了让通知状态能在多个View中共享,我们可以创建一个ObservableObject来管理:

import Foundation
import MSNotificationHub

class NotificationStateManager: ObservableObject {
    // 用@Published包装,状态变化时自动通知View更新
    @Published var pendingNotification: (title: String, body: String, message: MSNotificationHubMessage)? = nil
    
    init() {
        // 监听NotificationHub发送的通知
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(handleReceivedNotification(_:)),
            name: NSNotification.Name("MessageReceived"),
            object: nil
        )
    }
    
    @objc private func handleReceivedNotification(_ notification: NSNotification) {
        guard let message = notification.userInfo?["message"] as? MSNotificationHubMessage else { return }
        let title = message.title ?? "新通知"
        let body = message.body ?? ""
        
        // 更新状态,触发View的UI更新
        pendingNotification = (title, body, message)
        
        // 处理完后重置状态,避免重复弹窗
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
            self.pendingNotification = nil
        }
    }
}

步骤2:在App中注入状态管理器

在你的主App结构体中,把状态管理器注入到环境中,让所有子View都能访问:

import SwiftUI

@main
struct YourSwiftUIApp: App {
    // 用@StateObject创建单例状态管理器
    @StateObject private var notificationManager = NotificationStateManager()
    
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(notificationManager)
        }
    }
}

步骤3:在主View中监听状态并弹窗

在你的主ContentView(或者任何需要弹窗的View)中,监听状态变化并触发Alert:

import SwiftUI

struct ContentView: View {
    // 从环境中获取状态管理器
    @EnvironmentObject private var notificationManager: NotificationStateManager
    @State private var showAlert = false
    @State private var alertTitle = ""
    @State private var alertBody = ""
    @State private var currentMessage: MSNotificationHubMessage?
    
    var body: some View {
        // 你的主界面内容
        Text("欢迎使用我的应用")
            .onReceive(notificationManager.$pendingNotification) { notification in
                if let (title, body, message) = notification {
                    alertTitle = title
                    alertBody = body
                    currentMessage = message
                    showAlert = true
                }
            }
            .alert(isPresented: $showAlert) {
                Alert(
                    title: Text(alertTitle),
                    message: Text(alertBody),
                    primaryButton: .default(Text("跳转")) {
                        if let message = currentMessage {
                            handleNavigation(message: message)
                        }
                    },
                    secondaryButton: .cancel()
                )
            }
    }
    
    // 处理跳转逻辑
    private func handleNavigation(message: MSNotificationHubMessage) {
        // 这里复用你已实现的导航代码,比如通过NavigationStack跳转
    }
}

这种方案的优势是完全遵循SwiftUI的状态驱动UI设计,代码更易维护,也能更好地和其他SwiftUI组件集成。


关于后台通知的小补充

你提到后台时收不到日志,这是正常的:当应用处于后台且未被唤醒时,NotificationHub的代理方法不会被触发。只有当用户点击通知唤醒应用,或者推送payload包含content-available: 1(需要开启后台通知权限)时,应用才会在后台处理通知。如果需要后台接收并处理通知,记得在Xcode的Capabilities中开启"Background Modes",勾选"Remote notifications",同时确保推送的payload包含content-available: 1

内容的提问来源于stack exchange,提问作者lcj

火山引擎 最新活动