如何将UserDefaults笔记数据同步到Apple Watch?同步失败求助
问题描述
我正在开发一款跨iPhone和Apple Watch的笔记应用,支持创建笔记和子笔记,两端CRUD操作已完成,但Watch端无法查看数据。采用UserDefaults共享组存储数据,尝试在NoteViewModel实现NSObject和WCSessionDelegate协议但未解决问题,启动应用时输出会话状态WCSessionActivationState(rawValue: 2)(即会话已激活)。
开发环境:Xcode 16 beta、iOS 18 beta、WatchOS 11 beta
相关代码
NoteViewModel.swift
import Combine import Foundation import WatchConnectivity class NoteViewModel: NSObject, WCSessionDelegate, ObservableObject { @Published var notes: [Note] = [] func addNote(title: String) { let newNote = Note(title: title, subNotes: []) notes.append(newNote) saveNotes() } func addSubNote(to note: Note, title: String, content: String) { if let index = notes.firstIndex(where: { $0.id == note.id }) { let newSubNote = SubNote(title: title, content: content) notes[index].subNotes.append(newSubNote) saveNotes() } } func removeNote(note: Note) { notes.removeAll { $0.id == note.id } saveNotes() } func removeSubNote(from note: Note, subNote: SubNote) { if let index = notes.firstIndex(where: { $0.id == note.id }) { notes[index].subNotes.removeAll { $0.id == subNote.id } saveNotes() } } func updateNote(note: Note, newTitle: String) { if let index = notes.firstIndex(where: { $0.id == note.id }) { notes[index].title = newTitle saveNotes() } } override init() { super.init() if WCSession.isSupported() { WCSession.default.delegate = self WCSession.default.activate() self.notes = loadNotes() } } func session( _ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error? ) { if let error = error { print("Error al activar la sesión: \(error.localizedDescription)") } else { print("Sesión activada con estado: \(activationState)") } } #if os(iOS) func sessionDidBecomeInactive(_ session: WCSession) { } func sessionDidDeactivate(_ session: WCSession) { session.activate() } func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: @escaping ([String : Any]) -> Void) { if let notesArray = message["notes"] as? [[String: Any]] { let decoder = JSONDecoder() if let data = try? JSONSerialization.data(withJSONObject: notesArray, options: []), let notes = try? decoder.decode([Note].self, from: data) { self.notes = notes saveNotes() } } } #endif private func saveNotes() { let encoder = JSONEncoder() if let encoded = try? encoder.encode(notes) { if let defaults = UserDefaults(suiteName: "group.lucasleone.WatchNotesApp") { defaults.set(encoded, forKey: "notes") } sendNotesToWatch(notes: notes) } } private func loadNotes() -> [Note] { if let defaults = UserDefaults(suiteName: "group.lucasleone.WatchNotesApp"), let savedNotes = defaults.data(forKey: "notes") { let decoder = JSONDecoder() if let loadedNotes = try? decoder.decode([Note].self, from: savedNotes) { return loadedNotes } } return [] } private func sendNotesToWatch(notes: [Note]) { let encoder = JSONEncoder() if let encoded = try? encoder.encode(notes) { let notesDict = try? JSONSerialization.jsonObject(with: encoded, options: []) as? [[String: Any]] if WCSession.default.isReachable { WCSession.default.sendMessage(["notes": notesDict ?? []], replyHandler: nil, errorHandler: { error in print("Error enviando mensaje: \(error.localizedDescription)") }) } } } }
Watch端HomeView.swift
struct HomeView: View { @StateObject var viewModel = NoteViewModel() var body: some View { NavigationView { List { ForEach(viewModel.notes) { note in NavigationLink( destination: NoteDetailView( note: note, viewModel: viewModel) ) { Text(note.title) } } } .navigationTitle("Notas") } } }
解决方案
1. 补全Watch端WCSession消息处理逻辑
当前NoteViewModel仅在iOS端实现了消息接收,Watch端既没处理iOS发来的消息,也没有主动请求数据的逻辑。修改如下:
步骤1:统一消息接收逻辑
移除#if os(iOS)对didReceiveMessage的包裹,让两端都能处理消息,同时添加数据请求的响应逻辑:
func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: @escaping ([String : Any]) -> Void) { // 接收iOS推送的笔记数据 if let notesData = message["notesData"] as? Data { do { let notes = try JSONDecoder().decode([Note].self, from: notesData) DispatchQueue.main.async { // 必须在主线程更新@Published属性 self.notes = notes self.saveNotes() } replyHandler(["status": "success"]) } catch { print("解码数据失败: \(error.localizedDescription)") replyHandler(["status": "error", "message": error.localizedDescription]) } } // 响应Watch端的数据请求 else if message["requestNotes"] as? Bool == true { do { let data = try JSONEncoder().encode(notes) replyHandler(["notesData": data]) } catch { replyHandler(["status": "error", "message": error.localizedDescription]) } } }
步骤2:Watch端主动请求数据
在NoteViewModel初始化方法中添加主动请求逻辑:
override init() { super.init() if WCSession.isSupported() { WCSession.default.delegate = self WCSession.default.activate() self.notes = loadNotes() // Watch端启动后主动向iPhone请求数据 requestNotesFromiPhone() } } // 新增方法:向iPhone请求笔记数据 private func requestNotesFromiPhone() { guard WCSession.default.isReachable else { print("iPhone不可达,无法请求数据") return } WCSession.default.sendMessage(["requestNotes": true], replyHandler: { response in if let notesData = response["notesData"] as? Data { do { let notes = try JSONDecoder().decode([Note].self, from: notesData) DispatchQueue.main.async { self.notes = notes self.saveNotes() } } catch { print("解码请求数据失败: \(error.localizedDescription)") } } }, errorHandler: { error in print("请求数据失败: \(error.localizedDescription)") }) }
2. 修复UserDefaults共享组同步问题
- 确认iOS和WatchOS target均已启用App Groups功能,并加入同一个组
group.lucasleone.WatchNotesApp - 在
saveNotes中添加同步操作,避免写入延迟:
private func saveNotes() { let encoder = JSONEncoder() if let encoded = try? encoder.encode(notes), let defaults = UserDefaults(suiteName: "group.lucasleone.WatchNotesApp") { defaults.set(encoded, forKey: "notes") defaults.synchronize() // 强制同步共享组数据 sendNotesToWatch(notes: notes) } }
3. 简化数据序列化流程
当前代码存在冗余的序列化步骤,直接传输Data更高效且避免错误:
private func sendNotesToWatch(notes: [Note]) { guard WCSession.default.isReachable else { return } do { let data = try JSONEncoder().encode(notes) WCSession.default.sendMessage(["notesData": data], replyHandler: nil, errorHandler: { error in print("发送数据失败: \(error.localizedDescription)") }) } catch { print("编码笔记失败: \(error.localizedDescription)") } }
4. 确保模型正确实现Codable & Identifiable
Note和SubNote必须严格实现协议才能正常序列化和在ForEach中使用:
import Foundation struct Note: Identifiable, Codable { let id = UUID() var title: String var subNotes: [SubNote] } struct SubNote: Identifiable, Codable { let id = UUID() var title: String var content: String }
5. 会话激活后触发同步
在activationDidCompleteWith中添加激活后的同步逻辑:
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) { if let error = error { print("会话激活失败: \(error.localizedDescription)") } else { print("会话激活状态: \(activationState)") #if os(watchOS) // Watch端激活后再次尝试请求数据 requestNotesFromiPhone() #else // iOS端激活后主动推送最新数据 sendNotesToWatch(notes: notes) #endif } }
内容的提问来源于stack exchange,提问作者Lucas Leone
相关产品推荐
相关产品推荐

