Files
digital-pilates/ios/OutLive/AppDelegate.swift
richarjiang ea22901553 feat(background-task): 完善iOS后台任务系统并优化断食通知和UI体验
- 修复iOS后台任务注册时机问题,确保任务能正常触发
- 添加后台任务调试辅助工具和完整测试指南
- 优化断食通知系统,增加防抖机制避免频繁重调度
- 改进断食自动续订逻辑,使用固定时间而非相对时间计算
- 优化统计页面布局,添加身体指标section标题
- 增强饮水详情页面视觉效果,改进卡片样式和配色
- 添加用户反馈入口到个人设置页面
- 完善锻炼摘要卡片条件渲染逻辑
- 增强日志记录和错误处理机制

这些改进显著提升了应用的稳定性、性能和用户体验,特别是在iOS后台任务执行和断食功能方面。
2025-11-05 11:23:33 +08:00

190 lines
6.7 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import Expo
import React
import ReactAppDependencyProvider
import BackgroundTasks
@UIApplicationMain
public class AppDelegate: ExpoAppDelegate {
var window: UIWindow?
var reactNativeDelegate: ExpoReactNativeFactoryDelegate?
var reactNativeFactory: RCTReactNativeFactory?
public override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
) -> Bool {
// super.application()
// BGTaskScheduler
if #available(iOS 13.0, *) {
registerBackgroundTasks()
}
let delegate = ReactNativeDelegate()
let factory = ExpoReactNativeFactory(delegate: delegate)
delegate.dependencyProvider = RCTAppDependencyProvider()
reactNativeDelegate = delegate
reactNativeFactory = factory
bindReactNativeFactory(factory)
#if os(iOS) || os(tvOS)
window = UIWindow(frame: UIScreen.main.bounds)
factory.startReactNative(
withModuleName: "main",
in: window,
launchOptions: launchOptions)
#endif
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
// MARK: - Application Lifecycle
public override func applicationDidEnterBackground(_ application: UIApplication) {
super.applicationDidEnterBackground(application)
// BackgroundTaskBridge
//
if #available(iOS 13.0, *) {
NSLog("[AppDelegate] 应用进入后台,后台任务由 BackgroundTaskBridge 管理")
}
}
// MARK: - Background Task Registration
@available(iOS 13.0, *)
private func registerBackgroundTasks() {
let identifier = "com.anonymous.digitalpilates.task"
//
//
BGTaskScheduler.shared.register(
forTaskWithIdentifier: identifier,
using: nil
) { [weak self] task in
// BackgroundTaskBridge
// bridge
self?.handleBackgroundTask(task, identifier: identifier)
}
NSLog("[AppDelegate] 后台任务已在应用启动时注册: \(identifier)")
}
@available(iOS 13.0, *)
private func handleBackgroundTask(_ task: BGTask, identifier: String) {
NSLog("[AppDelegate] ====== 后台任务被触发 ======")
NSLog("[AppDelegate] 任务标识符: \(identifier)")
NSLog("[AppDelegate] 任务类型: \(type(of: task))")
// iOS 30
task.expirationHandler = {
NSLog("[AppDelegate] ⚠️ 后台任务即将过期,强制完成")
task.setTaskCompleted(success: false)
}
// BackgroundTaskBridge
// React Native bridge
guard let bridge = reactNativeFactory?.bridge,
bridge.isValid else {
NSLog("[AppDelegate] React Native bridge 未就绪")
NSLog("[AppDelegate] 执行基本的后台任务并调度下一次")
// 使 JS
self.executeBasicBackgroundMaintenance(task: task, identifier: identifier)
return
}
NSLog("[AppDelegate] React Native bridge 已就绪,尝试获取 BackgroundTaskBridge 模块")
// bridge BackgroundTaskBridge
DispatchQueue.main.async {
if let module = bridge.module(for: BackgroundTaskBridge.self) as? BackgroundTaskBridge {
NSLog("[AppDelegate] ✅ 找到 BackgroundTaskBridge 模块,发送任务通知")
// BackgroundTaskBridge
NotificationCenter.default.post(
name: NSNotification.Name("BackgroundTaskBridge.handleTask"),
object: nil,
userInfo: ["task": task, "identifier": identifier]
)
} else {
NSLog("[AppDelegate] ❌ BackgroundTaskBridge 模块未找到")
NSLog("[AppDelegate] 执行基本的后台任务并调度下一次")
self.executeBasicBackgroundMaintenance(task: task, identifier: identifier)
}
}
}
@available(iOS 13.0, *)
private func executeBasicBackgroundMaintenance(task: BGTask, identifier: String) {
NSLog("[AppDelegate] 执行基本后台维护任务")
// 线
DispatchQueue.global(qos: .background).async {
//
//
//
Thread.sleep(forTimeInterval: 2.0)
NSLog("[AppDelegate] 基本后台维护完成")
//
task.setTaskCompleted(success: true)
//
self.scheduleNextBackgroundTask(identifier: identifier)
}
}
@available(iOS 13.0, *)
private func scheduleNextBackgroundTask(identifier: String) {
let request = BGAppRefreshTaskRequest(identifier: identifier)
// 15
request.earliestBeginDate = Date(timeIntervalSinceNow: 15 * 60)
do {
try BGTaskScheduler.shared.submit(request)
NSLog("[AppDelegate] ✅ 成功调度下一次后台任务15分钟后")
} catch {
NSLog("[AppDelegate] ❌ 调度下一次后台任务失败: \(error.localizedDescription)")
}
}
// Linking API
public override func application(
_ app: UIApplication,
open url: URL,
options: [UIApplication.OpenURLOptionsKey: Any] = [:]
) -> Bool {
return super.application(app, open: url, options: options) || RCTLinkingManager.application(app, open: url, options: options)
}
// Universal Links
public override func application(
_ application: UIApplication,
continue userActivity: NSUserActivity,
restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
) -> Bool {
let result = RCTLinkingManager.application(application, continue: userActivity, restorationHandler: restorationHandler)
return super.application(application, continue: userActivity, restorationHandler: restorationHandler) || result
}
}
class ReactNativeDelegate: ExpoReactNativeFactoryDelegate {
// Extension point for config-plugins
override func sourceURL(for bridge: RCTBridge) -> URL? {
// needed to return the correct URL for expo-dev-client.
bridge.bundleURL ?? bundleURL()
}
override func bundleURL() -> URL? {
#if DEBUG
return RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: ".expo/.virtual-metro-entry")
#else
return Bundle.main.url(forResource: "main", withExtension: "jsbundle")
#endif
}
}