Add perform segue upon cell selection

Hey guys, I am struggling to find where to implement the didSelect delegate function from HitsTableViewController. Following the documentation, I have been able to implement a functioning UISearchController connected to a HitsTableViewController and using a TableViewCellConfigurable struct to configure the UITableViewCell (shown below in screenshot).

Upon selection, I would like to perform a method where I can implement a segue and transfer data to the new view controller.

I am running Xcode 12.3 and Algolia InstantSearch 7.1.

Any help is greatly appreciated, thanks in advance.

Hi @davidchen02,

The direct usage of HitsTableViewController is an easy way to make a prototype and to understand how it works.
In order to customize it, you have to subclass HitsTableViewController and to override its tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) method in which you can add your segue transition logic.

what if you are trying to instantiate a view controller, I am trying to instantiate a view controller but it doesn’t work when I select a row.

class InstantiatedHitsTableViewController: HitsTableViewController<NameOfEventTableViewCellConfigurator> {

override func viewDidLoad() {
    super.viewDidLoad()
    

}

override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    tableView.deselectRow(at: indexPath, animated: true)
    
    if let displayVC = storyboard?.instantiateViewController(withIdentifier: Constants.Storyboards.TeachStoryboardID) as? TeacherDetailsViewController {
     
        navigationController?.pushViewController(displayVC, animated: true)
    }
    
    }

}

This is my complete class I have subclassed and I took your advice in the reply you gave to override the didSelectRowAt method. The issue is the row won’t instantiate the view controller but it will deselect the row when it is selected. Do you know why that happens?

I had some trouble with that as well, and it seems that you can not instantiate a view controller like you are doing. I used this code in the didSelectRowAt:

self.data = hitsSource?.hit(atIndex: indexPath.row)    //this is whatever data you are retrieving or want to pass

let vc = self.presentingViewController as! PresentingViewController
vc.data = self.data.   //here is where you can transfer data to your presenting view controller

self.presentingViewController.performSegue(withIdentifier: "pushSegue", sender: self).   //this is where you add your segue logic

Then in your PresentingViewController class, add:

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if segue.identifier == "pushSegue" {
        let vc = segue.destination as! DestinationViewController
        vc.data = self.data
    }
}

with your segue name and transfer data this way from your PresentingViewController to your DestinationViewController.

Hope this helps!

my variables are a bit different then yours so for the prepare function what would I put?

Here’s my code in the subclassed tableVC:

override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    tableView.deselectRow(at: indexPath, animated: true)
    
    let data = hitsSource?.hit(atIndex: indexPath.row)
    
    let fixedString = "\(data)"
    let sub = fixedString.dropFirst(40).dropLast(3)
    let realString = String(sub)
  
    print(realString)
    
    let vc = presentingViewController as! SchoolDetailsViewController

    vc.selectedEventName = realString

    presentingViewController?.performSegue(withIdentifier: "schoolFromSearch" , sender: self)
 
}

I couldn’t do self.data like you did I would get an error, data for you in your code is selectedEventName for me in my code. Now when I get to the presenting view controller and try to put realString in place for vc.selectedEventName , it doesn’t recognize the variable, how would i get around that, cause Xcode won’t pick up the hits.hitSource .... outside of the didSelectRowAt method so i can’t call the function globally to make it public.

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    
    if segue.identifier == "schoolFromSearch" {
        let vc = segue.destination as! SchoolDetailsViewController
        
        vc.selectedEventName = 
    }
}

Not sure what to put in the body of the if statement.

Also, thank you for replying, nobody ever seems to respond on these forums.

Hey, no worries about the reply. I was having a lot of trouble implementing Algolia using their documentation so I know what you are going through :sweat_smile:. So I’m glad to be able to help someone out here.

So you are mixing up the destination view controller and the presenting view controller which are different things. So the presenting view controller is the controller which you set up the searcher, search controller, search connector etc. The destination view controller is the controller you want to end up on (i.e. a display page showing the details of a school). The prepareForSegue function should be performed in the presenting view controller and in the if statement, the segue.destination should be the destination view controller not the presenting view controller.

And then you would end up with this in the if statement, you would put:

let vc = segue.destination as! DestinationViewController // being what ever view controller you want to end up on
vc.selectedEventName = self.selectedEventName

You can also try parsing the data that is being retrieved. So what I did was to make a struct to encode/decode the data that was being retrieved from Algolia. Look at this for more help: Encodable and Decodable in Swift 4 | by Manoj Karki | Medium. This is what Algolia’s documentation refers to as a CustomHitModel.

Then, if for example your struct was called CustomStruct, your hitSourse.hit(atIndex: indexPath.row) will be of type CustomStruct. So then you can do something like this in your delegate functions:

let schoolData = hitsSource?.hit(atIndex: indexPath.row)
let teacherName = schoolData.teacherName // so you can access different properties of CustomStruct

I hope this makes sense!

Hey David, yeah looking at it now, at least for what I want to do, the code probably isn’t best suited for my situation. I really wish Algolia had the ability to be able to make the hits instantiate a VC, I already have a detail VC that perfectly lays out the details when a cell is clicked in my normal tableVC, and I want to instantiate that same detail VC when a hit cell is clicked. I want to somehow match up the hit cell name with the title name in the detail VC so that it shows the right details, but I’m not even sure if that’s possible in Algolia. Kinda depressing, but I’ll figure it out later on down the line in the development of the app. Thanks for your help and clarification either way, much appreciated!

Yeah it is quite a shame. At first, I was looking to instantiate a pop-up view controller but then had to resort to this solution. I had found the documentation to be really vague. But anyways, best of luck and no worries!

Hi @dantesauder ,

The concept of InstantSearch is to provide you a hit information. Then it’s up to you to decide how to use it.

Your HitsTableViewController contains hitsSource variable providing the access to a hit for indexpath:

let hit = hitsSource?.hit(atIndex: indexPath.row)

In your view controller instantiation code there are a lot of optional signs: storyboard?, as? TeacherDetailsViewController, navigationController?.. On each step this chain may silently fail if some of these values is nil. Check them carefully in order to define what went wrong.

Once you make it work, you can use a field hit fetched on the previous step to customize your viewController.

It may look as follows:

    if let displayVC = storyboard?.instantiateViewController(withIdentifier: Constants.Storyboards.TeachStoryboardID) as? TeacherDetailsViewController {
        displayVC.title = hitsSource?.hit(atIndex: indexPath.row)?.name
        navigationController?.pushViewController(displayVC, animated: true)
    }

I hope this will assist you.

Hi all,

I have the same issue to customize the HitsTableViewController but with no success. The cell of the tableView doesn’t push the next viewController.
Here is my code:

class Customize HitsTableViewController: HitsTableViewController {
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if let displayVC = storyboard?.instantiateViewController(withIdentifier: “WineDetailsViewController”) as? WineDetailsViewController {
displayVC.title = hitsSource?.hit(atIndex: indexPath.row)?.designation
navigationController?.pushViewController(displayVC, animated: true)
}
}
}

Many thanks in advance

Hi @vladislav.fitc ,

I follow your post to customize the HitsTableViewController but the segue transition didn’t work when I select a row:

class InstantiatedHitsTableViewController: HitsTableViewController {
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: “detail”, sender: self)
}
}

Do you why that happen?

Could you firstly check if the didSelectRowAt method is called when you click a row?

The method isn’t called when I click a row. I added a print to test but nothing appear in the console.

Hi David, I know i sort of closed off this topic but the idea of not having a search functionality in my app haunts me, so I decided to go forward with your advice since instantiating is impossible.

So in my didSelectRow() delegate method, I have the following code,

  override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    tableView.deselectRow(at: indexPath, animated: true)
    

    

    let costData = hitsSource!.hit(atIndex: indexPath.row)!.eventCost
    let dateData = hitsSource!.hit(atIndex: indexPath.row)!.eventDate
    let nameData = hitsSource!.hit(atIndex: indexPath.row)!.eventName
    let gradesData = hitsSource!.hit(atIndex: indexPath.row)!.forEventGrades
    
    
    
    

    let vc = self.presentingViewController as! TeacherTableViewController
    
    vc.nameTheEvent = nameData
    vc.dateTheEvent = dateData
    vc.costTheEvent = costData
    vc.gradeTheEvent = gradesData
    

    self.presentingViewController?.performSegue(withIdentifier: Constants.Segues.fromSearchToSchoolEventDetails, sender: self)


}

So i customized my struct and my index to have more data of the event as you said I should. I put the proper presentingVC, the VC that you set up the search controller in, and created a variable for that and transferred the variables as well. I created those four variables in the presentingVC with String data types so I can use the variables created in my delegate method.

Now the issue is that for the segue, I created as segue from TeacherTableVC (the presentingVC) and connected it to my DestinationVC, it’s registered and everything but when I click on the hit cell, it crashes and says, the presentingVC has no segue identifier with the segue identifier name I gave it which I get confused about.

I will also display the code in my presentingVC specifically the prepare function.

 override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if segue.identifier == Constants.Segues.fromSearchToSchoolEventDetails {
        let vc = segue.destination as! SchoolDetailsViewController
        vc.title = nameTheEvent
        vc.dateEditableTextF.text = dateTheEvent
        vc.costEditableTextF.text = costTheEvent
        vc.gradesEditableTextF.text = gradeTheEvent
        
        
    }
}

So after having all this I thought the code would work successfully but it crashed instead, if there’s anything you can point out that’s wrong in my code, that would be great. Thanks.

Disregard the last question, i had a created a tableVC that I accidentally forgot about, and connected the segue from there. So i tested it out with another segue and it works, but when I try it with the segue I want to perform, it crashes because it unwrapped a nil. I wanna know how I can get around this.

     let vc = presentingViewController as! TeacherTableViewController
    
 

    vc.nameTheEvent = nameData
    vc.dateTheEvent = dateData
    vc.costTheEvent = costData
    vc.gradeTheEvent = gradesData


    vc.performSegue(withIdentifier: Constants.Segues.fromSearchToSchoolEventDetails, sender: self)

This code is apart of the issue but not the actual issue itself.

The issue comes when I call the prepare in the presentingVC.

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if segue.identifier == Constants.Segues.fromSearchToSchoolEventDetails {
        let vc = segue.destination as! SchoolDetailsViewController
        vc.title = nameTheEvent
        vc.dateEditableTextF.text = dateTheEvent
        vc.costEditableTextF.text = costTheEvent
        vc.gradesEditableTextF.text = gradeTheEvent

    }
}

I had to initalize the variables in the presentingVC as empty strings and I can see why I get the error because I think the segue process disregards the variables in the didSelectRowAt() and just takes the value of the initialized variables, which is empty, and uses that in the prepare function. So my question is how can I transfer the proper data since, the data is in a local scope and can’t be called outside of the delegate method. If what I asked doesn’t make sense, feel free to ask more questions. Look forward to hearing from you soon.

Could you try to wrap the vc.performSegue in a Dispatch queue like this:

DispatchQueue.main.async {
    vc.performSegue(withIdentifier: Constants.Segues.fromSearchToSchoolEventDetails, sender: self)
}

It seems like there is something wrong with your ViewController presentation/instantiation logic. HitsTableViewController doesn’t override not the interaction logic (didSelectRow method), so the issue is unlikely related to InstantSearch .

Try to replace it with a basic UITableViewController subclass with your own implementation of UITableViewDataSource & UITableViewDelegate protocols and static data to detect the issue and then bring back the InstantSearch subclass.

1 Like