Swiping back to dismiss a child VC while passing data - swift

I have a Navigation Controller that contains 1 Parent VC and 1 Child VC. Data is passed between the two. On the Parent VC, I am passing data to the Child VC via prepareForSegue. On the Child VC back to Parent VC, I am passing data via a custom back button unwind segue.
Passing the data back and forth work great. However, I would like the Child VC to be able to be swiped right to close, while still passing the same information as in the unwind segue.
Is there a way to pass the data back to the parent using a swipe, along with being able to press the back button unwind? The only gesture method I am using is gestureRecognizerShouldBegin, which allows the Child VC to be swiped to the right.
extension ChildVC: UIGestureRecognizerDelegate {
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
}
For reference, here's my unwind:
#IBAction func unwindfromChild(_ sender: UIStoryboardSegue) {
if let CVC = sender.source as? ChildVC {
print("Unwind")
dataReceived() // do stuff with the received data
}
}

You can simply attach a swipeGestureRecognizer to your view, name the unwind segue in Interface Builder, and inside the target function trigger the unwind segue.

I solved this with a slightly different approach. I fully removed the unwind segue. Passing the data through delegation. The delegate method is called in navigationController:willShow.

Related

Does the segue create an instance of the target ViewController? Should I remove them from the stack If I am done with them? If so, how? [duplicate]

iOS 6 and Xcode 4.5 has a new feature referred to as "Unwind Segue":
Unwind segues can allow transitioning to existing instances of scenes in a storyboard
In addition to this brief entry in Xcode 4.5's release notes, UIViewController now seem to have a couple of new methods:
- (BOOL)canPerformUnwindSegueAction:(SEL)action fromViewController:(UIViewController *)fromViewController withSender:(id)sender
- (UIViewController *)viewControllerForUnwindSegueAction:(SEL)action fromViewController:(UIViewController *)fromViewController withSender:(id)sender
- (UIStoryboardSegue *)segueForUnwindingToViewController:(UIViewController *)toViewController fromViewController:(UIViewController *)fromViewController identifier:(NSString *)identifier
How do unwind segues work and what they can be used for?
In a Nutshell
An unwind segue (sometimes called exit segue) can be used to navigate back through push, modal or popover segues (as if you popped the navigation item from the navigation bar, closed the popover or dismissed the modally presented view controller). On top of that you can actually unwind through not only one but a series of push/modal/popover segues, e.g. "go back" multiple steps in your navigation hierarchy with a single unwind action.
When you perform an unwind segue, you need to specify an action, which is an action method of the view controller you want to unwind to.
Objective-C:
- (IBAction)unwindToThisViewController:(UIStoryboardSegue *)unwindSegue
{
}
Swift:
#IBAction func unwindToThisViewController(segue: UIStoryboardSegue) {
}
The name of this action method is used when you create the unwind segue in the storyboard. Furthermore, this method is called just before the unwind segue is performed. You can get the source view controller from the passed UIStoryboardSegue parameter to interact with the view controller that initiated the segue (e.g. to get the property values of a modal view controller). In this respect, the method has a similar function as the prepareForSegue: method of UIViewController.
iOS 8 update: Unwind segues also work with iOS 8's adaptive segues, such as Show and Show Detail.
An Example
Let us have a storyboard with a navigation controller and three child view controllers:
From Green View Controller you can unwind (navigate back) to Red View Controller. From Blue you can unwind to Green or to Red via Green. To enable unwinding you must add the special action methods to Red and Green, e.g. here is the action method in Red:
Objective-C:
#implementation RedViewController
- (IBAction)unwindToRed:(UIStoryboardSegue *)unwindSegue
{
}
#end
Swift:
#IBAction func unwindToRed(segue: UIStoryboardSegue) {
}
After the action method has been added, you can define the unwind segue in the storyboard by control-dragging to the Exit icon. Here we want to unwind to Red from Green when the button is pressed:
You must select the action which is defined in the view controller you want to unwind to:
You can also unwind to Red from Blue (which is "two steps away" in the navigation stack). The key is selecting the correct unwind action.
Before the the unwind segue is performed, the action method is called. In the example I defined an unwind segue to Red from both Green and Blue. We can access the source of the unwind in the action method via the UIStoryboardSegue parameter:
Objective-C:
- (IBAction)unwindToRed:(UIStoryboardSegue *)unwindSegue
{
UIViewController* sourceViewController = unwindSegue.sourceViewController;
if ([sourceViewController isKindOfClass:[BlueViewController class]])
{
NSLog(#"Coming from BLUE!");
}
else if ([sourceViewController isKindOfClass:[GreenViewController class]])
{
NSLog(#"Coming from GREEN!");
}
}
Swift:
#IBAction func unwindToRed(unwindSegue: UIStoryboardSegue) {
if let blueViewController = unwindSegue.sourceViewController as? BlueViewController {
println("Coming from BLUE")
}
else if let redViewController = unwindSegue.sourceViewController as? RedViewController {
println("Coming from RED")
}
}
Unwinding also works through a combination of push/modal segues. E.g. if I added another Yellow view controller with a modal segue, we could unwind from Yellow all the way back to Red in a single step:
Unwinding from Code
When you define an unwind segue by control-dragging something to the Exit symbol of a view controller, a new segue appears in the Document Outline:
Selecting the segue and going to the Attributes Inspector reveals the "Identifier" property. Use this to give a unique identifier to your segue:
After this, the unwind segue can be performed from code just like any other segue:
Objective-C:
[self performSegueWithIdentifier:#"UnwindToRedSegueID" sender:self];
Swift:
performSegueWithIdentifier("UnwindToRedSegueID", sender: self)
As far as how to use unwind segues in StoryBoard...
Step 1)
Go to the code for the view controller that you wish to unwind to and add this:
Objective-C
- (IBAction)unwindToViewControllerNameHere:(UIStoryboardSegue *)segue {
//nothing goes here
}
Be sure to also declare this method in your .h file in Obj-C
Swift
#IBAction func unwindToViewControllerNameHere(segue: UIStoryboardSegue) {
//nothing goes here
}
Step 2)
In storyboard, go to the view that you want to unwind from and simply drag a segue from your button or whatever up to the little orange "EXIT" icon at the top right of your source view.
There should now be an option to connect to "- unwindToViewControllerNameHere"
That's it, your segue will unwind when your button is tapped.
Unwind segues are used to "go back" to some view controller from which, through a number of segues, you got to the "current" view controller.
Imagine you have something a MyNavController with A as its root view controller. Now you use a push segue to B. Now the navigation controller has A and B in its viewControllers array, and B is visible. Now you present C modally.
With unwind segues, you could now unwind "back" from C to B (i.e. dismissing the modally presented view controller), basically "undoing" the modal segue. You could even unwind all the way back to the root view controller A, undoing both the modal segue and the push segue.
Unwind segues make it easy to backtrack. For example, before iOS 6, the best practice for dismissing presented view controllers was to set the presenting view controller as the presented view controller’s delegate, then call your custom delegate method, which then dismisses the presentedViewController. Sound cumbersome and complicated? It was. That’s why unwind segues are nice.
Something that I didn't see mentioned in the other answers here is how you deal with unwinding when you don't know where the initial segue originated, which to me is an even more important use case. For example, say you have a help view controller (H) that you display modally from two different view controllers (A and B):
A → H
B → H
How do you set up the unwind segue so that you go back to the correct view controller? The answer is that you declare an unwind action in A and B with the same name, e.g.:
// put in AViewController.swift and BViewController.swift
#IBAction func unwindFromHelp(sender: UIStoryboardSegue) {
// empty
}
This way, the unwind will find whichever view controller (A or B) initiated the segue and go back to it.
In other words, think of the unwind action as describing where the segue is coming from, rather than where it is going to.
Swift iOS:
Step 1: define this method into your MASTER controller view. in which you want to go back:
//pragma mark - Unwind Seques
#IBAction func goToSideMenu(segue: UIStoryboardSegue) {
println("Called goToSideMenu: unwind action")
}
Step 2: (StoryBoard) Right click on you SLAVE/CHILD EXIT button and Select "goToSideMenu" As action to Connect you Button on which you will click to return back to you MASTER controller view:
step 3: Build and Run ...
For example if you navigate from viewControllerB to viewControllerA then in your viewControllerA below delegate will call and data will share.
#IBAction func unWindSeague (_ sender : UIStoryboardSegue) {
        if sender.source is ViewControllerB  {
            if let _ = sender.source as? ViewControllerB {
                self.textLabel.text = "Came from B = B->A , B exited"
            }
            
        }
}
Unwind Seague Source View Controller ( You Need to connect Exit Button to VC’s exit icon and connect it to unwindseague:
Unwind Seague Completed -> TextLabel of viewControllerA is Changed.

Navigating 1 View Back, Then Forward To Another View (Whit animation looking like a normal push)

I'm trying to navigate form a child viewcontroller back to it's parent viewcontrollerand then from that parent viewcontroller straight to another child viewcontroller all by pressing one button and when this action happens, the animation needs to seem like the child viewcontroller navigated to the other child viewcontroller but when you tap on back, it needs to navigate to the parent viewcontroller.
I thought of 2 ways:
• 1: Navigating from one child viewcontroller to another child viewcontroller and somehow change the action that takes place when the back button is clicked to take you to the parent viewcontroller instead of the child viewcontroller. (If you know how to do this, it will give me the same results, so please please answer,)
• 2: (This is what i currently have) A delegate function in the parent that pops the current viewcontroller so it'll go back to the parent viewcontroller (this parameter is a BOOL), and after popping, navigate to the specified location. This works well! But you can still see the parent viewcontroller in this process. I just need it to look like it went directly from the one child to the other.
Additional Info:
Imagine 3 viewscontroller:
VC1 (which can be seen as the parent)
VC1.1 (a child viewcontroller of VC1)
VC1.2 (also a child viewcontroller of VC1)
So the picture in your head should be similar to the first two layers of a tree diagram.
VC 1
/\
/ \
/ \
VC.1 VC1.2
So I need VC1.1 -> VC1 -> VC1.2 But this action should seem like this happened: VC1.1 -> VC1.2 and when you are on VC1.2 and click back, VC1 should display.
What i did:
#IBAction func onAlreadyRegistered(_ sender: Any) {
delegate?.navigate(to: .SIGNIN, popView: true)
}
func navigate(to: VC.NAVIGATIONS, popView: Bool) {
// UIView.setAnimationsEnabled(false) // Tried disabling the animation as wll
if popView {
navigationController?.popViewController(animated: false)
}
performSegue(withIdentifier: to.abs, sender: self) // abs just returns the rawValue which is the String
UIView.setAnimationsEnabled(true)
}
This will then from the parent pop the current view controller which is the register screen, then perform seque to signin view controller
Thank you in advance!

Saving Changes from an Unwind. Working in conjunction with <Back

I have a problem understanding what I can do with the default Back BarButtonItem and how I can instigate a rewind.
The following shows a simplified layout. I embed a ViewController in a Navigation controller and add a BarButtonItem ("Show") and connect to a second VC. This adds the "Back" button to the second controller as shown below.
In the first VC I will show the user some details, in the second VC greater details will be shown. With the show button and the default back button the user can easily navigate back and forth with expected behavior.
My problem comes in that the user will be allowed to modify the "More Details Here" and I will need to pass that to the first VC after the user unwinds back.
I cannot control-drag from the Back to the Exit icon but I have determined I can do this with a "Save" button on the navigation bar as shown. (First dragging an Navigation Item to the top, then the BarButtonItem) then control-drag to Exit icon.
With the Save button, I can initiate a segue unwind and capture the changes back in my first VC with code like the following.
#IBAction func unwindFromSecondVC(_ sender: UIStoryboardSegue) {
if sender.source is AddCharacterViewController {
if let senderVC = sender.source as? SecondViewController {
details = senderVC.newDetails
}
}
}
This creates a problem when the user makes a change and then clicks the back button thereby loosing the changes.
My preferred solution would be to have the back button initiate a segue and transfer the changes. But this does not appear to be possible. My second solution would be to have the back button notice a transfer has not been made and stop the unwind seque. Neither approach appears possible based on my research.
How should I best handle this problem while giving the user the common Back navigation?
If you want to save any changes the user has made in the second VC, you could create a SecondVCDelegate:
protocol SecondVCDelegate : class {
func detailsDidChange(newDetails: String)
}
In SecondVC, declare a delegate property:
weak var delegate: SecondVCDelegate?
And whenever the details change (the text field's value changed, or whatever event happened), you call
delegate?.detailsDidChange(newDetails: newDetails)
In FirstVC.prepareForSegue, you should set self as the delegate of SecondVC:
if let vc = segue.destination as? SecondVC {
vc.delegate = self
}
// ...
extension FirstVC : SecondVCDelegate {
func detailsDidChange(newDetails: String) {
details = newDetails
}
}
This way, whenever the newDetails change, FirstVC will be notified.
If you just want to notify FirstVC when the user leaves SecondVC, you could instead call the delegate in SecondVC.viewWillDisappear or a method like that.

Programmatically press back button for UIViewController with UITableView iOS swift

I have a UIViewController that implements UITableViewDelegate, UITableViewDataSource and that contains a UITableView as a member variable. When a user click on one of the rows of that table, the app performs a storyboard segue to open the detail view controller. That detail view controller of course has a button in the top left of the screen that is the "back" button to go back up to the UIViewController with the UIViewTable.
So, suppose that I want to programmatically "click" that back button. How exactly would I do that in swift? This is the most recent version of swift (swift 4?) in XCode 10.1.
UPDATE:
So here is how I solved this. As the answers below show, it is possible to use self.navigationController?.popViewController(animated: true) to just return to the previous view controller. What I discovered I also wanted to do, however, was to call a specific method in that view controller so that it executed a certain behavior once it got shown. It turns out that is also possible, but in my case it was a bit tricky, since that prior view controller was actually a UITabBarController. Therefore I had to get the ViewController that I was interested in from the UITabBarController. I did it like this:
let numvc = navigationController!.viewControllers.count
let tvc:UITabBarController = navigationController!.viewControllers[numvc-2] as! UITabBarController
let my_vc: MyCustomVC = tvc.viewControllers![0] as! MyCustomVC
my_vc.some_function()
Here of course MyCustomV is my custom view controller class and some_function() is the method I want to call on that class. Hope this helps someone.
When You run a segue you perform a "pushViewController" method to the next view, so if you want to go back to the previous view programmatically you just have to do is pop the last view like so:
self.navigationController?.popViewController(animated: true)
UPDATE
You just need the if statement if you have multiple segues from that viewController, if not, you can delete and just cast the next view as you wish and set the properties, let the autocomplete write the *prepare(for segue... * method for you, so You don't run into any problems
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "yourSegueName" {
let destinationVC = segue.destination as! CustomViewController
destinationVC.labelExample.text = "Some text I'm sending"
}
}
Are you sure you need to "click" the button?
If all you need is to dismiss details view controller, you can just call navigationController?.popViewController(animated: true)
Or if you want to deal directly with button, you can tell it to send its actions: backButton.sendActions(for: .touchUpInside)
Or if you absolutely need to show button clicking animation, then you will need something like this (you should play and choose suitable delay):
backButton.isHighlighted = true
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.3) {
backButton.isHighlighted = false
backButton.sendActions(for: .touchUpInside)
}

Unwind doesn't work and I have the correct code

I have this segue in a navigation view controller. This has a ViewController called SectionsTableViewController, and this has a bar button item that I linked to exit action and called "close" action segue.
And in my ViewController I have this cose:
class ViewController: UITableViewController {
#IBAction func close(segue: UIStoryboardSegue) {
print("Hey")
}
}
But it doesn't work.
The unwind segue code needs to be in the view controller you're unwinding back to, as opposed to the one with the Stop button. So if your view controllers normally move in this direction:
StartingViewController -> SectionsTableViewController
you want to put the unwind segue in StartingViewController.
Putting the unwind in the wrong view controller and not connecting it via Interface Builder are the only possible ways this can go wrong.
Did you hook up the method to the object in the storyboard (control + drag to Exit)?