You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI中如何通过Toggle开关控制通知的开启与关闭

解决SwiftUI中Toggle与通知权限联动的问题

看起来你在尝试把Toggle状态和系统通知权限绑定,但遇到了@State的willSet/didSet不生效的问题,而且权限处理逻辑也有小疏漏,我来帮你梳理清楚解决方案:

问题核心分析

  1. @State的didSet/willSet失效:在SwiftUI的状态管理机制下,@State变量的didSet经常会因为视图刷新被绕过,无法稳定触发逻辑。官方更推荐用.onChange(of:)修饰符来监听状态变化。
  2. 权限判断逻辑颠倒:你的pushEnabledAtOSLevel函数里,把.denied状态当成了权限允许的情况,这完全搞反了——.denied是用户已经拒绝权限,此时应该引导用户去设置开启,而不是直接发通知。
  3. 关闭Toggle的逻辑缺失:你只处理了打开Toggle的场景,但关闭时需要取消所有已调度的通知(我们无法直接修改系统权限,只能控制本地推送的启停)。

修正后的完整实现

下面是调整后的代码,每一步都做了优化:

import SwiftUI
import UserNotifications

struct NotificationSettingsView: View {
    @State private var notificationsOn: Bool = false
    
    var body: some View {
        Toggle(isOn: $notificationsOn) {
            Text("Bildirimler")
                .font(.system(size: 17, design: .rounded))
        }
        // 监听Toggle状态变化,替代didSet
        .onChange(of: notificationsOn) { newValue in
            if newValue {
                openNotifications()
            } else {
                closeNotifications()
            }
        }
        // 视图加载时同步系统权限状态到Toggle
        .onAppear {
            syncToggleWithSystemPermission()
        }
    }
    
    // 打开通知:检查权限并请求(必要时)
    private func openNotifications() {
        UNUserNotificationCenter.current().getNotificationSettings { settings in
            DispatchQueue.main.async {
                switch settings.authorizationStatus {
                case .notDetermined:
                    // 用户未做选择,发起权限请求
                    UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { success, error in
                        DispatchQueue.main.async {
                            if success {
                                print("Bildirimlere izin verildi.")
                                self.notificationsOn = true
                                self.sendTestNotification()
                            } else {
                                print("Bildirim izni reddedildi: \(error?.localizedDescription ?? "Bilinmeyen hata")")
                                self.notificationsOn = false
                            }
                        }
                    }
                case .authorized:
                    // 已授权,直接发送测试通知
                    print("Bildirimler zaten aktif.")
                    self.sendTestNotification()
                case .denied, .provisional, .ephemeral:
                    // 权限被拒绝或其他限制状态,切回Toggle关闭状态
                    print("Bildirim izni reddedildi, lütfen ayarlar kısmından açın.")
                    self.notificationsOn = false
                @unknown default:
                    self.notificationsOn = false
                }
            }
        }
    }
    
    // 关闭通知:取消所有待发送的通知请求
    private func closeNotifications() {
        UNUserNotificationCenter.current().removeAllPendingNotificationRequests()
        print("Tüm bekleyen bildirimler iptal edildi.")
    }
    
    // 发送测试通知
    private func sendTestNotification() {
        let content = UNMutableNotificationContent()
        content.title = "Elini Yıka"
        content.subtitle = "Çabuk ol git! Elini Yıka!"
        content.sound = UNNotificationSound.default
        
        let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 3, repeats: false)
        let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: trigger)
        
        UNUserNotificationCenter.current().add(request) { error in
            if let error = error {
                print("Bildirim gönderilemedi: \(error.localizedDescription)")
            }
        }
    }
    
    // 同步Toggle状态与当前系统权限
    private func syncToggleWithSystemPermission() {
        UNUserNotificationCenter.current().getNotificationSettings { settings in
            DispatchQueue.main.async {
                self.notificationsOn = settings.authorizationStatus == .authorized
            }
        }
    }
}

关键优化点

  • 用.onChange替代didSet:这是SwiftUI官方推荐的状态监听方式,能稳定捕获Toggle的状态变化。
  • 权限逻辑分层处理:区分未授权、已授权、已拒绝等不同状态,对应不同的处理逻辑,避免逻辑混乱。
  • 关闭Toggle时清理通知:取消所有待发送的通知请求,实现“关闭Toggle即停止推送”的需求。
  • 主线程更新UI:所有修改@State变量的操作都切回主线程,避免后台线程操作UI导致的崩溃或状态不同步。
  • 初始状态同步:视图加载时自动同步系统权限状态到Toggle,避免打开页面时状态不一致的问题。

这样调整后,你的Toggle就能和通知权限正确联动:打开时请求权限并发送通知,关闭时取消所有待推送通知,状态同步也更可靠。

内容的提问来源于stack exchange,提问作者Ufuk Köşker

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.07 17:27:34