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

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))
    }
}

问题原因

  1. 重复记录:原TaskListView的循环中,每个任务被渲染了两次——一次是独立的TaskRowView,另一次是嵌套在NavigationLink中的TaskRowView,导致界面显示重复条目。
  2. 第一条记录无响应:第一条记录的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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 23:42:51