Problems with Navigation Controller (back button and prepareForSegue) - ios

I promise that I'm completely new to Xcode and Swift, so I know I am making silly mistakes but I don't know where. This is part of my iOS app storyboard:
where the segue between the first table view and the second navigation controller is called myTaskDetailSegue and its type is Show (e.g. Push). Now I have some problems:
Neither in the first table view controller nor in the second the back button is showed and I don't know why. Many people told me that navigation bar and back button are as default in navigation controllers but they did not appear
In the class of the first table view controller here is the method prepareForSegue()
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if (segue.identifier == "myTaskDetailSegue" ) {
let indexPath = self.tableView.indexPathForSelectedRow()
let task = self.taskCollection[indexPath!.row] as Task
let controller = (segue.destinationViewController as! UINavigationController).topViewController as! DetailsMyTasksViewController
controller.detailItem = task
println("segue mostra task \(task.id)")
controller.navigationItem.leftItemsSupplementBackButton = true
}
}
so you can read that the segue identifier is correct but when a row is tapped nothing happens and the second table view controller is not showed.
I don't really know what I am missing because of my inexperience.
Here is the complete storyboard:

You don't need two UINavigationController's to what you want to achieve. Is important to note that every time you push(with a segue or manually) a new UIViewController it's added to the navigation stack.
According to Apple:
Pushing a view controller displays its view in the navigation interface and updates the navigation controls accordingly. You typically push a view controller in response to user actions in the current view controller—for example, in response to the user tapping a row in a table.
So you can remove the second UINavigationController in your Storyboard and make the segue directly to your DetailsMyTaskViewController and update your prepareForSegue like in the following way:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if (segue.identifier == "myTaskDetailSegue" ) {
let indexPath = self.tableView.indexPathForSelectedRow()
let task = self.taskCollection[indexPath!.row] as Task
let controller = segue.destinationViewController as! DetailsMyTasksViewController
controller.detailItem = task
println("segue mostra task \(task.id)")
}
}
And your back button should appear by default as you said before. Nevertheless I strongly recommend you read the following two guides :
UINavigationViewController
View Controller Programming Guide for iOS
For a better understanding of the navigation stack, etc.
I hope this help you.

Related

SplitViewController template - Which mechanism is telling the device to perform the segue into the Detail Controller instead of Master Controller

In the Apple default Master/Detail template, there is the following code handling the showDetail segue:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showDetail" {
if let indexPath = tableView.indexPathForSelectedRow {
let object = fetchedResultsController.object(at: indexPath)
let controller = (segue.destination as! UINavigationController).topViewController as! DetailViewController
controller.detailItem = object
controller.navigationItem.leftBarButtonItem = splitViewController?.displayModeButtonItem
controller.navigationItem.leftItemsSupplementBackButton = true
}
}
}
And the storyboard indicating that this segue is type of Show Detail (e.g. Replace)
My question is the following:
Why in large screen mode (aka: on an iPhone 6+, 7+ or an iPad), the MasterViewController's navigation controller isn't pushing its embedded view as a normal segue would do (which part of the code is making it "working")?
Given the code I'm reading, I would expect on an iPad to see the Master Controller to be changed to the Detailed Controller, but the correct behaviour is happening (Master isn't being replaced and Detail is being updated) and I can't understand why.
The magic here is the Show Detail segue in the storyboard. The code has little to with it. It just passes data to DetailViewController. You can even delete the code if you want. The new VC will still be pushed on the detail view.
The "Kind" of the segue here is very special - Show Detail:
This is what causes the new VC to show on the detail view. Try changing the kind to something else like Show and the detail view will just be pushed onto the master.

Cannot update embedded child VC from non-parent VC

Up to date Xcode/Swift/iOS.
I have a Master VC (called StartVC) that contains a Child VC (called TopBarVC) via and embedded segue. The Child VC contains a button, that, when pressed, modally segues to a 3rd VC (called CategoryPickerOverlayVC) (the view in this VC serves as a dropdown box for picking a category).
#IBAction func CategoryFilterButtonPressed(_ sender: Any) {
performSegue(withIdentifier: "toCategoryPickerOverlay", sender: self)
}
When an option is selected from the dropdown box, which itself is composed of three buttons, the title of the selected button should be used to replace the title text of the button in the Child VC.
In the Master VC, I use prepareforsegue to store a reference to the Child VC in a variable - "topBarReference" - at the moment when the embed segue takes place.
var topBarReference: TopBarVC?
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "TopBarPane"{
topBarReference = segue.destination as? TopBarVC
}
}
Then, in the 3rd VC, when I click on one of the button options in the dropdown box, the button title is sent via a prepareforsegue to update the button in the Child VC (via "topBarReference").
if segue.identifier == "unwindToStartVC"{
let vc = segue.destination as! StartVC
vc.topBarReference?.filterButtonText = ((sender as! UIButton).titleLabel?.text)!
}
The 3rd VC then unwind segues back to the Master VC. I should add that when the button in the Child VC is changed, a variable (filterButtonText) in Child VC is first set with the title text and then this variable is then used to set the button title text via the viewDidAppear method of Child VC.
When using the debugger, I also note that viewDidAppear in the Master VC does not seem to execute after unwinding (I placed a diagnostic print-to-console in viewDidAppear and nothing prints after the unwind segue). I realise this would explain the button not getting updated but I've got no idea why viewDidAppear does not run.
I have also tried using a delegate protocol and instantiateViewController(withString:) to no avail. All of the methods produce the same result, which is that the button in the Child VC does not get updated. No errors are shown. Everything else happens as expected.
Any ideas as to what I am doing wrong?
Do you mean something like this?
If so, the solution I used was very simple: the third VC uses prepareForSegue to set a property of the embedded VC, and the embedded VC picks up that property in the unwind method.
In my implementation, the three view controllers are called ViewController, ChildViewController, and ThirdViewController. This is the entire code (everything else is configured in the storyboard):
class ChildViewController: UIViewController {
#IBOutlet weak var theButton: UIButton!
var buttonTitle : String?
#IBAction func unwind(_:UIStoryboardSegue) {
self.theButton.setTitle(self.buttonTitle, for: .normal)
}
}
class ThirdViewController: UIViewController {
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
(segue.destination as! ChildViewController).buttonTitle = (sender as! UIButton).currentTitle
}
}
Ok, so I have found that my original code works fine bar one line in the prepareforsegue of the Child VC. If I change that prepareforsegue from:
if segue.identifier == "unwindToStartVC"{
let vc = segue.destination as! StartVC
vc.topBarReference?.CategoryFilterButton.titleLabel?.text = ((sender as! UIButton).titleLabel?.text)!
}
to this:
if segue.identifier == "unwindToStartVC"{
let vc = segue.destination as! StartVC
vc.topBarReference?.CategoryFilterButton.setTitle((sender as! UIButton).titleLabel?.text, for: .normal)
}
it works just fine. The use of the .setTitle method seems to make a difference although I am not sure why.
Thanks to Matt for giving me the idea to change it to that. Matt's method did work when i tried it, although, as I am unwinding to the Master VC and not the Child VC, I had to edit the code accordingly, in terms of where I placed it.
As my little "discovery" equates to the smallest change to the original code, I'll mark this as the answer.
Thanks to all for taking the time to respond!

Second Master-Detail Scheme Does Not Load Correctly in SplitViewController

I have a master-detail application setup in a split view controller that is basically Swift standard code. With the initial master-detail scheme it works as expected.
I want to load a Second Master with it's Second Detail in the left and right panes of the split view controller. With one major exception it is working as expected. The exception is that the second detail view only covers the second master in the left pane. The second detail does not load in the right pane.
Here's the issue graphically. The first master-detail scheme:
Then when the second master is launched:
Then when a cell is tapped in the second master:
I load the second master from a MVC1 button with a simple storyboard segue, though I've done it programmatically as a delegate. Nothing has worked to show master 2 on the left and detail 2 on the right. In all cases, I have tried the segue as Show and as Show Detail. I can't discern a difference in the app behavior.
For the segue from MVC2 to DVC2:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showKeywordDetail" {
if let indexPath = self.tableView.indexPathForSelectedRow {
let object = self.fetchedResultsController.objectAtIndexPath(indexPath) as! Keyword
let controller = segue.destinationViewController as! KeywordDetailViewController
controller.detailItem = object
//controller.navigationItem.leftBarButtonItem = self.splitViewController?.displayModeButtonItem()
controller.navigationItem.leftItemsSupplementBackButton = false
controller.sentBy = "showKeywordDetail"
}//if let indexPath
} else if segue.identifier == "addKeyword" {
//bunch more code for other cases
}
Any guidance would be appreciated.
The key to this solution seems to be creating a separate "non-connected" master detail scheme.
On the original master, place a button with an action:
#IBAction func doCodeKeywordPush(sender : AnyObject) {
self.navigationController!.pushViewController(self.storyboard!.instantiateViewControllerWithIdentifier("KeywordTableViewController") as! UITableViewController, animated: true)
}//doCodeKeywordPush
Then make the second master-detail scheme a stand alone set in the storyboard. Embed the second master view controller in a navigation controller. Create a segue from the second master tableViewCell to the second detail view controller. There should be no segue or connection between the button and the second group.
Finally, configure the second master to second detail segue as any other setup.

Can't pass data between View Controllers using condition (Swift 2)

I've been trying to pass data to another View Controller. But as I have two Bar Buttons that lead to two different View Controllers I have to set a condition. But when I try to pass the data it won't work. Here's the code I've been using:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if (segue.identifier == "ViewController") {
let destViewController = segue.destinationViewController as! ViewController
destViewController.chips = chips
destViewController.blind = blind
}
}
I have my destination Storyboard id to ViewController but when ViewController view opens the data isn't passed through.
EDIT:
In both View Controllers chips and blind are declared as:
var chips = 0
var blind = 0
Before I added a back button the data was passed correctly. But then the application crashed every time I clicked "Back" so I decided to add a condition which doesn't seem to work.
I'm very new to Xcode/Swift, but I believe the string in your if (segue.identifier == "ViewController") is the problem. Instead of "ViewController" you need to use the identifier of your segue. Select your segue in Main.storyboard, click on the Attributes Inspector and give it a name in the Identifier field. That's the string you want to use.

No back button on segue in Swift 2

I just ported my project over to Swift 2, and everything is working great - except that even the most simple segues have no back button. Here is the prepare for segue function that I am using:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
if segue.identifier == "showExercise" {
if let nav = segue.destinationViewController as? UINavigationController {
if let exercisesController = nav.topViewController as? ExercisesController {
let cell = sender as! WorkoutCell
if let workout = cell.name!.text {
exercisesController.exercises = Workouts[workout]!
exercisesController.navigationItem.title = workout
}
}
}
}
}
Before, the back button to the parent segue used to automatically populate. Now, all I get is the title in the child navigation vc
Are you using show or show detail segue? It seems like you are using a modal segue. Destination view controller for show or show segue is usually the second view controller itself, and not embedded in another UINavigationController.
If your destination view controller for the show segue is really a UINavigationController, the new navigation controller's navigation bar settings may override the old one (the navigation controller of the source view controller). Try not embedding your destination view controller in another UINavigationController.

Resources