pop to view controller without instantiating a new one - ios

I am using a sidemenu for my app (Swift) and currently when I am taping on the menu I am creating a
new instance of a ViewController.
I have a UICollectionView with some data loaded, then I go to another ViewController from the menu and then when I get back on the previous ViewController the UICollectionView is empty and I have to reload the data.
destViewController = mainStoryboard.instantiateViewControllerWithIdentifier("SettingsViewController") as UIViewController
self.setViewControllers([destViewController], animated: true)
I need to find a way to pop to the View Controller at it's previous state. Any hints?
I have already used self.navigationController.popToViewController, same effect. I think the problem is that I am using instantiate method which create a new instance but I have no idea to check if there is already an instance and how to get back to it.

#IBAction func button_Back(sender: AnyObject)
{
self.navigationController?.popViewControllerAnimated(true)
}

Related

How to pass data between multiple (in my setup 3) ViewControllers?

I want to make an Flashcards App and the behaviour is that on the CoursesVC, the user can add courses and click on them. Then he gets the list with flashcards. There he can add more flashcards. The storage is managed by CoreData. When its clicked on the cell, I pass the data to the flashcards list with prepareForSegue. To add the flashcard, I had the same idea in mind, but it was not possible because the variable from the second view controller wasn't initialised, when prepareForSegue was created. Question: How can I pass a NSManagedObject from the first ViewController to the third ViewController in an appropriate way? (ugly way would be to let the view render before creating prepareForSegue)
The difference to questions like "how to pass data between ViewControllers" is that I have three ViewControllers. It won't work with using prepareForSegue at the first and at the second view controller, because when the prepareForSegue is created, the variable in the second VC is not defined yet, because the view is not initialised yet! Keep in mind that the segue from the second to the third view controller is "Present Modally" as "Page Sheet"!
This is the solution basically: Swift : prepareForSegue with navigation controller
The problem was that the third view controller is embedded as a navigation controller. That is the reason why the prepareForSegue is different.
Solution is to use following prepareForSegue in the second VC:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let navigationVC = segue.destination as? UINavigationController, let myViewController = navigationVC.topViewController as? DViewController {
myViewController.currentCourse = self.currentCourse
}
}

Dismissing Modally Presented VC and Popping Presenting VC

I have a viewcontroller embedded in a navigationcontroller that pushes another viewcontroller onto the stack. This pushed viewcontroller has an embedded viewcontroller that segues/modally presents a final viewcontroller.
On a button click, I am trying to dismiss the final presented viewcontroller and pop the present-ing viewcontroller and return to the initial state.
Thus far, I've been able to get the dismiss going, but popping does not seem to work in the completion handler of the dismiss.
I've tried printing out the hierarchy, i.e. self.presentingViewController, self.navigationController, self.presentingViewController.presentingViewController..., all of which output nil, and am admittedly stuck now on returning to the initial state.
In looking at the view hierarchy, the final presented viewcontroller is beneath a UITransitionView separate from the rest of the stack I had mentioned earlier..
Any thoughts/guidance would be appreciated.
Since you mentioned segues I think unwind segues might help. I built a quick test project and they do indeed function correctly in your scenario.
There is a rather excellent answer in a related SO question What are Unwind segues for and how do you use them?. A summary of the answer for your particular case is: place the following function in your initial view controller:
#IBAction func unwindToThisViewController(segue: UIStoryboardSegue)
{
}
You can then directly 'unwind' to that viewcontroller by using Storyboard Segues directly (as in the referenced answer) or programatically via:
self.performSegue(withIdentifier: "unwindToThisViewController", sender: self)
Again there's a good article entitled Working with Unwind Segues Programmatically in Swift which goes into lots of detail.
Can you try
if let nav = UIApplication.shared.keyWindow?.rootViewController as? UINavigationController {
self.dismiss(animated:true) {
nav.popToRootViewController(animated:true)
}
}

How to pass data between two view controllers of a TabBarContoller where the first view controller is a TableViewController

I am pretty new to iOS application programming in SWIFT. As part of our course, we are building an app which has a TabBarController as the root. The first view controller of the TabBarController is a TableView Controller and a second view controller is a MapViewController. What I want to achieve is to select a row in the TableView which would plot related values in the MapView. Also the map should also be accessible through the tab bar (if the map tab is chosen the previously plotted values will be shown).
Initially I tried to do this using segue. When a cell is selected the segue is called and when the tab is selected it doesn't. But with this method the segue is making a new instance of the MapViewController rather than calling the actual one. So, I removed the segue and tried to pass through custom class as advised in many posts here. But the problem is since the map is loaded in viewWillAppear, the values are not getting properly updated. I would like advice on how to achieve this. When a user selects the cell in the table view the actual MapViewController should be opened with data properly updated.
Thank You
The best way to pass data between two viewcontrollers is to get reference of viewcontroller where you want to pass data.
Suppose in ViewController1 user select some tableview cell then in method
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if let secondVC = self.tabBarController?.viewControllers?[1] as? ViewController2 {
self.tabBarController?.selectedIndex = 1
secondVC.instanceVariable = "updated value"
}
}
This will take to viewController2 and update the instance variable value.Now in viewWillAppear method you can reload mapview.
In this case you'll have to use Delegate-Protocol methods to pass data.
Declare a protocol in your tableviewcontroller
protocol refreshMapProtocol
{
func refresh(data: Dictionary<String, AnyObject>)
}
Declare a delegate property on your tableviewcontroller
var delegate: refreshMapProtocol?
Conform to this protocol in your mapViewController
On didSelectRowAtIndexPath of your tableviewcontroller, call the delegate method
delegate?.refresh()
tabBarController!.selectedIndex = 1
Finally in your mapViewController declare this refresh method to reload the map data.

Pass data with segue without displaying screen

I'm trying to achieve passing of data using segue, but without displaying the screen. There are two view controllers in my project and I will name them FirstViewController and SecondViewController.
On FirstViewController, I am using Show Adaptive Segue to pass data using performSegueWithIdentifier. The prepareForSegue method will be called, and recognising the identifier I've set to display SecondViewController.
However, the problem is I do not want to display SecondViewController. There are other things that my users may want to do before heading for SecondViewController.
I'm new to iOS programming, and I only know passing of data with segue. Please do share with me if there are methods to pass data apart from segue.
EDIT: To further elaborate my question. I'm working with TabBarController, and both View Controllers are accessible on the Tab bar. So when I am on SecondViewController with some data already "segue-ed" over from FirstViewController, I can head back to FirstViewController to add more data. At SecondViewController with UIRefreshControl I need the updated data.
If you do the segue in code you can do what you want in a following way
1) Instantiate view controller from the story board
let storyboard: UIStoryboard = UIStoryboard(name: "SecondViewController", bundle: nil)
let vc = storyboard.instantiateViewControllerWithIdentifier("set_it_in_IB") as! SecondViewController
2) Set the properties you need to set as the view controller is already instantiated
vc.someProperty = "asd"
3) Segue!
viewController.presentViewController(viewControllerToPresent: UIViewController, animated: Bool, completion: (() -> Void)?)
Hope that helps!
From your user's perspective, they would not know whether the data is being updated before they segue or not, so it is probably fine to just pass the data with the prepareFoSegue function.
Otherwise, you could try setting a notification to the viewDidLoad on your FirstViewControllerand SecondViewController by
NSNotificationCenter.defaultCenter().addObserver(self, selector: nil, name: updateSecondVCNotification, object: nil)
and posting the notification from the FirstViewController whenever the data needs to be updated using the
NSNotificationCenter.defaultCenter().postNotificationName(updateSecondVCNotification, object: self)
Then on second view controller set the the selector of the addObserver to a string of the name of whatever function you want it to do like "reloadNewData"
func reloadNewData() {
tableView.reloadData()
}
Does this help? Don't forget to set the updateSecondVCNotification as a global string constant at the top of your FirstViewController. Learn more about NSNotificationCenter at https://www.andrewcbancroft.com/2014/10/08/fundamentals-of-nsnotificationcenter-in-swift/
Woo! Notifications! \(^▽^)/
I'm not sure if that would work for you, but you could simply keep the data in memory of your FirstViewController for the time during which the user is still interacting and giving feedback and once it's done, send all the data to the SecondViewController through the segue.

Unwind Segue not dismissing View Controller with UIModalPresentationCustom

I'm presenting a modal view controller using a custom transition (by setting its modelPresentationStyle to UIModalPresentationCustom, providing a transitioning delegate, and UIViewControllerAnimatedTransitioning object).
In the presented view controller, I have an unwind segue hooked up to a button. The segue fires just fine; the IBAction method in the presenting view controller is called, and so is prepareForSegue in the presented view controller. However, the presented view controller is not dismissed, and the appropriate transitioning delegate method (animationControllerForDismissedController:) is not called.
If, however, I set the presented view controller's modalPresentationStyle to UIModalPresentationFullScreen (the default), the view controller is dismissed properly (this breaks my custom transition, though).
I'm at a complete loss at what to do here. I've looked through Apple's documentation, and didn't notice anything saying that one had to do special things with unwind segues when dealing with custom transitions.
I'm aware that I could call dismissViewControllerAnimated:completion: in the IBAction method of the presenting view controller, but I'd rather use that as a last resort, and get the unwind segue working the way it should (or at least know why it's not working :) ).
Any help would be much appreciated,
Thanks in advance
It seems that if you use UIModalPresentationCustom to present the controller with a custom transition manager, you also need to use a custom transition manager to dismiss it (which makes sense I guess, since you can do all kinds of weird stuff in the animator object and UIKit can't be sure that just dismissing the screen as usual will completely restore the original state - I just wish it told you that explicitly...).
Here's what I've done to fix this in my app:
override segueForUnwindingToViewController in the parent view controller (the one to which you're moving after the dismiss animation) and return an instance of your UIStoryboardSegue, either the one you've used for the original transition or a new separate class
if the unwind segue's target view controller is in a navigation hierarchy, then you need to override that method in the navigation controller instead
in the perform method call dismissViewControllerAnimated
the presented view controller needs to still hold a valid reference to the transitioning delegate, or you'll get an EXC_BAD_ACCESS (see DismissViewControllerAnimated EXC_Bad_ACCESS on true) - so either make it keep the delegate as a strong reference as described in that thread, or assign a new one before calling dismissViewControllerAnimated (it's possible that changing modelPresentationStyle to e.g. full screen before dismissing would work too, but I haven't tried that)
if the dismiss animation needs to do any non-standard things (mine luckily didn't), override animationControllerForDismissedController in the transition manager object and return a proper animator
if the target view controller is in a navigation hierarchy, then you also need to manually pop the navigation stack to the target controller before dismissing the presented screen (i.e. target.navigationController!.popToViewController(target, animated: false))
Complete code sample:
// custom navigation controller
override func segueForUnwindingToViewController(toViewController: UIViewController,
fromViewController: UIViewController,
identifier: String?) -> UIStoryboardSegue {
return CustomSegue(
identifier: identifier,
source: fromViewController,
destination: toViewController
)
}
// presented VC
var customTransitionManager: UIViewControllerTransitioningDelegate?
// custom segue
override func perform() {
let source = sourceViewController as! UIViewController
if let target = destinationViewController as? PresentedViewController {
let transitionManager = TransitionManager()
target.modalPresentationStyle = .Custom
target.customTransitionManager = transitionManager
target.transitioningDelegate = transitionManager
source.presentViewController(target, animated: true, completion: nil)
} else if let target = destinationViewController as? WelcomeViewController {
target.navigationController!.popToViewController(target, animated: false)
target.dismissViewControllerAnimated(true, completion: nil)
} else {
NSLog("Error: segue executed with unexpected view controllers")
}
}
I also met this problem when I need to pass data back from the modalpresented view.
I wandering around Google and here for a couple of hours but I couldn't find an answer that is easy to understand for me. But I did get some hint and here's a work around.
It seems that because it has to pass data back, and the dismissing process from the automatic Unwind is prior before the data passing which prevented the ViewController being dismissed. So I think that I have to manually dismiss it once one more time.
I got some luck here. I didn't notice that it was a child viewcontroller. I just configured it from the storyboard.
And then in the Unwind function, I added to lines to remove the child viewcontroller and the child view. I have no code in the sourceViewController.
Swift 4.1
#IBAction func unwindToVC(sender :UIStoryboardSegue){
if let source = sender.source as? CoreLocationVC{
if source.pinnedCity != nil{
clCity = source.pinnedCity
}
if source.pinnedCountry != nil {
clCountry = source.pinnedCountry
}
if source.pinnedTimeZone != nil {
clTimeZone = source.pinnedTimeZone
}
if source.pinnedLocation != nil {
clLocation = source.pinnedLocation
}
// I added 2 lines here and it just worked
source.view.removeFromSuperview()
source.removeFromParentViewController()
}

Resources