iOS Swift - UITableView didselectrowatindexpath crashes - ios

First of all, let me say that this is a noobish question so please bear with me here :D. I have a tableview embedded in a navigation control and it is my main view. There is an "Add" button where I can add "things" then when that is triggered it shows the added item on the table view and also switches back to the main view controller with the code below
#IBAction func buttonAddPost_Click(sender: UIButton) {
postMgr.addPost(titleBox.text, description: descriptionBox.text, postDate: date)
self.navigationController.popToRootViewControllerAnimated(true)
}
Again the above code takes me back to the main table view controller just fine.
But when I click on a cell to take me to a more detailed view, the app crashes. Here is the code
override func tableView(tableView: UITableView!, didSelectRowAtIndexPath indexPath: NSIndexPath!) {
let listingPage = self.storyboard.instantiateViewControllerIdentifier("listingPage") as PostListingTableViewController
self.navigationController.pushViewController(listingPage, animated: true)
}
"listingPage" is the storyboard ID for the new view controller I'm trying to go to. I have used the above technique a few times somewhere else and worked just fine but I'm not sure what's wrong here.
Error I get:
Thread 1
0 swift_dynamicCast and it highlights 0x1d7150: pushl %ebp Thread 1: EXC_BREAKPOINT (code=EXC_I386_BPT)
1
2 Exchange.PostlistTableViewController ([app name].[class name]) and it highlights the didselectrowatindexpath
.
.
.
Please help...
KM

The exception tells you what the problem is - You are casting the view controller as PostListingTableViewController but the object type that is returned is PostlistTableViewController so the cast generates an exception.
You need to determine what the correct class name is and either update your storyboard or your didSelectRowAtIndexPath so that they are consistent - either Postlist or PostListing.

Related

Strange behaviour during mid-swipe back

I'm experiencing a strange glitch where I can use the previous view controller before the top view controller has been dismissed.
At my main view controller I have a table view with the delegate function didSelectRowAtIndexPath. The function is below:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
performSegueWithIdentifier("commentsSegue", sender: self)
}
This works great, however if I am already showing this screen, I can select it again if I half-swipe back. This is sort of hard to explain but you can use a finger to swipe back and the other to select a cell on the main viewcontroller. This creates another segue to a new "commentsSegue". I can do this as many times as I like and it will action many segues.
I have tried to overcome this by using
if (self.presentingViewController?.presentedViewController == self) {
and also
if (self.navigationController?.topViewController.title == self.title) {
But both of these functions return the main viewcontroller as the active view controller instead of the "commentsSegue" controller.
How can I stop this behaviour from occurring?
On swipe create a Notification which check if your destination controller .isKindOfClass your current one. If not , make the segue.

Change variable based on Table View Cell

I need an event, that changes a variable based on which TableViewCell I click. But unlike an action connected to a button, there is no action indicator for table view cells at all. So my question is:
I want to make a TableView that contains items of an array. Based on which item I click, I want to change my variable so that the result on the next ViewController depends on which button you click.
So to make things easier, here is an example what I want the app to look like:
On the first TableViewController I have a list based on an array and on the second ViewController I have a label that shows text based on the variable.
I have a nameArray = ["Mum", "Brother", "Me"] and a weightArray = [140, 160, 120] and a variable weight = 0. The label on the second ViewController tells the var weight. So when you click on "Mum" in the TableView I want the next ViewController to say 140, when I click on "Brother" then 160 and so on...
Until here everything works just fine and I have no problems with anything but changing the var based on what I click.
Long story, short sense:
I want an Action for the TableViewCell that changes the var like in an Action connected to a Button, but there is no Action outlet for Cells at all.
Use this method. Use indexPath.row to find what row number you selected
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
var cell : UITableViewCell = tableView.cellForRowAtIndexPath(indexPath)!
switch cell.labelSubView.text as! String {
case "Mum":
self.weight = weightArray[0]
case "Brother"
self.weight = weightArray[1]
and so on..
..
default:
statements
}
}
Note A better alternative
I also considered a case where you have too many entries in nameArray and switch statement might not be good. In that case you can get the text inside the selected row by cell.labelSubView.text as! String
next you can check if the nameArray contains the cell text and get the index of the name that matches the cell text. Next you can get the required weight at the same index in weightArray. And then do self.weight = weightArray[requiredIndex]
Hope this helps.
Update : My experienced friend #Duncan mentioned down below that switch statement in this case is a bad coding practice . I am not going to delete it because it is a lesson for me and also my fellow programmers who are relatively new to programming. So i have put it in a yellow box, stating that it is not a good code
A better option for this would be :
As Duncan mentions, creating an array of dictionary is a good option
Second option is the option in my answer after my Note
You need to maintain array of dictionaries , those dictionaries have keys like "person", and "weight", then you can easily get weight value after selecting the cell by using table view delegate method UITableViewDelegate's tableView:didSelectRowAtIndexPath:
Create an instance variable in your view controller (a var at the top level after the class definition) for the selected cell.
class MyTableViewController: UIViewController
var selectedRow: Int
override func tableView(tableView: UITableView,
didSelectRowAtIndexPath indexPath: NSIndexPath)
{
selectedRow = indexPath.row
//invoke a segue if desired
performSegueWithIdentifier("someSegue");
}
override func prepareForSegue(segue: UIStoryboardSegue,
sender: AnyObject?)
{
if segue.identifier == "someSegue"
{
//Cast the destination view controller to the appropriate class
let destVC = DestVCClass(segue.destinationViewController)
destVC.selectedRow = selectedRow
}
}
As Andey says in his answer, it's probably better to create a single array of data objects (dictionaries, structs, or custom data objects). Then when the user taps a cell, instead of passing the index of the selected row to the next view controller, you could pass the whole data object to the destination view controller. Then the destination view controller could extract whatever data it needed. (Weight, in your example.)

Call UITableViewCell Method from Method in main VC?

I'm trying to do the opposite of what most people on this topic are asking to do. Most people want a button within a table view cell to call a method in their VC / VC table. I already have a protocol doing that.
Problem / Question
What I am trying to add now is the opposite: I need a button press on my main ViewController (which houses my table) to call a method within my CusomTableViewCell class (note: the button pressed on the main VC is not in the table). I have the protocol class created and the function written, but I don't know how to set the CustomCellViewClass as the delegate. When I did the opposite, I inserted "cell.delegate = self" into the cellForRowAtIndexPath method. I've also used prepareForSegue to assign a delegate. But with no segue and now cell-creation-method, I'm lost!
Example of Desired Function
My end goal is that pressing a button that is in the main VC will change the title of a button within the cells. A simple example would be that I have one view with a single table, on button press the table contents switch between two arrays, cars and motorcycles. When the table is showing cars, the cell button titles should all read "Look inside" but when showing the motorcycle button it should read "Look closer".
Code
I've already written the function that I want the cell to execute:
func cellButton_Title_Switch (currentList: String) {
if vcState == "cars" {
cellButton.setTitle("Look inside", forState: UIControlState.Normal)
}
else {
cellButton.setTitle("Look closer", forState: UIControlState.Normal)
}
}
I created the protocol:
protocol delegateToChangeCellBut {
func cellButton_Title_Switch (currentList: String)
}
I have the self.delegate.cellButton_Title_Switch(currentList) within my VC button and the protocol added to my custom cell class declaration. But how do I do that last missing piece in the custom cell class, where I assign the class to the delegate?
My original problem was that my UITableView's cell has buttons and labels, some of which change to match the state of things outside the table, things handled by the mainViewController.
The custom cell is defined by a customCellviewController. All the custom cell buttons and labels have their IBOutlets connected to the customCellviewController. I couldn't figure out how to make an action/change outside the table (in the mainViewController) immediately cause the cell labels and buttons to change.
Note: Protocols tend to work they other way around (a cell action triggers a function in the mainVC). I couldn't figure out how to use a protocol to solve this. Luckily, the solution was much simpler than a protocol.
The Solution!
I wrote the "updateCell" method that would change the labels and buttons and that code now sits in the customCellviewController. Then I called/triggered the "updateCell" function from the mainViewController simply by adding the call into my cellForRowAtIndexPath function. So it looks something like this:
var stateOfPage = "Green"
//Creates the individual cells. If the above function returns 3, this runs 3 times
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
//Setup variables
let cellIdentifier = "BasicCell"
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! customCell
cell.updateCell(stateOfPage)
return cell
}
Now the above code/method runs when the table gets built. So to update the cells, have some button tap or other action reload the table data:
tableView.reloadData()
Good luck.

Segue to a New Instance of the Current View Controller

I have a storyboard view embedded in a navigation controller that displays a record from a database along with a link to view a related record. The related record needs to use the same view to display its data while still maintaining a navigation stack so the user can go back to the previous record. Keeping in mind that some data needs to be passed to the new viewController and the UI is composed of a tableView with each element in a row, how can this segue be accomplished?
Below is the view. If possible, please respond with any sample code in Swift.
With some inspiration from this answer and guidance by #Jassi, here is the final product:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let vc = storyboard?.instantiateViewControllerWithIdentifier("inventoryItemDetail") as InventoryDetail
vc.fmRecordId = item["inContainerRecordId"]! //this is the data which will be passed to the new vc
self.navigationController?.pushViewController(vc, animated: true)
}
An idea-
Give the view controller an identifier. And override the below function.
prepareForSegue
In that function instantiate the view controller using the identifier you have and then pass the necessary data to that controller. And push it on navigation controller.
I hope it will work.

Reusing a detail UIViewController involved in a storyboard segue

I have a master/detail application running on an iPad. When in landscape mode, I have both views up side-by-side. The right/detail view controller contains an MKMapView.
The issue is that when selecting a different table cell in the left/master view controller, and essentially re-performing the segue, the entire detail view controller is reinstantiated.
This means that the MKMapView I was using loses the user's position, and essentially starts from scratch, zooming in from the country scale to the street scale.
Is there a way to determine, prior to performing the segue, whether the detail view being displayed is already the one I want, and simply providing it new data and telling it to refresh?
For example:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
segueParkName = parkNames[indexPath.row]
self.performSegueWithIdentifier("showParkDetails", sender: self)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showParkDetails" {
let controller = (segue.destinationViewController as UINavigationController).topViewController as ParkDetailsController
NSLog("Controller: \(controller)") // Different instance every time!
controller.parkName = segueParkName
}
}
I would like to either:
Somehow tell iOS that by the time prepareForSegue is reached, I'm okay with being provided a reused view controller, especially (!) if it's already displayed.
In the didSelectRowAtIndexPath method, perform a custom segue and do my own pushing. But I really like the idea of using the built-in system segues so I don't have to be specific about what I'm pushing and where. It seems more device-agnostic to use Show Detail (eg. Replace) than defining my own.
I think, in your first suggestion, it will be troublesome if not impossible to abandon the segue once you are in prepareForSegue. So I would go with your second option. But you don't need to trigger a segue at all, if the detail viewController you want is already in place. So rather than
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
segueParkName = parkNames[indexPath.row]
self.performSegueWithIdentifier("showParkDetails", sender: self)
}
you might have something like...
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
segueParkName = parkNames[indexPath.row]
self.detailViewController.parkName = segueParkName
}
This assumes that you already have a property detailViewController pointing to your detail ViewController. It also assumes that the detailViewController will always be the one you need - if necessary, check the detailViewController class to see whether it is the MKMapView you want. Finally, if setting parkName doesn't achieve everything you need (e.g. animating the change), then just implement a new method in your MkMapView and call that in place of setting parkName.
EDIT Just to expand on that, you can use:
if self.detailViewController.isKindOfClass(yourMKMapViewSubclass) {
self.detailViewController.parkName = segueParkName
}
to test whether detailViewController is indeed your MkMapView.
You can cancel a segue by implementing shouldPerformSegue however that is for the case where the park name is invalid for some reason, to prevent showing a view controller for an invalid park.
In this case the solution is use the reference to the detail controller in your master controller that the built-in master/detail template does for you. Then in prepareForSegue take the map from the old detail controller and put it on the new one.
As your app gets more complex it may no longer be suitable for the master to maintain a reference to the detail controller. For example, if you make a root controller that pushes a new master, then the master will not find the detail when the app is in portrait like the template app can. Thus in that case your class that implements the split controller delegate can also maintain the context for your master/detail (something that is initWithSplitViewController). By setting an owningContext param self on the splitViewController via a category in the init for this class, then you can access it from where you need to. E.g. setting the mapView on it from the master. And getting the mapView from it in the loadView of the detail.

Resources