Swift: Segue Error when calling - swift

I have a segue setup in my swift class called ViewController and I am calling from a tableView didSelectRowAtIndexPath. I am using this code in my segue
override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
var svc = segue!.destinationViewController as Homework;
svc.subject = subject_name
}
To tell it to set a varible called subject which is declared like this var subject:NSString! to a varible called subject_name. I then call it from my tableView didSelectRowAtIndexPath using this code prepareForSegue(UIStoryboardSegue(), sender: AnyObject?()).
This is my didSelectRowAtIndex
func tableView(tableView: UITableView!, didSelectRowAtIndexPath indexPath: NSIndexPath!) {
println("You selected cell #\(indexPath.row)!")
let indexPath = tableView.indexPathForSelectedRow();
var currentCell = tableView.cellForRowAtIndexPath(indexPath) as UITableViewCell
println(currentCell.textLabel!.text)
subject_name = currentCell.textLabel.text
// // Showing new storyboard
performSegueWithIdentifier("Homework", sender: self)
let vc : AnyObject! = self.storyboard.instantiateViewControllerWithIdentifier("Homework")
self.showViewController(vc as UIViewController, sender: vc)
}
But when I go and run the app, and click the table view cell I get this error "fatal error: unexpectedly found nil while unwrapping an Optional value", and a green arrow points towards var svc = segue!.destinationViewController as Homework;. I tested the exact same code out on another app expect with the segue being called when a button is clicked and that worked perfectly, I also tried answers from Calling segue programatically not working, and Preparing for segue in embedded tableView in Swift. Both of these answers did not work.

If I understood it correctly, you are calling prepareForSegue(). You shouldn't, that is automatically invoked.
What you should do instead is calling performSegueWithIdentifier(identifier: String, sender: AnyObject?). That triggers a segue invocation, which automatically executes prepareForSegue().
The identifier parameter is the one you set from IB: select the segue and look at the attributes inspector.
Besides that, #AnthonyKong's answer is a safer way to deal with optionals (a segue in this case) - that ensures that no runtime exception is thrown.
Addendum
Looking at your updated question, specifically at the implementation of didSelectRowAtIndexPath. The last 2 lines:
let vc : AnyObject! = self.storyboard.instantiateViewControllerWithIdentifier("Homework")
self.showViewController(vc as UIViewController, sender: vc)
are redundant - if you perform a segue, that will instantiate the destination view controller, so you don't have to do it manually. Remove those lines.

You should do this instead:
if let svc = segue!.destinationViewController as? Homework {
svc.subject = subject_name
}
It is because you might get passed other segues which do not have Homework as desination VC

Related

setting variables between view controllers

I'm new to swift and I'm trying to learn how to pass data between view controllers and use firebase along with it. This is my segue to a new controller with it setting a variable on that view controller I'm pushing to.
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let viewController = storyboard?.instantiateViewController(withIdentifier: "UserInfoViewController") as! UserInfoViewController
viewController.uidPassed = userUIDArray[indexPath.row]
self.present(viewController, animated: true, completion: nil)
}
It segues to the new controller where I have the variable declared as a string. When I go to run this it crashes with it saying that the string was empty. I can print it and it is empty in the viewDidAppear method, but the viewDidLoad method it has the UID stored in the variable from when I clicked on in the previous controller. Am I not passing the variable from one view controller to the other correctly?
override func viewDidAppear(_ animated: Bool) {
databaseRef.child("Users").child(self.uidPassed).child("Name").observe(.value, with: { (snapshot) in
let name = snapshot.value as! String
print(name)
})
There are two main ways to pass data from one view controller to another view controller.
First, you can connect your cell with the destination view controller in storyboard. In this case you will define the type of segue in storyboard, and you need to define an identifier for the segue in storyboard. When you want to pass data, you need to do it in prepareForSegue method, like this
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destinationViewController = segue.destination as? YourTableViewController {
destinationViewController.texts = self.texts
//...
}
}
Second, you can do this programmatically, like what you are trying to do in your example. In this case you should not connect view controllers by segue in storyboard. Instead, you set an identifier for the destination view controller itself. Then you initiate that controller in the code, set properties to the initiated view controller object and then present it, like what you are doing in your example.
Hope that helps.
This sounds like a typing issue. It seems like userUIDArray holds an array of UID's (not sure what type this is), and you're trying to assign it to viewController.uidPassed, which sounds like it's expecting a string. Try this:
viewController.uidPassed = userUIDArray[indexPath.row] as! String
The other option is to change the type of uidPassed to whatever type of value the userUIDArray variable holds.

Using 3D Touch with storyboard's peek and pop

I found very easy way to use 3D Touch — check "Peek & Pop" in storyboard. But I'm struggling with one problem.
I have UITableView, when user touches cell all is working ok with my code:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showDetail" {
print(tableView.indexPathForSelectedRow)
}
}
So I'm filling data in my Detailed controller based on selected row. But when I'm pressing with Peek or Pop method tableView.indexPathForSelectedRow always returning nil (hm.. I haven't selected row, I'm just previewing so indexPath is nil I guess). How can I get that "peeked" cell indexPath to pass it to segue?
Or storyboard's Peek & Pop not working in this simple way and I need to fully implement peek & pop in my code?
It is possible to fully implement it using the storyboard and prepareForSegue. You can do this by making use of the sender object.
By assuming that you have created your segue directly from the table cell in the storyboard to the next view controller, then the sender object will be of the type UITableViewCell. If you trigger the segue programmatically, then just remember to set the sender object in the method call.
You can use this cell to get a hold of the NSIndexPath from the tableView, something similar to the following (ignore all the force unwrapping, this is just for demonstration purposes):
override func prepare(for segue: UIStoryboardSegue, sender: AnyObject?) {
let cell = sender as! UITableViewCell
let indexPath = tableView.indexPath(for: cell)!
if segue.identifier == "mySegue" {
let destination = segue.destination as! DetailsViewController
destination.model = self.model[indexPath.row]
}
}

Master-Detail: UINavigatorController vs Storyboard Segue

Scenario: Master(TableView) --> Detail.
Modus Operandi: Select Row --> display DetailVC
As you can see below, I have a MasterVC embedded in a UINavigationController:
I currently display the DetailVC via pushing it into the UINavigationController's VC stack:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
println("tableView didSelectRowAtIndexPath")
let storyboard = UIStoryboard(name: "Bliss", bundle: nil);
let controller = storyboard.instantiateViewControllerWithIdentifier("DiaryPlayerVC") as DiaryPlayerViewController
self.navigationController?.pushViewController(controller, animated: true)
}
This works fine.
However, the 'prepareForSeque' doesn't fire:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showDiaryPlayer" {
if let indexPath = self.tableView.indexPathForSelectedRow() {
let object = objects[indexPath.row] as NSDate
// (segue.destinationViewController as DiaryPlayerViewController).detailItem = object
}
}
}
I understand that I probably have two (2) conflicting paradigms here:
1) Using the UINavigationController vs
2) Using the Storyboard Relationship.
So...
Option 1: it appears that I can remove the Segue link to have a storyboard stand-alone DetailVC.
Option 2: via Segue, I'm assuming I can remove the UINavigatorController from the link.
I'm currently using Option #1, launching the DetailVC via the UINavigationController.
Question: If I choose Option #2, how do I access (launch) the DetailVC ("Diary Player") from the Master's Row and hence, fire the Segue's 'prepareForSegue()'?
Answer: create a segue from the table view cell to the detail view controller.
Your screenshot shows that you already created a segue in your storyboard. Give that segue an identifier in its property inspectory. Then you can simply perform the segue in the didSelectRowAtIndexPath method:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
println("tableView didSelectRowAtIndexPath")
performSegueWithIdentifier("mySegueIdentifier", sender: nil)
}
Note: Ctrl-drag the segue from the TableViewController icon, not from the TableViewCell.

TableView Pushing to Detail View in Swift

I need help with pushing my tableView to a detailView. Right now I have most of the code, but right when my app loads, it goes to the detailedView, and whenever I try to go back to the tableView, it instantly goes back to the detailedView and the following error shows up in the debug area:
Finishing up a navigation transition in an unexpected state. Navigation Bar subview tree might get corrupted.
I don't understand why this is happening since my code is not in the viewDidLoad section of the code... Here is my code for the tableView:
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return Label.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "Cell")
cell.textLabel.text = Label[indexPath.row]
performSegueWithIdentifier ("showDetail", sender: self)
func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if segue.identifier == "showDetail" {
}
}
return cell
}
Please leave an answer below if you can help me make my segue work how it is supposed to. Thanks!
P.S. I am also curious on how to customize the back button on a navigation bar from "< Back" to just "<".
P.S.S. How can I make the tableViewCell auto release after pressed so it doesn't stay highlighted? Just so it looks like a nicer app overall.
P.S.S.S Sorry, but one last thing is how can I customize the DeatilView based on what cell the user clicks on? I already have the new class set up with text, but not sure how to make the if statement that customizes the text for each cell clicked.
Thanks again!
The error is in your cellForRowAtIndexPath:
performSegueWithIdentifier ("showDetail", sender: self)
this triggers the segue when the cell is displayed, not when it's selected. The easiest way to fix that is to create a segue in IB from the cell to the destination view controller.
Also, this code:
func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if segue.identifier == "showDetail" {
}
}
creates a function local to cellForRowAtIndexPath which is never executed. The correct way is to declare it in the class scope as an override of the superclass implementation.
If you have already defined a segue in IB, then your code should like:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "Cell")
cell.textLabel.text = Label[indexPath.row]
return cell
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showDetail" {
}
}
you have to perform the segue in tableView(_:didSelectRowAtIndexPath:) not
tableView(_:cellForRowAtIndexPath:)
P.S. How to customize the back button on a navigation bar from "< Back" to just "<".
I found this old question has the answer in Objective C, at How to change text on a back button
You must set this in the View Controller you are segueing from.
Let's say we have a simple iPhone app, that has a UITableViewController with a bunch of cells listing off numbers, let's call it the "NumberListTVC". We embed this NumberListTVC in a NavigationController. We set a selection segue on one of the cells to another ViewController, which we'll call the "NumberDisplayerVC". This NumberDisplayerVC has one label, which we will set to the contents of the label that was clicked on the NumberList. So when you select a cell in NumberListTVC, it will push to the "NumberDisplayer" in the NavigationController.
So for this app, you would put the code to customize the back button in the NumberListTVC, so that when you are looking at the NumberDisplayerVC, it will show the back button we want it to, instead of the default (since the back button brings it back to the NumberListTVC).
In Objective-C, as per the linked answer, the code to change "< Back" to "<" would be:
self.navigationItem.backBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:#"" style:UIBarButtonItemStylePlain target:nil action:nil];
Basically, we create a new UIBarButtonItem with whatever title we want. In this case, I just gave it the title of "", which is just an empty String.
The same command in Swift would be:
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style:.Plain, target: nil, action: nil)
I put this in NumberListTVC's viewDidLoad method, to make sure it was ran when NumberListTVC loaded.
P.S.S. How can I make the tableViewCell auto release after pressed so it doesn't stay highlighted?
This is covered in Objective-C on the answer Selected UItableViewCell staying blue when selected
You just have to tell the UITableViewCell to deselect it in its didSelectRowAtIndexPath.
In Objective-C, you would override it and do this:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
//Do whatever else you want to do here...
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
The Swift code would be similar:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath)
{
//Do whatever else you want to do here...
tableView.deselectRowAtIndexPath(indexPath, animated: true)
}
P.S.S.S How can I customize the DetailView based on what cell the user clicks on?
For simplicity's sake, I am going to use my first example which is just using our navigationController with the NumberListTVC and NumberDisplayerVC, so not on a UISplitView, but the question asked affects either scenario.
Basically, you have to get what you want from the cell that was clicked on, and then set it to whatever you want to use it in the next ViewController. So, in your prepareForSegue, you would do something like this:
if segue.identifier == "amazingSegue"
{
if let cellText = (sender as? UITableViewCell)?.textLabel.text
{
if let someVC = segue.destinationViewController as? NumberDisplayerVC
{
someVC.textToDisplay = cellText
}
}
}
This is basically what Antonio said in his answer. I added a bit more to it. So in my case, the NumberListTVC is a bunch of dynamically created UITableViewCells. I want to grab the label out of each one and then use that in the NumberDisplayerVC. So we:
Check if it is the segue we want to actually work with, in this case the segue between NumberListTVC and NumberDisplayerVC is named "amazing segue".
Next, use optional binding to assign the text of the sender cell to the constant cellText. We use the optional type cast operator to check if the sender is a UITableViewCell. We then use optional chaining to look inside this UITableViewCell and extract the text from its textLabel.
Again use Optional Binding to assign our destinationViewController to the constant someVC. We optionally type cast it to a NumberDisplayerVC, which is what it should be. If it indeed is one, it will correctly bind to the someVC variable.
Then just set the textToDisplay property to the cellText constant we optionally bound earlier.
I'm not sure if this is the best way, I don't feel great about the tower of if let statements, but it does the job initially.

Passing values between ViewControllers based on list selection in Swift

I'm trying to pass the selected index number of a listView selection from one ViewController to another but am running into an issue with the tableView didSelectRowAtIndexPath delegate runs slightly later than the prepareForSegue function.
Basically, in didSelectRowAtIndexPath, I seta variable, which is then picked up in the prepareForSegue. The issue is that prepareForSegue seems to run the second that the cell is selected and before the didSelectRowAtIndexPath function is called so my variable is not passed.
My main bits of code are:
tableView didSelectRowAtIndexPath delegate, which sets 'selectedResult'...
func tableView(tableView: UITableView!, didSelectRowAtIndexPath indexPath: NSIndexPath!) {
selectedResult = indexPath.item
txtNameSearch.resignFirstResponder() //get rid of keyboard when table touched.
println("saved result")
//tableView.deselectRowAtIndexPath(indexPath, animated: false)
//var tappedItem: ToDoItem = self.toDoItems.objectAtIndex(indexPath.row) as ToDoItem
//tappedItem.completed = !tappedItem.completed
//tableView.reloadData()
}
prepareForSegue function which sends the variable as 'toPass' to the other ViewController ('detailViewController'):
override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!){
if (segue.identifier == "detailSeque") {
println("preparing")
var svc = segue!.destinationViewController as detailViewController
svc.toPass = selectedResult
//println(tableView.indexPathsForSelectedRows().
//svc.toPass = tableView.indexPathForSelectedRow()
}
}
Thanks in advance
If you have an outlet to your tableView in your ViewController, you can just call indexPathForSelectedRow in prepareForSegue.
If your ViewController is a subclass of UITableViewController, then you can do:
let row = self.tableView.indexPathForSelectedRow().row
println("row \(row) was selected")
If your ViewController is not a subclass of UITableViewController, set up an IBOutlet to the UITableView in your view controller:
#IBOutlet var tableView: UITableView!
and wire that up in Interface Builder and call it from prepareForSegue as show above.
Instead of triggering your segue directly from a storyboard action, why don't you try programmatically calling performSegueWithIdentifier in your didSelectRowAtIndexPath method, after selectedResult is set?