My app will allow users to create a registration form and it will be displayed using a UITableView. I'm having trouble updating the UITableView. Just to explain a bit what is going on, I have 2 scenes. The 1st scenes displays the created questions and the 2nd scene allows the user to create a question. The 1st scene segues to the 2nd, and then the 2nd scene unwind segues to the 1st seen with the question data. Here's my code.
1st scene:
#IBOutlet var tableViewObject: UITableView!
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
// code for creating tableView
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.questionsArray.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
cell.textLabel?.text = self.questionsArray[indexPath.row].Label
return cell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
}
#IBAction func segueToView(sender: AnyObject) {
}
#IBAction func cancelToSecondViewController(segue:UIStoryboardSegue) {
}
#IBAction func saveQuestion(segue:UIStoryboardSegue) {
if let CreateQuestion = segue.sourceViewController as? createQuestion {
if (CreateQuestion.flag == 0) {
let textinput = CreateQuestion.newInputQuestion
questionsArray.append(textinput)
}
else {
let multichoice = CreateQuestion.newMultiQuestion
questionsArray.append(multichoice)
}
let indexPath = NSIndexPath(forRow: questionsArray.count-1, inSection: 0)
tableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
}
}
2nd scene:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "SaveQuestion" {
if (questionType.selectedSegmentIndex == 0) {
newInputQuestion = textInput(placeHolder: inputQuestionHint.text!, Label: inputQuestionTitle.text!, required: required.selectedSegmentIndex)
flag = 0
}
else {
var arrayOfAnswers = [String]()
for (var i = 0; i < numAnswers.selectedSegmentIndex + 1; i++) {
arrayOfAnswers.append(numAnswersArray[i].text!)
}
newMultiQuestion = multiChoice(answers: arrayOfAnswers, Label: multiQuestionTitle.text!, required: required.selectedSegmentIndex)
flag = 1
}
}
}
I get the following error when I try to create a question:
fatal error: unexpectedly found nil while unwrapping an Optional value
It highlights the following line:
tableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
tableView is nil. Since it is an #IBOutlet that is configured by interface builder, something is wrong with how you configured it in interface builder.
The most likely culprit is that you did not set the class for your view controller in the identity inspector.
Go to interface builder and make sure the utilities pane is open. Open the identity inspector in the utilities pane. Near the top is a place for you to enter the class name of your custom view controller. So if your view controller is called MyViewController, make sure to enter MyViewController into that field.
If that doesn't work, then the #IBOutlet isn't configured properly. Check your connections, and then double check them.
Related
Edit: mentioned at the bottom I was using another stackoverflow question for guidance. Turns out its the same issue that I don't believe ever got solved after the person edited their question. So I copied the exact same issue into my code: How to know which cell was tapped in tableView using Swift
There's probably many things wrong with my code at this point, but the main issue is the very edit at the bottom of the post this author puts on his question. I know that makes this a duplicate then but no one has answered that. All the accepted answers have the same outcome as my issue.
I'm very new to swift and I'm creating a flashcard type app where tapping on a tableview cell for details shows a term and definition. These terms and definitions are stored in two parallel arrays and the index is supposed to be the indexPath.row.
My issue is that int selectedCell which should be the index of the array for whichever cell the user taps always runs the code to display the term and definition before running the code to find the selected cell.
My earlier error before this was that if I made selected cell an optional (var selectedCell: Int?), the program would crash because its nil. To fix that, I made it var selectedCell: Int = 0 and that worked until I realized that no matter what I tap, the first selectedCell will always be 0.
How can I get the selectedCell before the terms and definitions are displayed.
This is the code inside CardViewController, the controller brought up after the user touches a cell for details. There are only two labels (term and definition) so the code is pretty scarce here.
override func viewDidLoad() {
super.viewDidLoad()
// Testing
print("new selectedCell: \(selectedCell)")
// Update labels for term and definition
termLabel.text = "Term: \n" + cards[selectedCell]
definitionLabel.text = "Definition: \n" + details[selectedCell]
}
The code inside CardTableViewController where the very last override func is what gives me the selectedCell. I have checked that the number is correct when tapped, it just runs after cardviewcontroller apparently.
This is the code for CardTableViewController, where it is tableview cells of each term from the flashcard listed.
Not to be confused with CardViewController which is the little detail flashcard screen that pops up
import UIKit
var cards = [String]()
var details = [String]()
var newCard:String = ""
var newDetail:String = ""
var study = [String]()
var selectedCell: Int = 0
class CardTableViewController: UITableViewController {
#IBOutlet var createCardButton: UIBarButtonItem!
override func viewDidLoad() {
super.viewDidLoad()
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return cards.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cardCell", for: indexPath)
cell.textLabel?.text = cards[indexPath.row]
return cell
}
#IBAction func cancel(segue:UIStoryboardSegue) {
}
#IBAction func create(segue:UIStoryboardSegue) {
let addCard = segue.source as! AddCardViewController
newCard = addCard.term
newDetail = addCard.definition
print("term: \(addCard.term)")
print("definition: \(addCard.definition)")
cards.append(newCard)
details.append(newDetail)
study.append(newCard)
tableView.reloadData()
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
selectedCell = indexPath.row
print("selectedCell: ", selectedCell)
}
}
I know it's out of order because of the print statements I put in. Instead, selectedCell should print before new selectedCell. Notice how new selectedCell will be 0 due to initialization (nil if I didn't initialize it) and then is always lagging one cell touch behind what it should be?
the segues for cancel and create are bar button items on the (details of the flashcard screen). They are unwind segues that I followed some online tutorial on for how to create an text field and unwind.
for background on the addCardViewController and unwind segues, this is the code inside that:
class AddCardViewController: UIViewController {
#IBOutlet weak var cardTerm: UITextField!
#IBOutlet weak var cardDefinition: UITextField!
var term:String = ""
var definition:String = ""
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "createSegue" {
term = cardTerm.text!
definition = cardDefinition.text!
}
}
Here's the printing results for the cell indexes
new selectedCell: 0
selectedCell: 0
new selectedCell: 0
selectedCell: 1
new selectedCell: 1
selectedCell: 1
Honestly not sure if there's a way to call that function first or if I'm choosing the selectedCell wrong (I got the idea off another post on stack overflow: How to know which cell was tapped in tableView using Swift)
Storyboard for my app. Shows the list of cards tableview controller and the card view controller:
So in my storyboard, I set up two ViewControllers (CardsTableViewController and CardViewController)
The segue in between these two view controllers is called CardSegue and is set up to present modally.
The reuse identifier for the prototype UITableViewCell in CardsTableViewController is CardCell.
This is how the CardsTableViewController looks like:
import UIKit
struct Card {
let term: String
let definition: String
}
class CardsTableViewController: UITableViewController {
var selectedCell: Int = 0
let cards: [Card] = [Card(term: "Привет", definition: "Hello"), Card(term: "Да", definition: "Yes")]
override func viewDidLoad() {
super.viewDidLoad()
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return cards.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CardCell", for: indexPath)
cell.textLabel?.text = cards[indexPath.row].term
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
selectedCell = indexPath.row
performSegue(withIdentifier: "CardSegue", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let vc = segue.destination as? CardViewController {
let selectedCard = cards[selectedCell]
vc.card = selectedCard
vc.selectedCell = selectedCell
}
}
}
This is the CardViewController:
import UIKit
class CardViewController: UIViewController {
var card: Card = Card(term: "<Set me>", definition: "<Set me>")
var selectedCell: Int = 0
#IBOutlet weak var termLabel: UILabel!
#IBOutlet weak var definitionLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Testing
print("new selectedCell: \(selectedCell)")
termLabel.text = "Term: \n" + card.term
definitionLabel.text = "Definition: \n" + card.definition
}
}
I created a Card struct which has a term and a definition, both being Strings.
I created an array of two Cards with two Russian words. This is the data we're working with.
In didSelectRowAt indexPath, I have set up the same setting of "selectedCell", which is defined at the top.
Directly after, I call performSegue, which will send the user to the CardViewController, which will display the term and the definition.
The prepare(for segue) method is always called whenever performSegue is called. In the view controller, if you start typing..."prepare(for...." Xcode will probably fill it out for you.
In this method, I get the selected card, and I pass the card to the CardViewController. In this example, I pass selectedCell, but I don't know if it's really necessary, it depends on what you are trying to achieve, I guess.
This is how the two view controllers should be communicating.
Here's some good information about how to pass information from one view controller to the next: See this section: Passing Data Between View Controllers Using Segues
i need an help, see this class
import UIKit
protocol TypesTableViewControllerDelegate: class {
func typesController(controller: TypesTableViewController, didSelectTypes types: [String])
}
class TypesTableViewController: UITableViewController {
let possibleTypesDictionary = ["bakery":"Bakery", "bar":"Bar", "cafe":"Cafe", "grocery_or_supermarket":"Supermarket", "restaurant":"Restaurant"]
var selectedTypes: [String]!
weak var delegate: TypesTableViewControllerDelegate!
var sortedKeys: [String] {
return possibleTypesDictionary.keys.sort()
}
// MARK: - Actions
#IBAction func donePressed(sender: AnyObject) {
delegate?.typesController(self, didSelectTypes: selectedTypes)
}
// MARK: - Table view data source
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return possibleTypesDictionary.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("TypeCell", forIndexPath: indexPath)
let key = sortedKeys[indexPath.row]
let type = possibleTypesDictionary[key]!
cell.textLabel?.text = type
cell.imageView?.image = UIImage(named: key)
cell.accessoryType = (selectedTypes!).contains(key) ? .Checkmark : .None
return cell
}
// MARK: - Table view delegate
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
let key = sortedKeys[indexPath.row]
if (selectedTypes!).contains(key) {
selectedTypes = selectedTypes.filter({$0 != key})
} else {
selectedTypes.append(key)
}
tableView.reloadData()
}
}
here the user can tap a cell of the tableView so that his prefer types are used on the next viewController for a search, now i need to build a class that do the same thing but there is no a tableview rather only 6 buttons in a view that the user can tap (so a viewController with only 6 different buttons to tap). The problem is that i don't know how to pass to the next viewController what buttons have been pressed and what are not, how can i build this class?
here is the function in the other class that need to know what buttons have been pressed
func fetchNearbyPlaces(coordinate: CLLocationCoordinate2D) {
mapView.clear()
dataProvider.fetchPlacesNearCoordinate(coordinate, radius:searchRadius, types: searchedTypes) { places in
for place: GooglePlace in places {
let marker = PlaceMarker(place: place)
marker.map = self.mapView
where is "types: serchedTypes"
What you wanna do is called delegation here is how you do it:
Make a protocol like this one:
protocol TransferProtocol : class
{
func transferData(types:[String])
}
Make the view controller with the buttons conform to that protocol, I like to do it by adding extensions to my classes like so:
extension ButtonsViewController:TransferProtocol{
func transferData(types:[String]){
//Do whatever you want here
}
}
Declare a variable in your Table View Controller class with the protocol you created as its type, this is called a delegate
weak var transferDelegate:TransferProtocol?
Before you segue to the Buttons View Controller you want to set that view controller as the delegate you just created like so:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let vc = segue.destination as? ButtonsViewController
transferDelegate = vc
vc?.transferData(types: selected)
}
If done correctly you should be able to work with the array you built in the Table View Controller(TypesTableViewController)
I currently have 2 table view controllers. I've added two disclosure indicators on two static cells for marital status and home state (canton). The user clicks on one of both and is taken to another view controller where he makes the appropriate selection.
The code is currently working for marital status. My question is if here I could reuse the second view controller (i.e. the one with the dynamic cells) for the same purpose but utilising a different array (in this case an array with states' names). For me it is clear that I could simply add a new view controller and implement the states' list there. Here is a screenshot of the storyboard:
First View Controller code:
import UIKit
class FirstTableViewController: UITableViewController, DataEnteredDelegate {
#IBOutlet var maritalStatusCell: UITableViewCell!
#IBOutlet var maritalStatusLabel: UILabel!
func userDidEnterInformation(info: String) {
maritalStatusLabel.text = "Marital Status: (\(info))"
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "maritalStatusSegue" {
let sendingVC: SecondTableViewController = segue.destination as! SecondTableViewController
sendingVC.delegate = self
}
}
}
Second View Controller code:
import UIKit
protocol DataEnteredDelegate {
func userDidEnterInformation(info: String)
}
class SecondTableViewController: UITableViewController {
let maritalStatusArray: [String] = ["Single", "Married"]
let cantonArray: [String] = ["ZG", "ZH", "BE", "LU", "AG"]
var delegate: DataEnteredDelegate? = nil
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return maritalStatusArray.count
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if delegate != nil {
let information: String? = tableView.cellForRow(at: indexPath)?.textLabel?.text
delegate!.userDidEnterInformation(info: information!)
dismiss(animated: true, completion: nil)
self.navigationController?.popViewController(animated: true)
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MaritalStatusCell", for: indexPath)
cell.textLabel?.text = maritalStatusArray[indexPath.row]
return cell
}
}
Does is make sense here to use the second table view controller for the states' list as well ? If yes, how can I implement that ? Thanks.
Yes you can use the Same View controller for displaying the Array of your states' names which I think you have declared in cantonArray, what you need to do is declare a bool variable in Second View Controller (In case if you want to manage only two arrays, if you want to manage more arrays then declare an enum). Then in the segue get from which index that segue is fired, you can get the selected indexPath like this
if let indexPath = tableView.indexPathForSelectedRow{
}
Now check the indexPath.row, if it is 0 then you have selected Marital State so you need to show maritalStatusArray array so make the bool variable true if you get indexpath.row = 1 then make that variable false
Now in Second View Controller add a condition as per the bool variable and show the data from that array like this
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MaritalStatusCell", for: indexPath)
if showMaritalArray {
cell.textLabel?.text = maritalStatusArray[indexPath.row]
} else {
cell.textLabel?.text = cantonArray[indexPath.row]
}
return cell
}
This is how you can declare enum
enum SelectedRow {
case MaritalStatus
case States
case ThirdRow
}
var selectedRow = SelectedRow.MaritalStatus
I have created this table with 3 sections and 7 rows. The code is shown below
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var subjectTabelView: UITableView!
var slSubject = ["English Lang&Lit", "Chinese Lang&Lit", "Economics"]
var hlSubject = ["Mathematics", "Chemistry", "Biology"]
var tokSubject = ["Theory of Knowledge"]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
subjectTabelView.dataSource = self
subjectTabelView.delegate = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 3
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0{
return hlSubject.count
}else if section == 1{
return slSubject.count
}else {
return tokSubject.count
}
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let subjectCell = tableView.dequeueReusableCellWithIdentifier("idSubjectCell", forIndexPath: indexPath) as! UITableViewCell
if indexPath.section == 0 {
subjectCell.textLabel?.text = hlSubject[indexPath.row]
} else if indexPath.section == 1{
subjectCell.textLabel?.text = slSubject[indexPath.row]
} else {
subjectCell.textLabel?.text = tokSubject[indexPath.row]
}
return subjectCell
}
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if section == 0 {
return "HL"
} else if section == 1{
return "SL"
} else {
return "ToK"
}
}
}
What do I have to do to make every cell in this table pushes a new view controller when it is tapped? The picture of my storyboard is shown below. In my storyboard, my view controller, I have already created a navigation controller, and made the view controller that has the table the rootViewController. And for now, my tableView has only one prototype cell and one cell identifier.
Thank you!
Suppose your "locationVC" is:
class LocationVC: UIViewController {
#IBOutlet weak var fromWhereLabel: UILabel!
//This can be changed when creating this UIViewController
var textToShow : String?
override func viewWillAppear(animated: Bool) {
if let textToShow = textToShow {
fromWhereLabel.text = textToShow
}
}
}
then, just adding function below to your code in ViewController named UIViewController (that should have a better name ;-)) you can achieve your goal.
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
//if such cell exists and destination controller (the one to show) exists too..
if let subjectCell = tableView.cellForRowAtIndexPath(indexPath), let destinationViewController = navigationController?.storyboard?.instantiateViewControllerWithIdentifier("locationVC") as? LocationVC{
//This is a bonus, I will be showing at destionation controller the same text of the cell from where it comes...
if let text = subjectCell.textLabel?.text {
destinationViewController.textToShow = text
} else {
destinationViewController.textToShow = "Tapped Cell's textLabel is empty"
}
//Then just push the controller into the view hierarchy
navigationController?.pushViewController(destinationViewController, animated: true)
}
}
You will be able to have a LocationVC UIViewController launched every time you tap a cell, and it will have some value to prove it right. :)
Hope it Helps!
UPDATE: Code and Instructions below are for allowing to launch
different UIViewControllers after tap on cells
1.- Let's create a class that will be the parent for every one of our new UIViewControllers (the ones we are willing to go from our tableview cell's tap):
public class CommonDataViewController: UIViewController {
//Here we are going to be putting any data we want to share with this view
var data: AnyObject?
}
2.- Let's create some sort of Navigation rules, just to be organised ;-)
enum Navigation: Int {
case vc1 = 0, vc2 = 1, vc3 = 2, vc4 = 3
//How many rules we have (for not to exceed this number)
static let definedNavigations = 4
//This must return the identifier for this view on the Storyboard
func storyboardIdentifier() -> String {
//for this example's sake, we have a common prefix for every new view controller, if it's not the case, you can use a switch(self) here
return "locationVC_\(self.rawValue + 1)"
}
}
Now, let's build upon previous code:
3.- For clarity, let's change a little our previous LocationVC (that for this example, will have an Storyboard Identifier with the text "locationVC_1")
class LocationVC: CommonDataViewController {
#IBOutlet weak var fromWhereLabel: UILabel!
//This is optional, but improves clarity..here we take our AnyObject? variable data and transforms it into the type of data this view is excepting
var thisVCReceivedData: String? {
return data as? String
}
override func viewWillAppear(animated: Bool) {
if let textToShow = thisVCReceivedData {
fromWhereLabel.text = textToShow
}
}
}
4.- Now, we trigger all of this in our didSelectRowAtIndexPath function.
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
//Just to avoid tapping on a cell that doesn't have an UIViewController asociated
if Navigation.definedNavigations > indexPath.row {
//The view's instance on our Navigation enum to which we most go after tapping this cell
let nextView = Navigation(rawValue: indexPath.row)!
//The identifier of the destination CommonDataViewController's son in our Storyboard
let identifier = nextView.storyboardIdentifier()
//If everything exists...
if let subjectCell = tableView.cellForRowAtIndexPath(indexPath), let destinationViewController = navigationController?.storyboard?.instantiateViewControllerWithIdentifier(identifier) as? CommonDataViewController {
//here you can use a switch around "nextView" for passing different data to every View Controller..for this example, we just pass same String to everyone
if let text = subjectCell.textLabel?.text {
destinationViewController.data = text
} else {
destinationViewController.data = "Tapped Cell's textLabel is empty"
}
navigationController?.pushViewController(destinationViewController, animated: true)
}
}
}
Notice that you can achieve same results using protocols and delegate approach, this is just simpler to explain
Well to push a view controller in a UINavigationController you just use this code:
ViewController *viewController = [self.navigationController.storyboard instantiateViewControllerWithIdentifier:#"locationVC"];
[self.navigationController pushViewController:viewController animated:YES];
The method you are looking for is this one:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
ViewController *viewController = [self.navigationController.storyboard instantiateViewControllerWithIdentifier:#"locationVC"];
[self.navigationController pushViewController:viewController animated:YES];
}
You could use prepareForSegue method. You just need to set up the destination view. or the didselectrowatindexpath
prepareForSegue code looks like :
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "nameofTheSegue"
{
if let destinationVC = segue.destinationViewController as? OtherViewController{
// do whatever you want with the data you want to pass.
}
}
}
I’m implementing a search bar with an UISearchController within a sectioned table. So far so good.
The main issue is that when the the filtered results come along, it’s a whole new table with no sections and fewer rows.
When selecting the row, I perform a segue to that position in the array, but the detailed view is expecting that exact row or index from the main array, which I can’t get from the filtered array of objects, which may be [0] [1] [2] in 300 elements.
I guess I can compare the selected object with the main array and assuming there’s no duplicates, get the index from there and pass it over… But these seems pretty inefficient to me.
Apple does something similar (I unfortunately don’t know how) when filtering Contacts, in the Contacts App. How they pass the contact object? That’s pretty much my goal.
Here I let you a snippet of what I’m doing:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if(self.resultSearchController.active) {
customerAtIndex = indexPath.row // Issue here
performSegueWithIdentifier("showCustomer", sender: nil)
}
else {
customerAtIndex = returnPositionForThisIndexPath(indexPath, insideThisTable: tableView)
performSegueWithIdentifier("showCustomer", sender: nil)
}
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showCustomer" {
if let destination = segue.destinationViewController as? CustomerDetailViewController {
destination.newCustomer = false
destination.customer = self.customerList[customerAtIndex!]
destination.customerAtIndex = self.customerAtIndex!
destination.customerList = self.customerList
}
}
}
You can either do in another way, it a trick, but it works. First change your didSelectRowAtIndexPath as below:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
var object :AnyObject?
if(self.resultSearchController.active) {
object = filteredArray[indexPath.row]
}
else {
object = self.customerList[indexPath.row]
}
performSegueWithIdentifier("showCustomer", sender: object)
}
Now, in prepareForSegue, get back the object and send it to your detailed view controller
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showCustomer" {
if let destination = segue.destinationViewController as? CustomerDetailViewController {
destination.newCustomer = false
destination.customer = sender as! CustomerObject
destination.customerAtIndex = self.customerList.indexOfObject(destination.customer)
destination.customerList = self.customerList
}
}
}
Here's the trick I used in my code, I basically load the tableView from the filteredObjects array so then indexPath is always correct:
var selectedObject: Object?
private var searchController: UISearchController!
private var allObjects: [Object]? {
didSet {
filteredObjects = allObjects
}
}
private var filteredObjects: [Object]? {
didSet {
NSOperationQueue.mainQueue().addOperationWithBlock {
self.tableView.reloadData()
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
loadData { objects in
self.allObjects = objects
}
}
// MARK:- UITableView
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return filteredObjects?.count ?? 0
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! UITableViewCell
cell.textLabel?.text = filteredObjects?[indexPath.row].name
return cell
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
selectedObject = filteredObjects?[indexPath.row]
}
// MARK:- UISearchBarDelegate
func searchBar(searchBar: UISearchBar, textDidChange searchText: String) {
if !searchText.isEmpty {
filteredObjects = allObjects?.filter{ $0.name.lowercaseString.rangeOfString(searchText.lowercaseString) != nil }
} else {
filteredObjects = allObjects
}
Add a new property NSMutableArray *searchArray to your table view class and then pass all search results to this array in -(void)filterContentForSearchText:scope: method. After that you will be able to get the selected object self.searchArray[indexPath.row] in tableView:didSelectRowAtIndexPath:.
I see two solutions -
1) Why not make detailed view look for row or index in filtered array instead of main array. I guess you are concerned only about the object in that row that you want to use in detail.
2) Make each object in the array have a unique id. Pass the unique id on selection thru segue and let detailed view search(predicate) in main array for that id.