Hoping you can help me out...I've been trying to figure this issue out for a couple days now.
I'm using Parse (www.parse.com) as my backend, and have hosted it on my own AWS server.
Structure of the app:
In AppDelegate, if user is logged in, show a ViewController that sets up my SlideMenuControllerSwift (https://github.com/dekatotoro/SlideMenuControllerSwift) and my TabBarController.
[Storyboard][1]
In my tab bar controller, I have a navigation controller that leads to a UITableViewController that segues to another UITableViewController when I click on a row.
Problem:
http://imgur.com/izdCBgt
1) I click on a row and it performs an asynch query to my parse database
2) The data populates the table and then disappears
3) If I change the tab and go back to the main tab, the data reappears
Also
1) I click on a row and it performs an asynch query to my parse database
2) The data populates the table and then disappears
3) If I go back to the original UITableViewController, it does not transition back properly, and I need to change tabs back and forth until it reappears
Code:
I segue to the documents table view controller using the storyboard segue. Here is the relevant code in my DocumentsTableViewController:
class DocumentTableViewController: UITableViewController, UIDocumentInteractionControllerDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate, MMCropDelegate {
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
print("initDocs")
}
var specificDocs=[Document](){
didSet{
dispatch_async(dispatch_get_main_queue(), {
//we might be called from the parse block which executes in seperate thread
self.loadView()
})
}
}
override func viewDidLoad() {
tableView.dataSource = self
tableView.delegate = self
print("we loadeD")
super.viewDidLoad()
navigationItem.title = "Job Documents"
let cameraButton = UIBarButtonItem(image: UIImage(named: "Camera"), style: .Plain, target: self, action: #selector(self.didPressCamera(_:)))
navigationItem.rightBarButtonItem = cameraButton
self.queryForTable()
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.tableView.reloadData()
}
// Define the query that will provide the data for the table view
func queryForTable() {
// Run a spinner to show a task in progress
let progressHUD = MBProgressHUD.showHUDAddedTo(self.view, animated: true)
progressHUD.label.text = "Loading..."
//2 using those jobActions to find which documents are mine
let query = PFQuery(className: Document.parseClassName())
query.whereKey(Document.jobActionCol(), containedIn: jobActions)
query.includeKey(Document.documentCategoryCol())
// do {
// let test = try query.findObjects()
// self.specificDocs = test as! [Document]
// progressHUD.hideAnimated(true)
// } catch {
// print("error!")
// }
// FIND WHY THIS DOESNT WORK....WHAT THE F
query.findObjectsInBackgroundWithBlock {
(objects: [PFObject]?, error: NSError?) -> Void in
if error == nil {
// The find succeeded.
self.specificDocs = objects as! [Document]
print("done")
progressHUD.hideAnimated(true)
} else {
// Log details of the failure
print("Error: \(error!) \(error!.userInfo)")
}
}
}
// MARK: - Table view data source
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return specificDocs.count
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell:UITableViewCell? = tableView.dequeueReusableCellWithIdentifier("cellIdentifier")
if ((cell) == nil){
cell = UITableViewCell.init(style: .Default, reuseIdentifier: "cellIdentifier")
}
cell!.textLabel?.text = specificDocs[indexPath.row].documentCategory!.type!
print("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
// print(UIApplication.sharedApplication().keyWindow?.performSelector("recursiveDescription"))
return cell!
}
deinit {
print("docs deinit")
}
}
Oh, now I see intention of your codes. But, Do you have some reason that doing? It isn't right way that directly call loadView().
tableView.dataSource = self
tableView.delegate = self
move to first in viewWillAppear and, It will be work except too fast done to receive from parse.
Normal way will be like below:
var specificDocs=[Document](){
didSet{
dispatch_async(dispatch_get_main_queue(), {
//we might be called from the parse block which executes in seperate thread
self.loadView()
})
}
self.loadView() to self.tableView.reloadData().
tableView.dataSource = self
tableView.delegate = self
That doesn't needs.
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.tableView.reloadData()}
That doesn't needs too.
Anyway, your codes will work just modify calling self.loadView() to self.tableView.reloadData()
Related
Sorry, I'm beginner to learn IOS.
I have a problem about my tableView and reload data.
When I frequently call "getData", I will crash and get error.
But I don't know where my data make it crash.
I guess I first call reloadData, and then the list.count are already changed in global thread
Have any advice to avoid it?
Thanks.
Crash Log:
fatal error: Index out of range
Model:
class ChatroomList:Model {
var all:[Chatroom] {
var rooms:[Chatroom] = [Chatroom]()
self.chatrooms.forEach({ (id,chatroom) in
if showType.contains(chatroom.type) {
rooms.append(chatroom)
}
})
return rooms
}
}
ViewController:
import RxCocoa
import RxSwift
import Alamofire
class ListViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
let chatrooms:ChatroomList = ChatroomList()
var list:[Chatroom] = [Chatroom]()
var subscribe:Disposable?
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.dataSource = self
self.tableView.delegate = self
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
subscribe = rooms.notifySubject.subscribe({ json in
self.getData() //here is called frequently
})
self.getData()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
subscribe?.dispose()
}
func getData() {
var idList:[String] = []
self.list.removeAll()
self.list = chatrooms.all
guard self.list.isEmpty == false else {
return
}
DispatchQueue.global().async() {
self.list.sort(by: { (a,b) in
if a.message.datetime.isEmpty {
return false
}
return a.message.datetime > b.message.datetime
})
self.list = self.list.filter { (chatroom) -> Bool in
if chatroom.id.isEmpty {
return true
}
if idList.contains(chatroom.id) {
return false
}
idList.append(chatroom.id)
return true
}
DispatchQueue.main.sync() {
self.tableView.reloadData()
}
}
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return list.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if list[indexPath.row].type == .city {
let cell: ChatroomCityTableViewCell = ChatroomCityTableViewCell(style: .default, reuseIdentifier: nil)
cell.loadByCityChatroom(chatroom: list[indexPath.row], cityId: list[indexPath.row].cityId)
return cell
}else{
let cell: ChatroomTableViewCell = ChatroomTableViewCell(style: .default, reuseIdentifier: nil)
cell.loadByChatroom(chatroom: list[indexPath.row])
return cell
}
}
The problem is most likely caused by how you currently use the GCD (Grand central dispatch).
When reloading, the tableView will ask many different questions like the number of rows and the cells for each of these rows. If the data changes between one of these calls it will result in an inconsistency exception because it tried to add or remove a number of row that no longer represents the data.
Reloading the tableView asynchronously on the main thread while your getData function can change the list at any given time will result in the above error.
The solution is not simple, you need to rethink how to update the list so it won't change while the tableView reload its data.
One thing you could try is to do something like:
func getData() {
// You cannot clear or change self.list here
guard !chatrooms.all.isEmpty else { return }
DispatchQueue.global().async() {
let updatedData = process(newData: self.chatrooms.all)
DispatchQueue.main.sync() {
self.list = updatedData
self.tableView.reloadData()
}
}
}
private func process(newData data: [Chatroom]) -> [Chatroom] {
// Do all your logic without making any changes to self.list
}
The key is to never make any change to the data that is used when reloading the tableView except synchronously on the main thread juste before reloading.
I've got a PFTableViewController that displays a table of items. I then segue to a navigation controller with root view controller that allows the user to enter a new item - and after they press save, I dismiss the view controller and return to the PFTableViewController.
I would like to reload the PFTableViewController at this point - so that its list includes the item that the user just added. Currently my attempts to get it to reload are not working; the user has to pull to refresh which is broken - it feels like the added item was not properly saved because it doesn't automatically show up.
I've put the self.tableView.reloadData() in the viewDidLoad() function - and it clearly fires as I added a println to verify that it triggers when the new item view is dismissed. But the table is not reloading the data.
Here's 1 version of the code that I've tried:
override func viewDidAppear(animated: Bool) {
println("going to reload")
self.tableView.reloadData()
}
And another, based on another example I saw:
override func viewDidAppear(animated: Bool) {
println("going to reload")
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.tableView.reloadData()
})
}
Neither are working.
Here's the majority of the PFTableViewController file set-up in case it's useful as well:
import UIKit
import Parse
import ParseUI
class CategoryViewController: PFQueryTableViewController {
var currentObject : PFObject?
var candidates: Array<AnyObject>?
override init(style: UITableViewStyle, className: String!) {
super.init(style: style, className: className)
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.parseClassName = "CategoryCandidates"
self.textKey = "candidateTitle"
self.pullToRefreshEnabled = true
self.paginationEnabled = false
}
override func queryForTable() -> PFQuery {
var query = PFQuery(className: "CategoryCandidates")
query.whereKey("categoryID", equalTo: currentObject!)
query.orderByDescending("votes")
return query
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath, object: PFObject?) -> CandidateTableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("Cell") as! CandidateTableViewCell!
if cell == nil {
cell = CandidateTableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "Cell")
}
cell?.voteButton.addTarget(self, action: "buttonAction:", forControlEvents: UIControlEvents.TouchUpInside)
// Extract values from the PFObject to display in the table cell
if let candidateTitle = object?["candidateTitle"] as? String {
cell?.textLabel?.text = candidateTitle
}
if let votesTotal = object?["votes"] as? Int {
if votesTotal > 1 {
cell?.votesLabel.text = "\(votesTotal.description) votes"
} else if votesTotal == 1 {
cell?.votesLabel.text = "\(votesTotal.description) vote"
}
}
cell.voteButton.tag = indexPath.row
return cell
}
Thanks in advance!
The PFQueryTable vc must do two things to refresh the data: (1) rerun the query, and (2) after the query has run, reload the table view. The method loadObjects runs the query, and the query's completion handler reloads the table view.
You can use a delegate to notify the PFTableViewController
protocol CategoryViewControllerDelegate {
func reloadTable()
}
class CategoryViewController: PFQueryTableViewController, CategoryViewControllerDelegate {
// MARK: CategoryViewControllerDelegate
func reloadTable() {
self.tableView.reloadTable()
}
// method to segue into adding of new item
fund addNewItem() {
let view = NewItemViewController()
view.delegate = self
presentViewController(view, animated: true, completion: nil)
}
}
class NewItemViewController: UIViewController {
var delegate:CategoryViewControllerDelegate?
func handleAddNewItem() {
// handle the adding of new item here
// ...
// then call the delegate to reload the table
delegate.reloadTable()
// dismiss and return to parent view
// ...
}
}
I am trying to use UISearchController however I confronted with retain issue that I can't solve. MainTableview has two sections.
Section 1
Filtered Data based on some Regex
Section 2
All Data
I added UISearchController to my tableview and attached ResultsTableController as resultsTableController. It works when user search something, ResultsTableController comes forward and because I set tableview delegate to self, selecting item from ResultsTableController calls didSelectRowAtIndexPath in my MainTableViewController. However I have allocation issue if user selects something from resultsTableController.
Following happens for different scenarios
User doesn't search anything, just selects an item from
MainTableview, I see deinit messages
User searches something, cancel the search, select item from
MainTableview, I see deinit messages
User searches something, and selects an item from
ResultsTableController, I don't get deinit in my viewcontrollers
MainTableViewController.swift
var searchController: UISearchController!
// Secondary search results table view.
var resultsTableController: ResultsTableController!
var allCompanies = ["Data1","Data2","Data3"]
override func viewDidLoad() {
super.viewDidLoad()
resultsTableController = ResultsTableController()
// We want to be the delegate for our filtered table so didSelectRowAtIndexPath(_:) is called for both tables.
resultsTableController.tableView.delegate = self
searchController = UISearchController(searchResultsController: resultsTableController)
searchController.searchResultsUpdater = self
searchController.searchBar.sizeToFit()
tableView.tableHeaderView = searchController.searchBar
searchController.delegate = self
searchController.dimsBackgroundDuringPresentation = false
searchController.searchBar.delegate = self
definesPresentationContext = true
}
}
// MARK: UISearchBarDelegate
func searchBarSearchButtonClicked(searchBar: UISearchBar) {
searchBar.resignFirstResponder()
}
// MARK: UISearchResultsUpdating
func updateSearchResultsForSearchController(searchController: UISearchController) {
// Update the filtered array based on the search text.
let filteredResults = allCompanies.filter({ company in
(company.lowercaseString as NSString).containsString(searchController.searchBar.text.lowercaseString)
})
// Hand over the filtered results to our search results table.
let resultsController = searchController.searchResultsController as! ResultsTableController
resultsController.searchResult = filteredResults
resultsController.tableView.reloadData()
}
// usual tableview methods
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if resultsTableController.searchResult.count > 0 {
selectedCompany = resultsTableController.searchResult[index]
//do something with selected company
navigationController?.popViewControllerAnimated(true)
return
}
//
selectedCompany = allCompanies[index]
navigationController?.popViewControllerAnimated(true)
}
deinit {
println("MainTableView deinit")
}
ResultTableController.swift
class ResultsTableController:UITableViewController {
var searchResult = [String]()
override func viewDidLoad() {
super.viewDidLoad()
tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "cell")
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return searchResult.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! UITableViewCell
let index = indexPath.row
cell.textLabel?.font = UIFont(name: "Avenir-Roman", size: 16)
cell.textLabel?.text = searchResult[index].description
return cell
}
deinit {
println("ResultTableController deinit")
}
}
Hey there I ran into the issue today
apparently I need to force the dismiss of the searchController to work around the retain issue
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
searchController?.dismissViewControllerAnimated(false, completion: nil)
}
here is my sample project
https://www.dropbox.com/s/zzs0m4n9maxd2u5/TestSearch.zip?dl=0
The solution does seem to be to call dismissViewControllerAnimated on the UISearchController at some point. Most people probably don't do that since the UISearchController is somewhat of an implementation detail related to your view controller that is hosting the UISearchController.
My solution, which seems to work no matter how you present your search UI (standard present or in a popover) is to call searchController.dismissViewControllerAnimated() from your host's viewDidDisappear, after checking to see if the view controller is no longer being presented. This catches all cases, especially the popover case where the user taps outside the popover to automatically dismiss the UI, or the case where the search UI is disappearing simply because you pushed something else onto the navigation stack. In the latter case, you don't want to dismiss the UISearchController.
override func viewDidDisappear(animated: Bool)
{
super.viewDidDisappear(animated)
if presentingViewController == nil
{
searchController.dismissViewControllerAnimated(false, completion: nil)
}
}
I'm new to Swift, so be gentle.
I'm using the Xcode 7 Beta, currently.
I have a TableViewController nested in a NavigationController. Within the TableView I've implemented a UISearchBar and UISearchDisplayController similar to the steps here. Pretty much everything is working as expected, except when I type my search criteria, the results table view for the UISearchDisplayController is not getting populated. When I hit Cancel on the search, the results have been populated in the initial table view.
The tutorial I linked to is pre-populating a list of items and the search bar is filtering these items. My approach is different because I'm populating the search from an external API.
My question is two-fold:
a) how do I properly populate the results TableView?
b) the two TableViews seem redundant and I don't feel the first is necessary (might be wrong). What's the proper way to achieve this functionality? Ideally, I would like to be able to put a UISearchBar in the navigation item so it lives in the UINavigationBar, but I have had trouble finding resources for doing that.
import UIKit
import Alamofire
import SwiftyJSON
import Foundation
class DSTableViewController: UITableViewController, UISearchBarDelegate, UISearchDisplayDelegate {
var songs: [Song] = []
var timer: NSTimer = NSTimer()
func getSongs(timer: NSTimer!) {
let searchTerm = timer.userInfo as! String
self.title = "Results for \(searchTerm)"
// logic to switch service; need to eventually move this to an interface
// or move this code inside a rest API, and then into an interface
// URL encode the search term
let searchTermEncoded = searchTerm.stringByAddingPercentEncodingWithAllowedCharacters(.URLHostAllowedCharacterSet())
// create the url for the web request
let uri: String = "https://api.spotify.com/v1/search?q=\(searchTermEncoded!)&type=artist,album,track"
// call to Spotify API
Alamofire
.request(.GET, uri)
.response { request, response, data, error in
let json = JSON(data: data!)
print(json["tracks"]["items"].count);
print(json["tracks"]["items"])
for var i = 0; i < json["tracks"]["items"].count; i++ {
let data = json["tracks"]["items"][i]
// return the object list
let song = Song()
song.title = data["name"].string!
song.album = data["album"]["name"].string!
song.artist = data["artists"][0]["name"].string!
self.songs += [song]
}
dispatch_async(dispatch_get_main_queue()) {
self.tableView!.reloadData()
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem()
}
func searchDisplayController(controller: UISearchDisplayController, shouldReloadTableForSearchString searchString: String?) -> Bool {
timer.invalidate()
timer = NSTimer.scheduledTimerWithTimeInterval(0.5, target: self, selector: Selector("getSongs:"), userInfo: searchString, repeats: false)
return true
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(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 songs.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCellWithIdentifier("SongCell", forIndexPath: indexPath)
let song = songs[indexPath.row]
cell.textLabel?.text = song.title
cell.detailTextLabel?.text = song.artist + " - " + song.album
cell.imageView?.image = song.albumImage
return cell
}
}
When I call
func searchDisplayController(controller: UISearchDisplayController, shouldReloadTableForSearchString searchString: String?) -> Bool {
self.getSongs(searchString)
return true
}
I'm able to populate the view correctly, but I'd like to have the delay so I'm not making an API call every time the text changes.
Hopefully I explained that correctly. Please feel free to edit the question if I wasn't clear or missed something.
So I ended up getting the functionality I wanted and ended up answering my question of what the proper approach was. Turns out that I was trying to fit a square peg in a round hole.
What I ended up doing is removing the UISearchDisplayController altogether and instead just creating a UISearchBar and assigning it to my self.navigationItem.titleView. This removed some of the built-in functionality of the search controller, but also gave me a more concise way of doing what I needed to do, without worrying about silly workarounds. My code looks roughly like
class TableViewController: UISearchBarDelegate {
override func viewDidLoad() {
super.viewDidLoad()
let searchBar: UISearchBar = UISearchBar()
searchBar.placeholder = "Search"
searchBar.delegate = self
self.navigationItem.titleView = searchBar
self.definesPresentationContext = true
}
func searchBar(searchBar: UISearchBar, textDidChange searchText: String) {
timer.invalidate()
timer = NSTimer.scheduledTimerWithTimeInterval(0.5, target: self, selector: "getResults:", userInfo: searchText, repeats: false)
}
func searchBarSearchButtonClicked(searchBar: UISearchBar) {
searchBar.resignFirstResponder()
}
}
This approach got me the functionality I was looking for (being able to delay the call to the API until the user was done typing) and I was also able to remove some code that was extraneous. Hopefully this helps someone in the future.
I have a view in my app called JournalViewController that I'm presenting over my PastSessionsViewController. PastSessions has a table view that the user can tap to edit and bring up the journal.
When the user edits an entry and saves it (saving to CoreData), dismissing JournalViewController I'd like for the table view in PastSessions to reflect those changes and show the updated table cell.
I'm calling tableView.reloadData() in PastSessionsViewController viewDidLoad() but that doesn't seem to be working. I've also added a delegate for JournalViewController to interact with PastSessionsViewController ahead of dismissViewController
Here's some code to look at:
In PastSessionsViewController:
class PastSessionsViewController: UIViewController, UITableViewDelegate, JournalVCDelegate {
weak var tableView: UITableView?
weak var backButton: UIButton?
let pastSessionsDataSource: PastSessionsDataSource
init() {
pastSessionsDataSource = PastSessionsDataSource()
super.init(nibName: nil, bundle: nil)
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
let tableView = UITableView()
tableView.backgroundColor = nil
tableView.delegate = self
tableView.dataSource = pastSessionsDataSource
tableView.registerClass(EntryCell.self, forCellReuseIdentifier: "cell")
view.addSubview(tableView)
self.tableView = tableView
}
override func viewDidAppear(animated: Bool) {
tableView?.reloadData()
}
func didFinishJournalVC(controller: JournalViewController) {
var newDataSource = PastSessionsDataSource()
tableView?.dataSource = newDataSource
// tried this ^, but it's causing the app to crash
// tableView?.reloadData() <- this isn't doing the trick either
dismissViewControllerAnimated(true, completion: nil)
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let editJournalVC = JournalViewController(label: "Edit your thoughts")
editJournalVC.delegate = self
presentViewController(editJournalVC, animated: true, completion: nil)
}
}
In JournalViewController:
protocol JournalVCDelegate {
func didFinishJournalVC(controller: JournalViewController)
}
class JournalViewController: UIViewController, UITextViewDelegate {
var delegate: JournalVCDelegate! = nil
func doneJournalEntry(sender: UIButton) {
journalEntryTextArea?.resignFirstResponder()
... do some core data saving ...
delegate.didFinishJournalVC(self)
}
}
In PastSessionsDataSource:
import UIKit
import CoreData
class PastSessionsDataSource: NSObject {
var arrayOfEntries = [Entry]()
var coreDataReturn: [Meditation]?
func prepareEntries() {
// gets stuff from coredata and formats it appropriately
}
override init() {
super.init()
prepareEntries()
}
}
extension PastSessionsDataSource: UITableViewDataSource {
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return arrayOfEntries.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! EntryCell
... set up the labels in the cell ...
return cell
}
}
Thanks for looking!
viewDidLoad is called when the view controller load its view at the first time, so basically it will only be called once during the view controller's whole life cycle.
One quick solution is to put tableView.reloadData() in PastSessionsViewController viewWillAppear() or viewDidAppear().
However I do not like this quick solution as every time you dismiss JournalViewController, the table view will be reloaded, even the user has not changed anything on JournalViewController (for example, cancel the edit). So I suggest to use delegate approach between PastSessionsViewController and JournalViewController, when the user actually edit the data on JournalViewController then inform PastSessionsViewController to refresh the table.
You are currently prepare entries only on init of PastSessionsDataSource, but not after you did CoreData changes. So each time when you reloadData for tableView you work with the same data set loaded initially. As a quick hack you can try to updated viewDidAppear in a following way:
override func viewDidAppear(animated: Bool) {
if let tableView = tableView {
let dataSource = tableView.dataSource! as PastSessionsDataSource
dataSource.prepareEntries()
tableView.reloadData()
}
}
Your tableView property is probably nil in viewDidAppear, based on your listed code. The reason is that in viewDidLoad you construct a UITableView as tableView, and that is a local variable. You need to assign that variable to the property:
self.tableView = tableView