Lets say I have 3 ViewControllers - A, B and C. A shows B, B shows C. Then I want to go back to A without going through (visually) B (like home button). My problem currently is that when I unwind to A, for short period of time B is shown (viewWillAppear and etc. methods are called). How can I resolve this?
Note1: The above example is highly simplified compared to my real app navigation tree and for me using NavigationController as container to all A,B and C is not possible (or at least not desirable). Few of the reasons are in the middle of the transition there are complex embed segues and different custom transition(almost all animated transitions are absolutely different) across all views.
Note2: I found some pseudo solution with defining custom segue and using it as a Custom Unwind Segue. That is not desirable too because I want to use my already done transition animators.
Any help is highly appreciated.
Please note that this is an amended answer - in response to the first comment below.
I have a possible solution which seems to work. But it may not be appropriate. I am not certain and offer it for consideration only.
The 3 view controllers are set up as described in the question (the second is red in order for me to notice whether it is visible during the unwind).
The unwind segue is created in the third view controller (C) by dragging to the Exit. Before doing this the required actions have to be added to the first and second view controllers.
Below is the code for the 3 view controllers. My fix - I am using a a global boolean called unwindCheck as a flag which is set true before the unwind but is otherwise false. If it is true then self.view.hidden = true in viewWillAppear for the second view controller. ie hidden during the unwind.
Please note that I have also added a second button on the third view controller - it calls the same unwind segue - the identifier property of which is set to "UnwindToFirstSegue". The second button is not a necessary part of this possible solution.
class FirstViewController: UIViewController {
#IBAction func unwindToFirstViewController(segue: UIStoryboardSegue) {
}
override func viewDidLayoutSubviews() {
unwindCheck = false
}
}
class SecondViewController: UIViewController {
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
if unwindCheck == true {
self.view.hidden = true
}
}
#IBAction func unwindToSecondViewController(segue: UIStoryboardSegue) {
}
}
class ThirdViewController: UIViewController {
#IBAction func backToA(sender: AnyObject) {
performSegueWithIdentifier("UnwindToFirstSegue", sender: self)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let identifier = segue.identifier{
switch identifier {
case "UnwindToFirstSegue":
unwindCheck = true
default:
break
}
}
}
}
The unwind segue's identifier can be set by selecting it in the Document Outline and then going to the Attributes inspector.
Related
I have three view controllers in a Storyboard.
The user always starts out in View Controller A.
Most of the time, the user will transition to View Controller B, then to view controller C, then continuing on back and forth between B and C.
However, occasionally, a user will jump straight from A to C. If this is the case, I would like to be able to unwind back to B once the user is done with C, but I can't figure out a way to insert B into the heirarchy for C to unwind back to it. Using trickery to segue to B and then automatically call a segue from B to C is visually jarring, even if I turn animations off.
The only other option I can see for myself is designing B and C so that they can unwind to each other but that seems like it's going to require a lot of duplicate code and I'd rather avoid it if I can.
I am not sure if there is a non-hackish way of solving this.
If NavigationController was used we can pop and push views to the stack from any child view controllers. That would work reasonably well.
With modal presentation its more difficult. I would set A as Delegate of C. After the user goes from A to C and wants to go "back". I would call the delegate method on A. A would segue to B. Then I would dismiss C. Something like that will work:
protocol CDelegate {
func presentB()
}
class VCA: CDelegate {
override func viewDidLoad() {
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "VC" {
if let _vc = segue.destinationViewController as? VC {
_vc.delegate = self
}
}
}
func presentB {
self.present(VCB, animated: false)
}
}
class VC {
weak var delegate: CDelegate!
func dismiss() {
delegate.presentB()
self.dismiss(animated: true, completion: nil)
}
}
I have not tested this, so this might still cause jittering animations.
I have built an app that centers around a pretty tableview that I've built.
I have setup a secondary view controller as a menu that is presented modally, and I would like to filter the tableview by selecting one of the buttons on the secondary view controller.
For example, each cell has a City assigned to it. In the menu, I'd like to be able to click a city and filter the tableview to only show cells with that city.
I have too much code to paste, and I'm confident I can solve this problem with a smidge of direction.
Thanks for your help!
You can do this with an unwind segue from your second view controller's buttons back to your table view controller.
In your table view controller, say,
func unwindToTableView(_ segue: UIStoryboardSegue) {
switch segue.identifier {
case "FilterNames":
filterByName()
etc…
}
}
or you could have different unwind funcs for each filter…
func unwindAndFilterName(_ segue: UIStoryboardSegue) {
filterByName()
}
etc
To hook up an unwind segue, just add the method to your table view controller, then in your storyboard, drag from the button on the second view controller to it's Exit icon. The segue func should appear in the list
To do this you would like to have separate DataSource layer in the application to have move clear code.I will write small example for you how it is possible to implement.
For example you have class DataSource. With information that you are showing. In my case it is cities. Now when I would like to do sorting I will call sortAlphabetically() and reload tableview. It is quite simple way and you're solution really depends on how you are working with UITableView.
class DataSource {
var cities = ["Lviv", "Lutsk", "Kiev", "Rivne"]
func sortAlphabetically() {
cities = cities.sorted { $0 < $1 }
//reload tableview hear
}
}
Best way to do so is to use delegates, add a protocol to your filter view controller and a delegate function in tableView that filters datasource for tableView. Don't forget to assign your table view controller as the delegate before you segue to the filter viewcontroller
Best way to do so is to use delegates, add a protocol to your filter view controller and a delegate function in tableView that filters datasource for tableView. Don't forget to assign your table view controller as the delegate before you segue to the filter viewcontroller
Before your filterViewController
protocol FilterViewControllerDelegate {
func tableViewCriteria(criteria: AnyObject)
}
In your filterViewController:
var delegate: FilterViewControllerDelegate?
In the class declaration of tableViewController, add FilterViewControllerDelegate
class MyTableViewController: UITableViewController, FilterViewControllerDelegate{
Don't forget to set the FilterViewControllerDelegate to self before you segue to the filterView:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showFilter" {
if let fvc = segue.destination as? FilterViewController{
fvc.delegate = self
}
}
}
Implement the func in tableView that will update tableView data source based on the chosen criteria:
//In myTableViewController
func tableViewCriteria(criteria: AnyObject) {
//update tableView data source base on criteria here
return
}
Finally, call the delegate function from filterView whenever you need to return to tableview:
self.delegate?.tableViewCriteria(criteria: foo)
Voila! :)
Salutations,
This is a follow up from my previous post (Incorrect data passed on first click of button). It was answered, but the updated edit of course occurred.
My inquiry is as follows, at the moment I have two IBActions funcs which empty, lack any sort of code, and their only purpose for existence is that they are connected to my other view controller, and as such if I remove them I have no way to differentiate between which segue should be used (My understanding is that although we can create segues between two view controllers, I do not think anything more than one would make sense (as in, how to decide which one to go with). I digress.
I tried using the following in my code before:
self.performSegue(withIdentifier: "slideTwo", sender: self);
This works well, however, it does cause double segueing. As such, I settled for the following:
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
{
let passedPic = segue.destination as! ViewControllerTwo;
if(segue.identifier == "slideOne") {
passedPic.picsChosen = sLS1;
} else {
passedPic.picsChosen = sLS2;
}
}
This works well, does what I need, but I do then have the problem of two empty IBActions:
#IBAction func slideShowOne() {
}
#IBAction func slideShowTwo() {
//self.performSegue(withIdentifier: "slideTwo", sender: self);
}
This is where I thought perhaps I could try to create a getter/setter (which swift makes far more complicated than it honestly should be). This is where I am lost.
The code looks as follows:
var picsChosen : [String] {
set(newData) {
for index in 0..<newData.count {
print("Index is: ", newData[index]);
}
self._picsChosen = newData;
} get {
return self._picsChosen;
}
}
Now, my issue is two fold. First, what is the correct way to actually access and pass values into my variable. I tried doing: myClass: ViewControllerTwo! = ViewControllerTwo();
and then use myClass.picsChosen = myArray; However, although it does appear that the data was passed successfully into my VC2, since when I loop through (inside the setter) I do see the values successfully being displayed, when I try to access picsChosen outside of that I get an index out of bounds error, in other words, the value was never associated with picsChosen. The current way I have it works well, but I want to know what is the correct way to use getters/setters, and why what I have did not work.
Cheers!
This works well, does what I need, but I do then have the problem of
two empty IBActions:
You don't need these empty #IBActions. They aren't doing anything for you. The segues are wired from the buttons in the Storyboard, so you don't need the #IBActions. If you delete the code, you also need to remove the connection from the Storyboard or your app will crash when it tries to call the removed #IBActions. To remove, control-click on the buttons in the Storyboard, and then click on the little x next to Touch Up Inside to remove the connection. Then you can delete #IBAction in your code.
if I remove them I have no way to differentiate between which segue
should be used
Not true. You have two different segues wired from two different buttons. The segue's identifiers are how you differentiate between the two segues.
I tried doing: myClass: ViewControllerTwo! = ViewControllerTwo();
When using segues, the segue creates the destination viewController for you.
This doesn't work because you are creating an entirely new ViewControllerTwo here. You are passing the values to this new instance, but this isn't the ViewControllerTwo that the Storyboard segue created for you.
When using segues, you should pass the data in prepare(for:sender:), just like you showed above. Get the destination ViewController and fill in the data you want to pass.
And now for the question you didn't ask:
How could I have done this using the #IBActions to trigger the
segue?
Remove the segues that are wired from your buttons.
Wire the segue from viewController1 to viewController2. Click on the segue arrow in the Storyboard and assign the identifier slideShow.
Assign unique tags to your two buttons. You can set this in Interface Builder. Give the first button tag 1, and the second button tag 2.
Your buttons can share the same #IBAction:
#IBAction func goToSlideShow(button: UIButton) {
self.performSegue(withIdentifier: "slideShow", sender: button)
}
In prepare(for:sender:):
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
{
let passedPic = segue.destination as! ViewControllerTwo
if let button = sender as? UIButton {
if button.tag == 1 {
passedPic.picsChosen = sLS1
} else if button.tag == 2 {
passedPic.picsChosen = sLS2
}
}
}
Currently working on my first IOS application. I have a purchase button, on success this currently sets a test button on the same view controller to hidden. Code is as follows
Decleration
#IBOutlet weak var Test: UIButton!
hide button on successful purchase
Test.isHidden = true
Now this works on my Test button, which is sat in the PurchaseViewController,class is the MasterViewController.Swift. (Purchase button that initiates this method is also in the same view controller)
PlanViewController also has a button, and class is also linked to MasterViewController.Swift. This has a separate button that i wish to hide on success of the purchase button.
When I utilise the same code as above for the button, it crashes, is their a limitation on manipulating other view controllers while you are not in it? I would have thought this worked given that they both have the Masterviewcontroller.swift as the class
Thanks
Although sometimes possible, it's generally not a good idea to directly manipulate one view controller's view from another view controller, as you are trying to do. Here is how I would do what you are trying to do.
First, set a segue identifier between your two view controllers by clicking on the segue in the storyboard and going to the attributes inspector. I suggest goToMasterViewController
In both MasterViewController.swift and PurchaseViewController.swift declare a variable var buttonHidden = false
In PurchaseViewController.swift add the following code, which will be called just before your segue to MasterViewController is performed:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if(segue.identifier == "goToMasterViewController") {
let destinationController = segue.destination as! MasterViewController
destinationController.buttonHidden = buttonHidden
}
}
When you hide the button in PurchaseViewController, also set buttonHidden = true
And finally in MasterViewController.swift:
override func viewWillAppear(_ animated: Bool) {
testButton.isHidden = buttonHidden
}
I have two UICollectionViewControllers and the first one uses a push segue to get to the second one. The problem I'm having is passing information back to the first controller when the back button (the one that gets added automagically) is pressed in the second controller. I've tried using the segueForUnwindingToViewController, and canPerformUnwindSegueAction override functions, but no dice. I need to be able to access both view controllers so I can set some variables. Any ideas?
Here is an example with two view controllers. Let's say that the names of the two view controllers and ViewController and SecondViewController. Let's also say that there is an unwind segue from the SecondViewController to the ViewController. We will pass data from the SecondViewController to the ViewController. First, let's set the identifier of this segue by opening the document outline and selecting the unwind segue. Then open up the attributes inspector and set the identifier to "unwind".
SecondViewController Code:
class SecondViewController: UIViewController
{
override func prepareForSegue(segue: UIStoryBoardSegue, sender: AnyObject?) {
if let identifier = segue.identifier {
if let destination = segue.destinationViewController as? ViewController {
if identifier == "unwind" {
destination.string = "We Just Passed Data"
}
}
}
}
}
ViewController Code:
class ViewController: UIViewController {
var string = "The String That Will Be We Just Passed Data"
#IBAction func unwindSegue(segue: UIStoryBoardSegue) {
}
}
It sounds like you are trying to intercept the back button, there are many posts for this on SO, here are two:
Setting action for back button in navigation controller
Trying to handle "back" navigation button action in iOS
In practice, it is more clear to return state in closures (more modern), or delegates.