Swift – Using popViewController and passing data to the ViewController you're returning to - swift

I have an optional bool variable called showSettings on my first view controller which is called ViewController, and I'm popping from SecondViewController back to ViewController.
Before I pop, I want to set the bool to true. Seems wrong to instantiate another view controller since ViewController is in memory.
What's the best way to do this? I'm not using storyboards, if that's important for your answer.
Thanks for your help

So I figured it out, based mostly from this post – http://makeapppie.com/2014/09/15/swift-swift-programmatic-navigation-view-controllers-in-swift/
In SecondViewController, above the class declaration, add this code:
protocol SecondVCDelegate {
func didFinishSecondVC(controller: SecondViewController)
}
Then inside of SecondViewContoller add a class variable:
var delegate: MeditationVCDelegate! = nil
Then inside of your function that your button targets, add this:
self.navigationController?.popViewControllerAnimated(true)
delegate.didFinishSecondVC(self)
What we're doing here is doing the pop in SecondViewController, and not passing any data, but since we've defined a protocol, we're going to use that in ViewController to handle the data.
So next, in ViewController, add the protocol you defined in SecondViewController to the list of classes ViewController inherits from:
class ViewController: UIViewController, SecondVCDelegate { ... your code... }
You'll need to add the function we defined in the new protocol in order to make the compiler happy. Inside of ViewController's class, add this:
func didFinishSecondVC(controller: SecondViewController) {
self.myBoolVar = true
controller.navigationController?.popViewControllerAnimated(true)
}
In SecondViewController where we're calling didFinishSecondVC, we're calling this method inside of the ViewController class, the controller we're popping to. It's similar to if we wrote this code inside of SecondViewController but we've written it inside of ViewController and we're using a delegate to manage the messaging between the two.
Finally, in ViewController, in the function we're targeting to push to SecondViewController, add this code:
let secondVC = secondViewController()
secondVC.delegate = self
self.navigationController?.pushViewController(secondVC, animated: true)
That's it! You should be all set to pass code between two view controllers without using storyboards!

_ = self.navigationController?.popViewController(animated: true)
let previousViewController = self.navigationController?.viewControllers.last as! PreviousViewController
previousViewController.PropertyOrMethod

I came across this while looking for a way to do it. Since I use Storyboards more often, I found that I can get the array of controllers in the navigation stack, get the one just before the current one that's on top, check to see if it's my delegate, and if so, cast it as the delegate, set my methods, then pop myself from the stack. Although the code is in ObjC, it should be easily translatable to swift:
// we need to get the previous view controller
NSArray *array = self.navigationController.viewControllers;
if ( array.count > 1) {
UIViewController *controller = [array objectAtIndex:(array.count - 2)];
if ( [controller conformsToProtocol:#protocol(GenreSelectionDelegate)]) {
id<GenreSelectionDelegate> genreDelegate = (id<GenreSelectionDelegate>)controller;
[genreDelegate setGenre:_selectedGenre];
}
[self.navigationController popViewControllerAnimated:YES];
}

Expanding upon the answer by Abdul Baseer Khan:
For cases where the current view controller may have been loaded by different types of previous view controller, we can use the safer as? call instead of as!, which will return nil if the controller is not what we were looking for:
let previousVC = self.navigationController?.viewControllers.last as? AnExampleController
previousVC?.doSomething()
Although, you would need to repeat that for each different view controller that could load the current view controller.
So, you may want to, instead, implement a protocol to be assigned to all the possible previous view controllers:
protocol PreviousController: UIViewController {
func doSomething()
}
class AnExampleController: UIViewController, PreviousController {
// ...
func doSomething() {}
}
class AnotherController: UIViewController, PreviousController {
// ...
func doSomething() {}
}
class CurrentController: UIViewController {
// ...
func goBack() {
let previousVC = self.navigationController?.viewControllers.last as? PreviousController
previousVC?.doSomething()
}
}

Related

How to access a referencing outlet from a different view controller?

I am new to Swift and Xcode. I am building an Financial Expense ios app.
In my first view controller, I created a referencing outlet for a label called expenseNum.
In my second view controller, I have a function for a button called Add Expense. When it is clicked, I need it to update the expenseNum variable with the amount of the expense.
What is the best way to go about this? I had created an object of the first view controller class and accessed it like "firstviewcontroller.expenseNum" but this will create a new instance of the class and I need it to be all the same instance so it can continuously add to the same variable. Thanks for the help!
You need a delegate
protocol SendManager {
func send(str:String)
}
In first
class FirstVc:UIViewcontroller , SendManager {
func send(str:string) {
self.expenseNum.text = str
}
}
when you present SecondVc
let sec = SecondVc()
sec.delegate = self
// present
In second
class SecondVc:UIViewcontroller {
var delegate:SendManager?
#IBAction func btnClicked(_ sender:UIButton) {
delegate?.send(str:"value")
}
}
// setting delegate
in viewDidLoad of SecondVc
if let first = self.tabBarController.viewControllers[0] as? FirstVc {
self.delegate = first
}
There are several ways you can pass data from ViewController2 to another ViewController1
The best way here is Protocol Delegates
Please follow below steps to pass data
In Your SecondViewController from where you want to send data back declare a protocol at the top of class declaration
protocol SendDataBack: class {
func sendDataFromSecondVCtoFirstVC(myValue: String)
}
Now in the class , declare a object of your protocol in same ViewController
weak var myDelegateObj: SendDataBack?
And now in your Add Expense button action just call the delegate method
myDelegateObj?.sendDataFromSecondVCtoFirstVC(myValue: yourValue)
Now go to your first ViewController
the place from where you have pushed/present to SecondViewController you must have taken the object of SecondVC to push to push from first
if let secondVC = (UIStoryboard.init(name: "Main", bundle: nil)).instantiateViewController(withIdentifier: "secondVCID") as? SecondViewController {
vc?.myDelegateObj = self
self.navigationController?.pushViewController(secondVC, animated: true)
**OR**
self.present(secondVC, animated: true, completion: nil)
}
now in your FirstViewController make an extension of FirstViewVC
extension FirstViewController: SendDataBack {
func sendDataFromSecondVCtoFirstVC(myValue: String) {
}
}
I think you can make a variable in your properties in second ViewController (before viewDidLoad method)
var delegate: FirstViewController? = nil
and use from the properties of the first view controller anywhere of the second view controller.
delegate!.mainTableView.alpha=1.0
//for example access to a tableView in first view controller
The simplest way to achieve this is to use a public var. Add a new Swift file to your project, call it Globals. Declare the public variable in Globals.swift like so:
public var theValue: Int = 0
Set its required value in the first ViewController, and you'll find you can read it in the second with ease.

how to use protocol and delegate methods for viewcontroller that are not directly connected?

Im new to swift and iOS development.I have a doubt regarding protocol and delegate methods.
I have a 4 vc's say vc1,vc2,vc3,vc4.And i'm navigating from vc1->vc2->vc3-vc4->vc1. That is from vc4, im poping using navigation controller back to vc1.
i have a protocol and methods in it like
protocol myProtocol{
func myFunc()
}
In vc4, im making a delegate as,
var delegate:myProtocol?
and im using it in a button action as
if let delegate = self.delegate{
delegate.myFunc()
}
and also pop vc4 back to vc1.
Now in VC1,im extending myProtocol as
class vc1:myProtocol{
override func viewDidLoad()
{
let vcProtocol = vc4()
vc4.delegate = self
}
func myFunc()
{
print("executing this")
}
}
But its not working. Can i do like this?
How can i connect these to classes with delegate and protocol.Please help me
viewDidLoad is only executed once if the controller is not destroyed.
Try calling in viewWillAppear for example.
Also, you do not need to change the delegate of vc4, just set the delegate to vc1 again (in viewWillAppear).
Have you tried Notification?
In your VC1 ViewController ViewDidLoad method:
NotificationCenter.default.addObserver(self, selector: #selector(myFunc, name: NSNotification.Name(rawValue: "aNotificationName"), object: nil)
In your VC4 when the myFunc is needed:
NotificationCenter.default.post(name: Notification.Name("aNotificationName"), object: nil)
You can get the viewController from the navigation stack which has the same type as your first viewController and then assign it as the delegate object in your vc4.
For instance:
In vc4's viewDidLoad, you may do this
for viewController in self.navigationController?.viewControllers{
if viewController.isKindOfClass(YourVC1ControllerType){
self.delegate = viewController
}
}
I assume that your vc1,vc2,vc3, etc have their own ViewController custom classes.
Another way of implementing this would be to get the first controller from the self.navigationController?.viewControllers array and setting it as the delegate object in vc4.
//In viewDidLoad of vc4
delegate = self.navigationController?.viewControllers.first as! HomeController
or,
//In viewDidLoad of vc4
delegate = self.navigationController?.viewControllers.[indexOfTheController] as! HomeController

Swift: Passing a class method as a function parameter (instead of a generic function)

I'm trying to call a function on a root view controller from a popover view controller. The function below works but it seems there should be a better way:
func doSomethingInRoot() {
if let appDelegate = UIApplication.shared.delegate as? AppDelegate,
let currentRoot = appDelegate.window!.rootViewController as? RootViewController {
currentRoot.somefunction()
}
}
In the above code, doSomethingInRoot would be contained in the popover, and someFunction would be a function of RootViewController.
What I'm looking for is a way to pass someFunction to another function that contains the if let above, however, the problem is currentRoot is only assigned as a RootViewController within the above (i.e., doSomethingInRoot doesn't know that it's being passed a method of RootViewController instead of a generic function).
In writing this question, I realized that I could do the following, which feels pretty clean, but I'm still curious whether I can pass a method to a specific class as parameter.
func doSomethingInRoot() {
if let root = getRoot() {
root.someFunction()
}
}
func getRoot() -> RootViewController? {
if let appDelegate = UIApplication.shared.delegate as? AppDelegate,
let currentRoot = appDelegate.window!.rootViewController as? RootViewController {
return currentRoot
} else {
return nil
}
}
In your popover view controller, you could add a property like this to hold the reference to someFunction
var rootsSomeFunction: (()->Void)!
Then assign the reference when creating the popover VC whether it's in prepareForSegue, or after you instantiate if from storyboard...etc. I'd also move getRoot() to the App Delegate.
popoverVC.rootsSomeFunction = appDelegate.getRoot().someFunction().self
Finally, in your popover view controller, you can call it by the property like this:
self.rootsSomeFunction()

(re)-Pass data after click on backbutton

I'm trying to pass data from a SecondViewController to my FirstViewController when I click on my back button (UINaviagtionController).
For pass my data from FirstViewController to the SecondViewController I do this:
if segue.identifier == "showSecondVC" {
let vc = segue.destination as! SecondViewController
vc.rows = rows[pathForAVC]
vc.lap = lapTime[pathForAVC]
vc.indexPath = pathForAVC
}
But I have no idea how to pass data from SecondViewController to the FirstViewController and I really don't understand topics about it on Stack Overflow.
I want to transfer it when I click here:
Thanks.
You can use delegate pattern for that. You can grab the back button press event like this and update the data
override func viewWillDisappear(_ animated: Bool) {
if self.isMovingFromParentViewController {
self.delegate.updateData( data)
}
}
For more information on delegates you can go through this.
Actually things depend on your requirement, if you want data to be updated in first view controller as soon as it is updated in second view controller, you would need to call delegate as soon as the data is updated. But as in the question you have mentioned that you want it to be updated on back button only, above is the place to do it.
Another way would be to have Datasource as singleton so that it is available to all the view controllers and the changes are reflected in all view controllers. But create singleton if absolutely necessary, because these nasty guys hang around for entire time your application is running.
You should have a custom protocol such as:
public protocol SendDataDelegate: class {
func sendData(_ dataArray:[String])
}
Here I suppose you want to send a single array back to FirstViewController
Then make your first view controller to conform to the custom protocol, such as:
class FirstViewController: UIViewController, SendDataDelegate
In the second view controller, create a delegate a variable for that protocol, such as:
weak var delegate: SendDataDelegate?
and then you catch the back action and inside it you call your custom protocol function, such as:
self.delegate?.sendData(arrayToSend)
In the first viewController, in the prepare for segue function just set the delegate like
vc.delegate = self

Swift, Pass data back from popover to view controller

I have sorted the original array in popover controller. Now I want to send that array back to the original view controller for tableview and map view.
Below is my code
If propertyNameSrt == false
{
if ascSorting == false
{
properties.sort(sorterForbuildingAsc)
}
else
{
properties.sort(sorterForbuildingDesc)
}
}
My array is properties which includes custom object.
How can pass this to my original view controller?
Thanks in advance,
Dhaval.
You can use delegate(protocol) methods to send back data to previous view controller.
IN CURRENT VC:
protocol MyProtocol: class
{
func sendArrayToPreviousVC(myArray:[AnyObject])
}
Make a var in your class.
weak var mDelegate:MyProtocol?
Now call the protocol method when you pop the view controller, with your "properties" array as parameter.
mDelegate?.sendArrayToPreviousVC(properties)
IN PREVIOUS VC:
In your previous VC, set the mDelegate property to self, when you push the current VC.
currentVC.mDelegate = self
//PUSH VC
Now implement the protocol method in your previous VC.
func sendArrayToPreviousVC(myArray:[AnyObject]) {
//DO YOUR THING
}