1

I have an app with a chain of TableViewControllers, linked by a notification controller, retrieving information from a webservice. Data from the next table is pulled from an webservice, regarding the data chosen on the previous one. When I pull any table to refresh, it requests again the information a protocol sends a delegate message to the tableview to refresh the information.

Well, so far so good. But, I'm experiencing something strange... Example: I'm on tableView #2 and then use the navigation back button to get to the previous table, the #1. When, on #1, I pull the tableview to refresh, it crashes and gives me one of those "unrecognized selector sent to instance" telling that Tableview #2 doesn't have the optional delegate method, used to get the information to fill TableView #1. And I'm not even on TableView #2 anymore, iOS is sending the delegate message to the wrong ViewController, as the tableViewController wasn't even dismissed when the NavigationController backButton was clicked...

Has anyone experienced it? I know it seems a little bit confuse, but it is what's happening.

Lets get some code in here: Two TableViewControllers: ClientsViewController and ProjectsViewController When you select a client on the ClientsViewController Table, it loads all projects from the client on the ProjectsViewController Table.

ClientsViewController

override func viewDidLoad() {
        super.viewDidLoad()
        wsCommHandler.delegate = self
        self.tableView.delegate = self
        self.tableView.dataSource = self

        self.refreshControl!.addTarget(self, action: Selector("refreshClients:"), forControlEvents: UIControlEvents.ValueChanged)
        wsCommHandler.getClients()

    }

    func refreshClients(sender:AnyObject){
        println("Refreshing")
        self.refreshControl?.beginRefreshing()
        wsCommHandler.getClients()
        self.refreshControl?.endRefreshing()
    }

func didReceiceResponseFromGetClientsRequest(response: [AnyObject]) {
        self.clients = response
        self.tableView.reloadData()

    }

The wsCommHandler 'getClients()' retrieves from a webservice all the clients and uses a delegate 'didReceiceResponseFromGetClientsRequest' to reload the table.

When a client is selected it segues to the next TableViewController, passing a client_id between the ViewControllers.

Here's a code sample from the ProjectsViewController

override func viewDidLoad() {
        super.viewDidLoad()
        wsCommHandler.delegate = self
        self.tableView.delegate = self
        self.tableView.dataSource = self

        self.refreshControl!.addTarget(self, action: Selector("refreshProjects:"), forControlEvents: UIControlEvents.ValueChanged)
        wsCommHandler.getProjectsByClient(client_id) 
    }

    func refreshProjects(sender:AnyObject){
        println("Refreshing")
        self.refreshControl?.beginRefreshing()
        wsCommHandler.getProjectsByClient(client_id)
        self.refreshControl?.endRefreshing()
    }

    func didReceiceResponseFromGetProjectsRequest(response: [AnyObject]) {
        self.projects = response
        self.tableView.reloadData()

    }

It loads the Projects on the screen on the same way as the clients, but now using the delegate 'didReceiceResponseFromGetProjectsRequest'

Now, if I press the navigation controller back button, it brings the ClientsViewController back to front (without reloading it)... When I pull it to refresh. The class is called, retrieves the data and then: Crash!

2015-03-06 09:26:18.978 PostItHours[9174:792018] -[PostItHours.ProjectsViewController didReceiceResponseFromGetClientsRequest:]: unrecognized selector sent to instance 0x7fe0c4132c30 2015-03-06 09:26:18.980 PostItHours[9174:792018] Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[PostItHours.ProjectsViewController didReceiceResponseFromGetClientsRequest:]: unrecognized selector sent to instance 0x7fe0c4132c30'

As you guys can see, it is trying to do it on the ProjectsViewController, which was already dismissed, by the backButton.

Any ideas?

Lucas Almeida
  • 245
  • 2
  • 11
  • Do you use a UIRefreshControl? Can you show us some code? Maybe you could create a new UIRefreshControl each time you change your tableView, and change the target of the refreshcontrol accordingly : `refreshControl!.addTarget(self,...` – Paul Mar 06 '15 at 12:23
  • Paul, I updated my question with some code :) – Lucas Almeida Mar 06 '15 at 12:43
  • Try to move the `refreshControl` code to the `viewDidAppear` method, which is called when the view is refreshed, whereas `viewDidLoad` is called once and is not called when you come back from the navigation stack, more explanations here : http://stackoverflow.com/questions/5562938/looking-to-understand-the-ios-uiviewcontroller-lifecycle – Paul Mar 06 '15 at 13:50
  • Same issue.. I had already tried it before. What I'm not understanding is: I already got back to the first TableViewController and refreshed its table, but the second tableviewcontroller is the one receiving the data from the delegate... Very very strange – Lucas Almeida Mar 06 '15 at 13:53

1 Answers1

1

I tried and it works for me. 2 viewcontrollers, each with a different data source, a "toSecondVC" segue between the two.

The ViewController :

import UIKit

class ViewController: UITableViewController {
    var texts = [String]()

    override func viewDidLoad() {
        super.viewDidLoad()
        texts.append("viewController")
        self.title = "viewController"
    }

    override func viewDidAppear(animated: Bool) {
        super.viewDidAppear(animated)

        refreshControl = UIRefreshControl()
        refreshControl!.addTarget(self,
            action: "handleRefresh:",
            forControlEvents: .ValueChanged)

        tableView.addSubview(refreshControl!)

    }

    func handleRefresh(paramSender: AnyObject){
        //pause it 1 sec
        let popTime = dispatch_time(DISPATCH_TIME_NOW, Int64(NSEC_PER_SEC))
        dispatch_after(popTime,
            dispatch_get_main_queue(), {
                self.texts.append(String("viewController-\(self.texts.count+1)"))
                self.refreshControl!.endRefreshing()
                let indexPathOfNewRow = NSIndexPath(forRow: self.texts.count - 1,
                    inSection: 0)

                self.tableView!.insertRowsAtIndexPaths([indexPathOfNewRow],
                    withRowAnimation: .Automatic)
        })
    }

    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
        if segue.identifier == "toSecondVC" {
            let viewController:SecondVC = segue.destinationViewController as SecondVC
            let indexPath = self.tableView.indexPathForSelectedRow()
            viewController.stringSelected = texts[indexPath!.row]
        }
    }

    //MARK : TableView Delegate
    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell
        cell.textLabel.text = texts[indexPath.row]
        return cell
    }

    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return texts.count
    }
}

The secondVC :

import UIKit

class SecondVC: UITableViewController {
    var texts = [String]()
    var stringSelected = String()

    override func viewDidLoad() {
        super.viewDidLoad()
        self.title = "secondVC"
        texts.append("secondVC")
    }

    override func viewDidAppear(animated: Bool) {
        super.viewDidAppear(animated)
        refreshControl = UIRefreshControl()
        refreshControl!.addTarget(self,
            action: "handleRefresh:",
            forControlEvents: .ValueChanged)

        tableView.addSubview(refreshControl!)

        println("stringSelected : \(stringSelected)")
    }

    func handleRefresh(paramSender: AnyObject){

        //pause it 1 sec
        let popTime = dispatch_time(DISPATCH_TIME_NOW, Int64(NSEC_PER_SEC))
        dispatch_after(popTime,
            dispatch_get_main_queue(), {
                self.texts.append(String("secondVC-\(self.texts.count+1)"))
                self.refreshControl!.endRefreshing()
                let indexPathOfNewRow = NSIndexPath(forRow: self.texts.count - 1,
                    inSection: 0)

                self.tableView!.insertRowsAtIndexPaths([indexPathOfNewRow],
                    withRowAnimation: .Automatic)

        })

    }

    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell
        cell.textLabel.text = texts[indexPath.row]
        return cell
    }

    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return texts.count
    }


}
Paul
  • 6,108
  • 14
  • 72
  • 128
  • You're only adding a delay to refresh the table? My problem persists if even I get back to the first view, wait for a minute and then try to refresh it – Lucas Almeida Mar 06 '15 at 18:42
  • @LucasAlmeida the delay is to simulate the refresh only, it is not related to your issue. I posted the code so that you can better see where you might have made a mistake. – Paul Mar 06 '15 at 20:14
  • Between the tables youre using a common push segue? Embedded a navigation control? – Lucas Almeida Mar 06 '15 at 23:49
  • 1
    And suddenly, I found what I was doing wrong (although I never experienced it before). The class, responsible to fetch data from the webservice, was a shared Instance... I resolved to create a new instance for each Controller and boom, started working... :) Guess it was driving the protocol nuts :) Well, thank you for all support, Paul – Lucas Almeida Mar 07 '15 at 04:25