UIKit Core Data Refreshing View Controller Data

Hi Everyone,

I am working on creating a journal style app in core data. I am having issues with updating views when the core data entries are changed and looking for some help.

I have 3 view controllers (VC 1 is a table view of all the journal entries; VC 2 is a table view with a single cell entry displaying the different entry data; VC 3 is a table view with a single cell where the entries are text fields so they can be changed). User clicks on a cell in VC 1 and it presents VC 2 which is a static display of the data. It passes in the entry that the user tapped on VC1 into VC2 to populate the data. There is an edit button that the user taps which displays VC 3. When the user is in VC 3 they can edit any of the entry attributes. On VC 3 there is a save button which saves the data to Core Data and dismisses VC 3 showing the user VC 2. When VC 2 reappears, none of the updated data is there. I have to close the app and re-navigate to VC 2 to see the data.

I know in SwiftUI I could use @Environment object but what about UI Kit? I’ve tried delegates and protocols and haven’t had any luck. I think the issue is that the data in VC 2 is being configured and passed in from VC 1.

My problem is that when VC 3 dismisses, the values in VC2 do not update. Can anyone please offer some guidance?

Thank you!

Code below for 3 separate view controllers:

import UIKit
import CoreData

class TableViewController1: UITableViewController {
    
    var entry: JournalEntry?
    var journalEntries: [JournalEntry] = []
    private let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
    var fetchedEntryRC:NSFetchedResultsController<JournalEntry>?
    private let appDelegate = UIApplication.shared.delegate as! AppDelegate
    
    @IBOutlet weak var tableView: UITableView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // Set datasource and delegate for the table
        tableView.delegate = self
        tableView.dataSource = self
        
        
    }
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        
        // Get a reference to VC2
        
        let VC2 = storyboard?.instantiateViewController(identifier: Constants.Storyboard.VC2) as! TableViewController2
        
        // Get the entry for this indexpath and pass to VCc
        
        let entry = indexPath.row
        
        VC2.delegate = self
        
        VC2.entry = entry
        
        VC2.modalPresentationStyle = .overFullScreen
        
        present(VC2, animated: true) {
            print("presented")
        }
    }
    
    override func numberOfSections(in tableView: UITableView) -> Int {
        // #warning Incomplete implementation, return the number of sections
        return 1
    }
    
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        // #warning Incomplete implementation, return the number of rows
        return journalEntries.count
    }
    
    func refresh() {
        
        // Get a fetch request for ShaveEntries
        let request:NSFetchRequest<JournalEntry> = JournalEntry.fetchRequest()
        
        // Set a sort descriptor
        
        let sortByDate = NSSortDescriptor(key: "date", ascending: false)
        
        // Sort the data by the sort descriptors
        request.sortDescriptors = [sortByDate]
        
        do {
            // Exectute the fetch
            journalEntries = try context.fetch(request)
        }
        catch {
            print("Couldn't fetch entries")
        }
        // Tell tableview to request data
        tableView.reloadData()
        
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        
        // Create a reusable cell
        let cell = tableView.dequeueReusableCell(withIdentifier: Constants.Storyboard.ENTRY_CELL) as! EntryCell
        
        let entry = indexPath.row
        
        // Pass the entry to the cell View and configure it
        cell.setCell(entry)
        
        return cell
        
    } //: cellForRowAt
    
    extension TableViewController1: EntryModifiedProtocol {
        func entryChanged() {
            refresh()
        }
    }
}


protocol EntryModifiedProtocol {
    func entryChanged()
}

class TableViewController2: UITableViewController {
    
    private let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
    var fetchedEntryRC:NSFetchedResultsController<ShaveEntry>?
    private let appDelegate = UIApplication.shared.delegate as! AppDelegate
    
    // Variables
    var entry: JournalEntry?
    
    var delegate:EntryModifiedProtocol?
    
    // Text Outlets
    @IBOutlet weak var dateLabel: UILabel!
    @IBOutlet weak var nameLabel: UILabel!
    
    @IBOutlet weak var editButton: UIButton!
    @IBOutlet weak var backButton: UIButton!
    @IBOutlet weak var notesTV: UITextView!
    
    @IBOutlet var entryTableView: UITableView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        entryTableView.dataSource = self
        entryTableView.delegate = self
    } //: viewDidLoad
    
    override func viewWillAppear(_ animated: Bool) {
        
        if entry !== nil && entry?.date != nil {
            
            // Configure the entry fields
            configureLabels(entry: entry!)
        }
    }
    
    func formatDate(_ date:Date) -> String {
        let date = entry!.date
        let df = DateFormatter()
        df.dateFormat = "MM/dd/yyyy"
        return df.string(from: date!)
    }
    
    func configureLabels(entry:ShaveEntry) {
        dateLabel.text =  formatDate(entry.date!)
        nameLabel.text = entry.name
        notesTV.text = entry.notes
    }
    
    override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
    
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 1
    }
    
    @IBAction func editTapped(_ sender: Any) {
        
        let VC3 = storyboard?.instantiateViewController(identifier: Constants.Storyboard.VC3) as! TableViewController3
        
        VC3.entry = entry
        
        VC3.delegate = self
        
        VC3.modalPresentationStyle = .overFullScreen
        
        present(VC3, animated: true, completion: nil)
    }

    @IBAction func backTapped(_ sender: Any) {
        
        dismiss(animated: true) {
            print("dismissed")
        }
    }
}

protocol ChangedEntryDelegate {
    func EntryChanged()
}

class TableViewController3: UITableViewController, UITextFieldDelegate{

    var delegate:ChangedEntryDelegate?
    
    //References to App delegate and Core Data context
    private let aDelegate = UIApplication.shared.delegate as! AppDelegate
    private let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
    
    // Variables
    var entry: JournalEntry?
  
    @IBOutlet weak var datePicker: UIDatePicker!
   
    // Button Outlets
    @IBOutlet weak var cancelButton: UIButton!
    @IBOutlet weak var saveButton: UIButton!
    
    // Text Field Outlets
    @IBOutlet weak var nameTF: UITextField!
    @IBOutlet weak var notesTV: UITextView!
    @IBOutlet weak var scrollView: UIScrollView!

    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // Set date picker to compact calendar and date only
        datePicker.preferredDatePickerStyle = .compact
        datePicker.datePickerMode = .date

        if entry != nil && entry?.date != nil {
            // This is an exising entry, so need to present the data for that entry
            datePicker.date = entry!.date!
            nameTF.text = entry!.name
            notesTV.text = entry!.notes
        }
        // Set the textfield delegates to self
        nameTF.delegate = self
    }
    
    @IBAction func saveTapped(_ sender: Any) {
        
        // Check if creating a new entry or edititng an exiting entry
        if entry == nil {
            
            // Create a new entry
            let e = JournalEntry(context: context)
            
            // Configure the properties
            e.date = datePicker.date
            e.name = nameTF.text
            e.notes = notesTV.text
        }
        else {
            // Editing an existing entry, so update values (entry != nil, so can force unwrap entry)
            
            entry!.date = datePicker.date
            entry!.name = nameTF.text
            entry!.notes = notesTV.text
        } //: Else
        
        // Save the core data context
        aDelegate.saveContext()
        
        // Let the delegate know that the entry was added
        delegate?.EntryChanged()
        
        // Dismiss the popup
        dismiss(animated: true, completion: nil)
    }
}

Matt,

I just realized this post is a while ago… For what it is worth I struggled with the same problem with not having my view refresh after changing data in an ObservableObject. Footnote: I am a complete novice to Swift so be careful.

At any rate, I created a StateObject on entry to my app and also make it an environmentObject. My child views were using ObservedObjects of my viewModel. I changed the child views to EnvironmentObject of my viewModel and the views refreshed properly.

Dumb luck that I stepped on the right land mine and got it to work. Hope that is of some help.

Tom