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

Swift基于位置的持久化通知异常问题求助

问题描述

我为首个Swift应用编写了如下代码,目前仅当应用关闭且处于指定位置坐标时,通知事件触发后能正常推送通知,其余所有场景均失败。已在后台模式中开启Location Updates和Background Fetch权限,恳请各位提供解决思路,谢谢。

import SwiftUI
import WebKit
import CoreLocation
import UserNotifications

let locationManager = CLLocationManager()


struct ContentView: View {
    @StateObject private var locationManager = LocationManager()

    var body: some View {
        WebView(url: URL(string: "https://www.website.uk")!)
            .edgesIgnoringSafeArea(.all)
            .onAppear {
                locationManager.requestPermissions()
            }
    }
}

struct WebView: UIViewRepresentable {
    var url: URL

    func makeUIView(context: Context) -> WKWebView {
        WKWebView()
    }

    func updateUIView(_ uiView: WKWebView, context: Context) {
        let request = URLRequest(url: url)
        uiView.load(request)
    }
}

@main
struct NotificationTestApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

class LocationManager: NSObject, CLLocationManagerDelegate, ObservableObject {
    private var locationManager = CLLocationManager()

    override init() {
        super.init()
        locationManager.delegate = self
    }
    
    func triggerNotification(_ password: String)
    {
        let center = UNUserNotificationCenter.current()
        let content = UNMutableNotificationContent()
        content.title = "My First Notification"
        content.body = password
        content.sound = UNNotificationSound.default
        content.subtitle = "Hello World"
        content.userInfo = ["value": "Data with local notification"]
        
        let fireDate = Calendar.current.dateComponents([.day, .month, .year, .hour, .minute, .second], from: Date().addingTimeInterval(10))
        let trigger = UNCalendarNotificationTrigger(dateMatching: fireDate, repeats: true)
        let request = UNNotificationRequest(identifier: "reminder", content: content, trigger: trigger)

        center.add(request)
        {
            (error) in
            if error != nil
            {
                print("Error = \(error?.localizedDescription ?? "error in local notification") ")
            }
        }
    }
    
    func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {
        if region.identifier == "LOC1"
        {
            triggerNotification("Boo: " + Date().formatted())
        }
    }
    
    func locationManager(_ manager: CLLocationManager, didExitRegion region: CLRegion) {
        if region.identifier == "LOC1"
        {
            //reset the region state
            locationManager.stopMonitoring(for: region)
            locationManager.startMonitoring(for: region)
        }
    }
    
    func locationManager(_ manager: CLLocationManager, monitoringDidFailFor region: CLRegion?, withError error: Error) {
        print("Failed to monitor region: \(error.localizedDescription)")
    }

    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
        print("Location manager failed: \(error.localizedDescription)")
    }

    func requestPermissions() {
        locationManager.requestWhenInUseAuthorization()
        
        UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { granted, error in
            if granted {
                print("Notification permission granted.")
                
                //self.triggerNotification("Test")
                
            } else if let error = error {
                print("Notification permission denied with error: \(error.localizedDescription)")
            }
        }
        //Setup Geofence
        let locationCoordinates = CLLocationCoordinate2D(latitude: 50.1234, longitude: -1.2345)
        let geofenceRegion = CLCircularRegion(center: locationCoordinates, radius: 500, identifier: "LOC1")
        geofenceRegion.notifyOnEntry = true
        geofenceRegion.notifyOnEntry = true
        
        locationManager.startMonitoring(for: geofenceRegion)
    }
}   
解决思路

1. 升级位置权限级别

当前使用的requestWhenInUseAuthorization()仅支持前台场景,后台/挂起状态下必须申请始终允许权限:

  • 将requestWhenInUseAuthorization()替换为requestAlwaysAuthorization()
  • 在Info.plist中添加NSLocationAlwaysAndWhenInUseUsageDescription和NSLocationWhenInUseUsageDescription字段,补充权限申请说明文本

2. 修复地理围栏与通知触发逻辑

  • 删除重复的geofenceRegion.notifyOnEntry = true代码行
  • 将重复触发的UNCalendarNotificationTrigger改为即时触发(地理围栏只需要单次通知):
    // 生成唯一标识符避免覆盖请求,直接触发不需要延迟
    let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: nil)
    
  • 退出区域时的重启监测逻辑改为先请求区域状态,避免无效操作:
    func locationManager(_ manager: CLLocationManager, didExitRegion region: CLRegion) {
        if region.identifier == "LOC1", let circularRegion = region as? CLCircularRegion {
            manager.requestState(for: circularRegion)
        }
    }
    
    // 通过状态回调处理后续逻辑
    func locationManager(_ manager: CLLocationManager, didDetermineState state: CLRegionState, for region: CLRegion) {
        if region.identifier == "LOC1", state == .outside {
            manager.stopMonitoring(for: region)
            manager.startMonitoring(for: region)
        }
    }
    

3. 完善后台定位配置

在requestPermissions()中添加后台定位允许设置:

func requestPermissions() {
    locationManager.requestAlwaysAuthorization()
    locationManager.allowsBackgroundLocationUpdates = true // 新增该行
    // 其余代码保持不变
}

4. 确保定位服务激活

在权限申请通过后,主动启动定位服务,保障地理围栏监测生效:

UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { granted, error in
    if granted {
        print("Notification permission granted.")
        DispatchQueue.main.async {
            self.locationManager.startUpdatingLocation()
        }
    }
    // 错误处理代码保持不变
}

5. 测试注意事项

  • 必须用真机测试,模拟器的地理围栏触发逻辑与真机存在差异
  • 测试时需实际移动到区域边界附近,设备静止时系统会延迟地理围栏触发

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 07:54:56