Realm本地存储出现重复记录及导航跳转异常问题求助
问题描述
使用Realm进行本地数据存储时,基础功能运行正常,但存在以下两个异常:
- 添加带导航链接的新记录时,界面出现重复记录
- 点击记录尝试跳转导航页面时,第一条记录无响应,第二条及后续记录可正常跳转
相关代码
Model代码
import SwiftUI import RealmSwift struct Task: Identifiable { var id: String var title: String var completed: Bool = false var completedAt: Date = Date() init(taskObject: TaskObject) { self.id = taskObject.id.stringValue self.title = taskObject.title self.completed = taskObject.completed self.completedAt = taskObject.completedAt } }
持久化Model代码
import Foundation import RealmSwift class TaskObject: Object { @Persisted(primaryKey: true) var id: ObjectId @Persisted var title: String @Persisted var completed: Bool = false @Persisted var completedAt: Date = Date() }
ViewModel代码
final class TaskViewModel: ObservableObject { @Published var tasks: [Task] = [] private var token: NotificationToken? init() { setupObserver() } deinit { token?.invalidate() } private func setupObserver() { do { let realm = try Realm() let results = realm.objects(TaskObject.self) token = results.observe({ [weak self] changes in self?.tasks = results.map(Task.init) .sorted(by: { $0.completedAt > $1.completedAt }) .sorted(by: { !$0.completed && $1.completed }) }) } catch let error { print(error.localizedDescription) } } func addTask(title: String) { let taskObject = TaskObject(value: [ "title": title, "completed": false ]) do { let realm = try Realm() try realm.write { realm.add(taskObject) } } catch let error { print(error.localizedDescription) } } func markComplete(id: String, completed: Bool) { do { let realm = try Realm() let objectId = try ObjectId(string: id) let task = realm.object(ofType: TaskObject.self, forPrimaryKey: objectId) try realm.write { task?.completed = completed task?.completedAt = Date() } } catch let error { print(error.localizedDescription) } } func remove(id: String) { do { let realm = try Realm() let objectId = try ObjectId(string: id) if let task = realm.object(ofType: TaskObject.self, forPrimaryKey: objectId) { try realm.write { realm.delete(task) } } } catch let error { print(error.localizedDescription) } } func updateTitle(id: String, newTitle: String) { do { let realm = try Realm() let objectId = try ObjectId(string: id) let task = realm.object(ofType: TaskObject.self, forPrimaryKey: objectId) try realm.write { task?.title = newTitle } } catch let error { print(error.localizedDescription) } } }
ContentView代码
struct ContentView: View { var body: some View { NavigationView { VStack { AddTaskView() TaskListView() } .navigationTitle("Todo") .navigationBarTitleDisplayMode(.automatic) } } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } }
AddTaskView代码
import SwiftUI struct AddTaskView: View { @State private var taskTitle: String = "" @EnvironmentObject private var viewModel: TaskViewModel var body: some View { HStack(spacing: 12) { TextField("Enter New Task..", text: $taskTitle) Button(action: handleSubmit) { Image(systemName: "plus") } } .padding(20) } private func handleSubmit() { viewModel.addTask(title: taskTitle) taskTitle = "" } }
原TaskListView代码(有问题版本)
struct TaskListView: View { @EnvironmentObject private var viewModel: TaskViewModel var body: some View { ScrollView { LazyVStack (alignment: .leading) { ForEach(viewModel.tasks, id: \.id) { task in TaskRowView(task: task) Divider().padding(.leading, 20) NavigationLink (destination: TaskView(task: task)) { TaskRowView(task: task) }.animation(.default) } } } } }
TaskRowView代码
struct TaskRowView: View { let task: Task @EnvironmentObject private var viewModel: TaskViewModel var body: some View { HStack(spacing: 12) { Button(action: { viewModel.markComplete(id: task.id, completed: !task.completed) }) { Image(systemName: task.completed ? "checkmark.circle.fill" : "circle") .resizable() .frame(width: 20, height: 20) .foregroundColor(task.completed ? Color.green : Color.gray) } VStack(alignment: .leading, spacing: 8) { Text(task.title) .foregroundColor(.black) if !task.completedAt.formatted().isEmpty { Text(task.completedAt.formatted()) .foregroundColor(.gray) .font(.caption) } } Spacer() } .padding(EdgeInsets(top: 16, leading: 20, bottom: 16, trailing: 20)) } }
问题原因
- 重复记录:原
TaskListView的循环中,每个任务被渲染了两次——一次是独立的TaskRowView,另一次是嵌套在NavigationLink中的TaskRowView,导致界面显示重复条目。 - 第一条记录无响应:第一条记录的
NavigationLink被上方的独立TaskRowView遮挡,点击事件被上层视图捕获,无法触发导航跳转。
修复方案
1. 修复重复记录与导航无响应问题
修改TaskListView,将TaskRowView直接作为NavigationLink的内容,移除重复的独立视图:
struct TaskListView: View { @EnvironmentObject private var viewModel: TaskViewModel var body: some View { ScrollView { LazyVStack(alignment: .leading) { ForEach(viewModel.tasks, id: \.id) { task in NavigationLink(destination: TaskView(task: task)) { TaskRowView(task: task) } Divider().padding(.leading, 20) } } } } }
2. 保留按钮点击功能(可选)
修改后的代码中,NavigationLink会捕获整个行的点击事件,导致TaskRowView中的完成状态切换按钮可能无法正常响应。可以通过以下方式修复:
给按钮添加样式和高优先级手势,确保按钮事件优先被触发:
struct TaskRowView: View { let task: Task @EnvironmentObject private var viewModel: TaskViewModel var body: some View { HStack(spacing: 12) { Button(action: { viewModel.markComplete(id: task.id, completed: !task.completed) }) { Image(systemName: task.completed ? "checkmark.circle.fill" : "circle") .resizable() .frame(width: 20, height: 20) .foregroundColor(task.completed ? Color.green : Color.gray) } .buttonStyle(.plain) // 移除按钮默认样式,避免影响布局 .highPriorityGesture(TapGesture()) // 让按钮优先响应点击 VStack(alignment: .leading, spacing: 8) { Text(task.title) .foregroundColor(.black) if !task.completedAt.formatted().isEmpty { Text(task.completedAt.formatted()) .foregroundColor(.gray) .font(.caption) } } Spacer() } .padding(EdgeInsets(top: 16, leading: 20, bottom: 16, trailing: 20)) } }
内容的提问来源于stack exchange,提问作者user8705895
相关产品推荐
相关产品推荐

