当我在另一个视图中使用 CoreData 保存新数据时如何更新模型的另一个实例

Posted

技术标签:

【中文标题】当我在另一个视图中使用 CoreData 保存新数据时如何更新模型的另一个实例【英文标题】:How to update another instance of a model when I save new data using CoreData in another view 【发布时间】:2021-01-19 18:55:16 【问题描述】:

我正在构建一个简单的 Quiz ios 应用程序,它使用 CoreData 来存储测验中使用的问题。

我的主屏幕视图导航到“播放演示测验”、“播放我的测验”和“添加新问题”视图。

struct ContentView: View 
var quizViewModel = QuizViewModel(categoryIndex: 1)

var body: some View 
    NavigationView
        VStack
            Spacer()
            Text("Quizzy").font(.largeTitle).bold()
            Spacer()
            NavigationLink(
                destination: MainQuizView(categoryIndex: 0),
                label: 
                    DefaultNavigationLinkLabel(labelText: "Play Demo Quiz")
                
            )
            NavigationLink(
                destination: MainQuizView(categoryIndex: 1),
                label: 
                    DefaultNavigationLinkLabel(labelText: "Play My Quiz").padding()
                
            )
            NavigationLink(
                destination: AddQuestionView(),
                label: 
                    DefaultNavigationLinkLabel(labelText: "Add New Questions")
                
            )
            Spacer()
            Spacer()
        .navigationBarTitle(Text("Quizzy"), displayMode: .inline)
    .onAppear()
        quizViewModel.deleteAllQuestions()
        UserDefaults.standard.set(false, forKey: "launchedBefore")
        if(!quizViewModel.checkIfLaunchedBefore())
            quizViewModel.savePresetDemoQuestions()
            print("First time.")
        else
            print("Launched before.")
        
    

这些视图中的每一个都有自己的 QuizViewModel,其中有一个 QuizModel

class QuizViewModel : ObservableObject

private let categoryIndex: Int
@Published private var quizModel: QuizModel
private(set) var currentQuestion: Question?

init(categoryIndex: Int) 
    self.categoryIndex = categoryIndex
    self.quizModel = QuizModel(categoryIndex: categoryIndex)
    currentQuestion = Question()
    if(getQuestionCount() != 0)
        getCurrentQuestion()
    


func checkAnswer(answerIndex: Int) -> Bool
    quizModel.checkAnswer(answerIndex: answerIndex)


func getCurrentQuestion() -> Bool
    do
        try currentQuestion = quizModel.getCurrentQuestion()
        return true
     catch 
        return false
    


func getQuestionCount() -> Int
    quizModel.getQuestionCount()


func getCurrentQuestionIndex() -> Int
    quizModel.getCurrentQuestionIndex()


func getUserScore() -> Int
    quizModel.getUserScore()


func restartGame()
    quizModel.restartGame(categoryIndex: categoryIndex)
    getCurrentQuestion()


func deleteAllQuestions()
    quizModel.deleteAllQuestions()
    UserDefaults.standard.set(false, forKey: "launchedBefore")


func savePresetDemoQuestions()
    self.quizModel.savePresetDemoQuestions()
    restartGame()


func saveQuestion(questionText: String, answers: [String], correctAnswerIndex: Int)
    self.quizModel.saveQuestion(questionText: questionText, answers: answers, correctAnswerIndex: correctAnswerIndex, categoryIndex: 1)
    restartGame()


func saveDummyQuestion()
    quizModel.saveDummyQuestion()
    restartGame()


// Utility functions
func checkIfLaunchedBefore() -> Bool
    let launchedBefore = UserDefaults.standard.bool(forKey: "launchedBefore")
    if launchedBefore  
        return launchedBefore
     else 
        print("First launch, setting UserDefault.")
        UserDefaults.standard.set(true, forKey: "launchedBefore")
    
    return false
   

这是测验模型

struct QuizModel
private var questions: [Question] = []
private let categoryIndex: Int
private var currentQuestionIndex: Int = 0
private let coreDataManager = CoreDataManager()

var userScore = 0;

init(categoryIndex: Int)
    self.categoryIndex = categoryIndex
    loadQuestions(categoryIndex: categoryIndex)


enum QuizQuestionSetError: Error
    case outOfQuestions;


func getCurrentQuestion() throws -> Question
    guard currentQuestionIndex < questions.count else
        throw QuizQuestionSetError.outOfQuestions
    
    
    return questions[currentQuestionIndex]


mutating func checkAnswer(answerIndex: Int) -> Bool
    var isCorrect: Bool = false
    
    if(answerIndex == questions[currentQuestionIndex].correctAnswerIndex)
        userScore += 1
        isCorrect = true
    
    currentQuestionIndex += 1
    return isCorrect


func getQuestionCount() -> Int
    questions.count


func getCurrentQuestionIndex() -> Int
    currentQuestionIndex


func getUserScore() -> Int
    userScore


mutating func loadQuestions(categoryIndex: Int)
    self.questions = coreDataManager.fetchQuestions().filter question in
        question.categoryIndex == categoryIndex
    .shuffled()


mutating func restartGame(categoryIndex: Int)
    loadQuestions(categoryIndex: categoryIndex)
    self.currentQuestionIndex = 0
    self.userScore = 0


func saveQuestion(questionText: String, answers: [String], correctAnswerIndex: Int, categoryIndex: Int)
    coreDataManager.saveQuestion(questionText: questionText, answers: answers, correctAnswerIndex: correctAnswerIndex, categoryIndex: categoryIndex)
    


mutating func savePresetDemoQuestions()
    guard let encodedDemoQuestions = self.readDemoJsonFile(forName: "DemoQuestions") else
        return
    
    
    if let decodedDemoQuestions = self.parse(jsonData: encodedDemoQuestions)
        for demoQuestion in decodedDemoQuestions 
            coreDataManager.saveQuestion(questionText: demoQuestion.questionText,
                                         answers: demoQuestion.answers,
                                         correctAnswerIndex: demoQuestion.correctAnswerIndex,
                                         categoryIndex: demoQuestion.categoryIndex)
        
        print(decodedDemoQuestions.count)
    
    print("Updating questions.....")
    self.loadQuestions(categoryIndex: categoryIndex)
    

// MARK: - Utility Methods
private func readDemoJsonFile(forName name: String) -> Data? 
    do
        if let bundlePath = Bundle.main.path(forResource: name, ofType: "json"),
           let jsonData = try String(contentsOfFile: bundlePath).data(using: .utf8)
            return jsonData
        
     catch 
        print(error)
    
    
    return nil


private func parse(jsonData: Data) -> [QuestionStruct]?
    do 
        let decodedData = try JSONDecoder().decode([QuestionStruct].self, from: jsonData)
        return decodedData
     catch
        print(error)
    
    return nil


func saveDummyQuestion()
    saveQuestion(questionText: "AAAAAAAAAAA", answers: ["N","B","C","D"], correctAnswerIndex: 2, categoryIndex: 1)


func deleteAllQuestions()
    print("\n\n\n Deleted")
    coreDataManager.deleteAllQuestions()

那么,当使用该模型的另一个实例将问题保存在“AddQuestionView”中时,我如何告诉我的实例化“播放我的测验”模型重新加载他的问题? 如果需要,可以在这里访问完整的项目:https://github.com/MateoA/Quizzy

提前非常感谢,对于这个混乱的问题,我们深表歉意。

【问题讨论】:

【参考方案1】:

要观察/侦听 CoreData 更改,您需要使用 View 中为 SwiftUI 制作的 @FetchRequest 包装器,或使用包装在 ObservableObject 中的传统 FetchedRequestController(因为您有 CoreData 管理器)

它有很多代码,但CoreData Programming Guide 有一个很好的教程,介绍如何在 TableView 中创建它。最大的不同是,您将更新 ObservableObject 中的变量,而不是更新 TableView。

func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) 
    //Update an @Published variable here

Tutorial Combo Video

另外,你应该观察QuizViewModel并将它传递给appropriately

//In the ContentView
@StateObject var quizViewModel = QuizViewModel(categoryIndex: 1)
    //Initialize the Views that need the model like this
AddQuestionView().environmentObject(quizViewModel)

//All Views that need the Model
struct AddQuestionView:View
   @EnvironmentObject var quizViewModel = QuizViewModel
  //...

如果您查看Apple SwiftUI tutorials,您可能能够最大限度地发挥 SwiftUI 的优势

【讨论】:

以上是关于当我在另一个视图中使用 CoreData 保存新数据时如何更新模型的另一个实例的主要内容,如果未能解决你的问题,请参考以下文章

在 tableViewController 中使用重新加载的 CoreData

保存 CoreData 上下文和绑定问题

为啥我在 SwiftUI 中重新打开应用程序时没有保存 CoreData?

CoreData 应用程序中的“保存”

使用 CoreData 保存图像后分辨率发生变化

核心数据故障