tableView.reloadData() not updating user input data from persistent storage

Viewed 78

I'm new with ios dev and learning core data and stuck with updating tableview cells with reloaddata(). Here's the code in my GoalsVC:

import UIKit

import CoreData

//make app delegate accessible for all view controllers as in has core data things

let appDelegate = UIApplication.shared.delegate as? AppDelegate

class GoalsVC: UIViewController {

    @IBOutlet weak var tableView: UITableView!
    
    var goals: [Goal] = []
    
    override func viewDidLoad() {
        super.viewDidLoad()
        //set tableview delegate and source so that it knows what its delegate its datasource is from otherwise it does not know where its getting information from
        tableView.delegate = self
        tableView.dataSource = self
        tableView.isHidden = false
    }
    
    //need to think about when should we fetch the data. viewdidlaod only called once when the view is originally load. after we presented goalvc, creategoalvc on the top and dismiss them, viewdidload is not called bcs were just presenting a view on top of it and dismissing it.
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        self.fetch { (complete) in
        if complete {
            if goals.count >= 1 {
                //showing tableview or hide them depending if data is available or not
                tableView.isHidden = false
            } else {
                tableView.isHidden = true
            }
        }
    }
        self.tableView.reloadData()
}

I don't know why self.tableView.reloadData() at the end there won't update the tableview.

Here's the code I made for saving data in FinishGoalVC if it helps to give a clearer view:

func save(completion: (_ finished: Bool) -> ()) {
            
    
    guard let managedContext = appDelegate?.persistentContainer.viewContext else {
                return
            }
            
    
    //accessing core data properties
            
    let goal = Goal(context: managedContext)
            
    goal.goalDescription = goalDescription
    goal.goalType = goalType.rawValue
    goal.goalCompletionValue = Int32(pointsTextField.text!)!
    goal.goalProgress = Int32(0)
            
    //using save in order to commit unsaved changes to the context parents store
            
    do {
        try managedContext.save()
        completion(true)
    } catch {
        debugPrint("Could not save \(error.localizedDescription)")
        completion(false)
    }
}
3 Answers

Update the tableview once the fetch is completed. if goals.count >= 1 using !goals.isEmpty is good practice.

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    self.fetch { complete in
        if complete {
            tableView.isHidden = goals.isEmpty
            if !goals.isEmpty {
                //showing tableview or hide them depending if data is available or not
                DispatchQueue.main.async {
                    self.tableView.reloadData()
                }
            }
        }
    }
}

Just edit your function like below;

if goals.count >= 1 {
   //showing tableview or hide them depending if data is available or not
    tableView.isHidden = false
} else {
    tableView.isHidden = true
    self.tableView.reloadData()
}

Because your reload function work before fetching data due the threads. You should run this after fetched data.

Ok so I just figured out why it doesn't show the updated tableview. I'll just put this here in case someone's gonna face similar problems in the future (?) lol.

So yeah anyway,

  1. I put in breakpoints for tableView.reloadData() and checked the debugger and saw that it had data and it did invoke the reloadData() method when I finished creating goals. So, no issues with reloadData()

  2. However, the self.fetch call in viewWillAppear function was not run after I finished creating new goals. It only ran when starting the app and fetching the already created goals.

Before moving on, here are the initial code I used for UIViewController extension called presentDetail:

extension UIViewController {

   func presentDetail(_ viewControllerToPresent: UIViewController) {

    let transition = CATransition()

    transition.duration = 0.3

    transition.type = CATransitionType.push

    transition.subtype = CATransitionSubtype.fromRight

    self.view.window?.layer.add(transition, forKey: kCATransition)

    present(viewControllerToPresent, animated: false, completion: nil)        
}

and this extension was used for addGoal Button which presents the next VC, createGoalVC:

@IBAction func addGoalBtnWasPressed(_ sender: Any) {
    guard let createGoalVC = storyboard?.instantiateViewController(identifier: 
    "CreateGoalVC") else {
        return
    }
    presentDetail(createGoalVC)
}

}

I also had another extension for UIViewController which was:

func presentSecondaryDetail(_ viewControllerToPresent: UIViewController) {

    let transition = CATransition()

    transition.duration = 0.3

    transition.type = CATransitionType.push

    transition.subtype = CATransitionSubtype.fromRight
    
    guard let presentedViewController = presentedViewController else {return}
    
    presentedViewController.dismiss(animated: false) {

    self.view.window?.layer.add(transition, forKey: kCATransition)

    self.present(viewControllerToPresent, animated: false, completion: 
      nil)

    }
}

this function was used for nextButton which presents a final VC, finishGoalVC:

@IBAction func nextBtnWasPressed(_ sender: Any) {

    if goalTextView.text != "" && goalTextView.text != "What is your goal?" {
        guard let finishGoalVC = 

        storyboard?.instantiateViewController(identifier: "FinishGoalVC") as?

        FinishGoalVC else {
            return
        }
        finishGoalVC.initData(description: goalTextView.text!, type: goalType)
        
        presentingViewController?.presentSecondaryDetail(finishGoalVC)
    }
    
}

And because My first VC (GoalsVC) was not presented in fullscreen, the createGoalsVC and finishGoalsVC was not properly dismissed since GoalsVC was not covering the other two view controllers. If that makes any sense. Hence, why the viewWillAppear function was only called once and not after creating new goals.

So the final code for the UIViewController extension presentDetail function was changed to:

extension UIViewController {

func presentDetail(_ viewControllerToPresent: UIViewController) {

    let transition = CATransition()

    transition.duration = 0.3

    transition.type = CATransitionType.push
    
    transition.subtype = CATransitionSubtype.fromRight
    
    self.view.window?.layer.add(transition, forKey: kCATransition)
    
    let navigationController: UINavigationController = 

    UINavigationController(rootViewController: viewControllerToPresent)

    navigationController.modalPresentationStyle = .fullScreen

    present(navigationController, animated: false, completion: nil)
}

and for presentSecondaryDetail function:

func presentSecondaryDetail(_ viewControllerToPresent: UIViewController) {

    let transition = CATransition()
    transition.duration = 0.3

    transition.type = CATransitionType.push

    transition.subtype = CATransitionSubtype.fromRight
    
    guard let presentedViewController = presentedViewController else { return }
    
        presentedViewController.dismiss(animated: false) {

        self.view.window?.layer.add(transition, forKey: kCATransition)
        
        let navigationController: UINavigationController = 

        UINavigationController(rootViewController: viewControllerToPresent)

        navigationController.modalPresentationStyle = .fullScreen

        self.present(navigationController, animated: false, completion: nil)
    }
}

And that solved it! The tableview has been updated with new goals from the user :)

Related