Search code examples
swiftfunctioninstancereloaddatadismiss

UITableView returning nil when reloadData method is called upon it


Project Information:

I have three views: 'Main, AddTask, and TaskTable' and three view controllers: 'MainVC, AddTaskVC, TaskTableVC'. My 'Main' view has an embedded 'UITableView' which is 'TaskTable'. My 'Main' view also has a button that presents a modal view which is 'AddTask'. My 'AddTask' view has a button that dismisses itself. My 'TaskTableVC' has an object named 'taskTable' which is the 'UITableView'

Goal:

I have been trying to call reloadData on taskTable when the modal view AddTask is dismissed. However, no matter how I tackle this I cannot call reloadData on the taskTable because it always seems to be nil after AddTask appears for the first time.

Problem Research:

The first thing I did was directly call reloadData() on my taskTable object using completion when the AddTask was dismissed :

self.dismiss(animated: true, completion: {           
    TaskTableVC().taskTable.refreshData()
})

but I quickly learned that I cannot call it directly because the TaskTable view was not loaded and I was creating an instance.

Next, I put print statements on my viewDidLoad for my three views (see Project Information). On startup, the first thing that loads is my TaskTable and then my Main. When I click a button on my Main my AddTask loads. So far so good... However, when I dismiss my AddTask, nothing loads. I thought my TaskTable and Main would get loaded but that was not the case. So I tried calling a function refreshData() that was part of MainVC on completion of dismiss in AddTaskVC The function would load the TaskTable view using loadViewIfNeeded:

func refreshData() {
     TaskTableVC().loadViewIfNeeded()
}

Then in my TaskTableVC under my viewDidLoad I have:

override func viewDidLoad() {
    super.viewDidLoad()
    print("table view loaded")
    self.taskTable.reloadData()
}

Yet the error I continue to get is: Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value I believe this is because I called TaskTableVC.loadViewIfNeeded() via an instance? Whatever the case may be, I cannot get this to work for the life of me.

TL;DR I need to call reloadData on an embedded UITableView when I call dismiss on a modal view.

//MainVC:
class MainVC: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        print("main view loaded")
    }

    func refreshData() {
        TaskTableVC().loadViewIfNeeded()
    }
}
//AddTaskVC
class AddTaskVC: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        print("AddTask view loaded")
    }
    //...
    @IBAction func addTask(_ sender: Any) {
        //...
        self.dismiss(animated: true, completion: {     
            MainVC().refreshData()
        })
    }
}
//TaskTableVC
class TaskTableVC : UITableViewController {

    @IBOutlet var taskTable: UITableView!

    override func viewDidLoad() {
        super.viewDidLoad()
        print("table view loaded")
        self.taskTable.reloadData() //error here
    }
    //...
}

Solution

  • There are several options, for example you could use Delegate pattern to communicate between those ViewControllers, there are also some other options like Observer pattern or RxSwift, you could also keep some reference directly to those embeded ViewControllers, but I don't recommend to do that.

    Since there are segue relationships between those embeded ViewControllers we can use prepare(for segue methot to assing those delegates.

    class MainVC: UIViewController, TasksUpdateDelegate {
    
        weak var tasksTableDelegate: TaskTableDelegate?
    
        override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
            if let taskTableVC = segue.destination as? TaskTableVC {
                tasksTableDelegate = taskTableVC
            } else if let addTaskVC = segue.destination as? AddTaskVC {
                addTaskVC.delegate = self
            }
        }
    
        func tasksChanged() {
            tasksTableDelegate?.reloadTasks()
        }
    
    }
    

    TasksUpdateDelegate informs MainVC about any changes in tasks.

    protocol TasksUpdateDelegate: class {
        func tasksChanged()
    }
    
    class AddTaskVC: UIViewController {
    
        weak var delegate: TasksUpdateDelegate?
    
        @IBAction func addTask(_ sender: Any) {
            dismiss(animated: true) { [weak self] in
                self?.delegate?.tasksChanged()
            }
        }
    }
    

    TasksTableDelegate is used in MainVC to inform TaskTableVC that tasksTable should be reloaded.

    protocol TaskTableDelegate: class {
        func reloadTasks()
    }
    
    class TaskTableVC: UITableViewController, TaskTableDelegate {
    
        @IBOutlet var taskTable: UITableView!
    
        func reloadTasks() {
            taskTable.reloadData()
        }
    
    }