Master-Detail: UINavigatorController vs Storyboard Segue - swift

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.

Related

Xcode Cannot go back to TableViewController in navigation stack

I have controller1 -> TableViewController2 -> TableViewController3 in my storyboard. When I press a button in controller1, I want to jump to TableViewController3 and from there when I select a row, I want to go back to TableViewController2 and get some data and then go back to controller1.
In controller1 instantiate TableViewController3:
if segue.identifier == "MySegue" {
let viewController:TableViewController3 = segue.destination as! TableViewController3
viewController.addLocationToMap = true
}
In TableViewController3 instantiate TableViewController2 like this:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if addLocationToMap == true {
let navc:UINavigationController = self.navigationController!
let tvc:UITableViewController = TableViewController2()
let rootView = navc.viewControllers[0]
navc.setViewControllers([rootView, tvc], animated: true)
return
}
In TableViewController2 viewDidLoad, it fails in ViewDidLoad when I try to set a text field value because the textfield is nil. It cannot be because Textfield is already in the view. Looks like the TableViewController2 view never got loaded.
in TableViewController2
override func viewDidLoad() {
super.viewDidLoad()
locationPurposeTextField.text = "sometext"
}
Fails when setting text value because locationPurposeTextField is nil.
How to fix this?
EDITS:
On pressing a button In controller1:
let navc:UINavigationController = self.navigationController!
let alTvc = self.storyboard!.instantiateViewController(withIdentifier: "AddLocationID") as! UITableViewController
navc.pushViewController(alTvc, animated: false)
let cListTvc = self.storyboard!.instantiateViewController(withIdentifier: "ContactListID") as! UITableViewController
navc.pushViewController(cListTvc, animated: true)
The code takes me to TableViewController3 with storyboard ID: ContactListID as desired.
Next, in TableViewController3
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.myDelegate?.userSelectedContact(contactIdentifier: self.contactId, contactFullName: fullName, contactSelected: true)
let navc:UINavigationController = self.navigationController!
let tvc:UITableViewController = self.storyboard!.instantiateViewController(withIdentifier: "AddLocationID") as! UITableViewController
let rootView = navc.viewControllers[0]
navc.setViewControllers([rootView, tvc], animated: true)
}
Takes me to TableViewController2 with storyboard ID: AddLocationID
Based on the delegate sent TableViewController3, in ViewDidappear method of TableViewController2, I set some text in the view and call tableview.realoadData(). It does not load the view.
However if I select a button in TableViewController2 and load TableViewController3 and then comeback to TableViewController2 upon execution of the very same method didSelectRow it reloads the view in TableViewController2
How to fix it, please let me know?
There are a couple of things wrong with your approach.
If you want to go from controller1 to TableViewController3 but have your navigation stack contain controller1 -> TableViewController2 -> TableViewController3, you will have to have the button in controller1 first push TableViewController2 without animation, and then push TableViewController3 with animation. (You won't be able to have your button trigger a segue.)
Second problem: You can't create a copy of your TableViewController2 by just invoking it's initializer (TableViewController2()). When you do that it doesn't load its views from the storyboard. Instead, you should assign a storyboard identifier to it and use the UIStoryboard method instantiateViewController(withIdentifier:) to create a new instance of it.

Perform a segue programmatically

Hi I'm trying to perform a segue programmatically without the Storyboard. Currently I have this as my code:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "popOutNoteExpanded" {
let vc = segue.destination as! ExpandedCellViewController
let cell = sender as! AnnotatedPhotoCell
sourceCell = cell
vc.picture = cell.imageView.image
print("button pressed")
}
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
performSegue(withIdentifier: "popOutNoteExpanded", sender: indexPath)
}
When I click on the collection view cell it's saying that:
has no segue with identifier 'popOutNoteExpanded'.
Not sure how to perform my custom animated transition.
To trigger a segue programmatically, you have to follow steps:
1. Set it up in Storyboard by dragging your desired segue between two view controllers and set its identifier (for example, in your case is "popOutNoteExpanded") in Attributes Inspector section.
2. Call it programmatically
performSegue(withIdentifier: "popOutNoteExpanded", sender: cell)
Please check if you set its identifier correctly or not.
Besides, in your above code, you put an incorrect sender.
In your prepare() method, you use the sender as a UITableViewCell, but you call performSegue() with sender as a IndexPath.
You need a cell by calling:
let cell = tableView.cellForRow(at: indexPath)
And then you can perform a segue:
performSegue(withIdentifier: "popOutNoteExpanded", sender: cell)
Segues are components of storyboard. If you don't have a storyboard, you can't perform segues. But you can use present view controller like this:
let vc = ViewController() //your view controller
self.present(vc, animated: true, completion: nil)
Additional to the correct answer, please be aware that if you are in a navigation stack (say, you are in a Settings page and want to segue to an MyAccountVC) you should use:
let vc = MyAccountVC()
self.navigationController?.pushViewController(vc, animated: true)
If the navigated VC appears with a transparent background during the transition, just set the backgoundColor of it to .white.
In my case I was presenting a TableViewController, without a segue defined, and found it was necessary to instantiate from the storyboard otherwise tableViewCells couldn't be dequeued in cellForRowAt.
let vc = (storyboard.instantiateViewController(withIdentifier: "YourTableViewController") as? YourTableViewController)!
self.navigationController!.pushViewController( vc, animated: true)

Why is my data not passing to my second view controller from a table view?

I'm trying to pass the value to a second view controller
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "headToDetail" {
let indexPath = self.tableView.indexPathForSelectedRow!.row
print("SELECTED INDEX \(indexPath)")
let destVC = segue.destinationViewController as! SecondViewController
print("Segue TEst \(self.toPass)")
destVC.vieSegue = self.toPass
}
I've remove the didSelectRowAtIndexPath and just going to use the Segue but when I tap on a row cell I get:
fatal error: unexpectedly found nil while unwrapping an Optional value
This is a major update to the original code.
the problem is, that you are creating a new ViewController at line
var destination = SecondViewController()
but then the controller simply deallocates because nothing is pointing to it (there is no reference to it and it falls out of scope). You haven't pushed into it, nor you performed any segue.
How to fix it? Basically there are two options.
Storyboard
If you're working with storyboards, you have to create a segue between the first ViewController and SecondViewController. You have to give it an identifier.
Then in didSelectRowAtIndexPath you will write
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
let row = indexPath.row
print(fruits[row])
self.toPass = fruits[row]
performSegueWithIdentifier("YOUR_SEGUE_IDENTIFIER", sender: nil)
}
then in prepareForSegue you'll set the property to the destination SecondViewController.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "YOUR_SEGUE_IDENTIFIER" {
let destVC = segue.destinationViewController as! SecondViewController
destVC.vieSegue = self.toPass!
}
}
Manual Push
The second option is to push the controller manually within the didSelectRowAtIndexPath method
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
let row = indexPath.row
print(fruits[row])
let destination = SecondViewController()
destination.vieSegue = fruits[row]
// if you are using navigation controller
navigationController?.pushViewController(destination, animated: true)
// if you want to present it modally
// presentViewController(destination, animated: true, completion: nil)
}
The second version doesn't force you to create segues.
It's empty because you create a new object...
var destination = SecondViewController()
...pass it your data...
destination.vieSegue = self.toPass!
...and then let it go out of scope without showing it, saving it, or doing anything else useful.

Show ViewController from XIB-file-Button - Swift

is there a way how to segue from a xib-file (custom TableViewCell) to another ViewController in the Main.storyboard.
There's no possibility to drag a segue, like within the main storyboard.
In the cell I've got a button, from where I want to change the view.
How can I solve it?
Thanks!
You can always instantiate a view controller from your Storyboard and present it on button tapped:
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewControllerWithIdentifier("ViewControllerID") as UIViewController
self.presentViewController(vc, animated: true, completion: nil)
Define a segue in StoryBoard and specify the identifier, like detail
Perform segue in talbeView(_, didSelectRowAtIndexPath):
Use self.performSegueWithIdentifier to fire a segue
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
self.performSegueWithIdentifier("detail", sender: nil)
tableView.deselectRowAtIndexPath(indexPath, animated: true)
}

Swift: Segue Error when calling

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