I have the following code to go back to the last view controller
self.navigationController?.popViewController(animated: true)
How do I send data back to the last view controller as I do this?
Swift relies a lot on the delegate pattern and this is a good place to use it.
class FirstViewController: UIViewController {
func pushToSecondViewController() {
let second = SecondViewController()
second.firstViewControllerDelegate = self // set value of delegate
navigationController?.pushViewController(second, animated: true)
}
func someDelegateMethod() {
print("great success")
}
}
class SecondViewController: UIViewController {
weak var firstViewControllerDelegate: FirstViewController? // establish a delegate
func goBackToFirstViewController() {
firstViewControllerDelegate?.someDelegateMethod() // call delegate before popping
navigationController?.popViewController(animated: true)
}
}
One common way is to use delegate pattern. Pass the viewController back with some data using the delegate method and dismiss it from “parent” ViewController.
See these links for extra daya about delegates
link1
link2
Related
I have a view controller, lets call it vc1, which passes some data to another (vc2) using prepare for segue, and then calling performSegue.
Is there a way to pass some data back from vc2 to vc1 when vc2 is dismissed by swiping down?
Thanks,
Edit --
Apologies for the lack of information, very new to swift so unsure of the correct question to ask in this situation.
To elaborate, the root of the issue at the moment is that vc2 is not dismissed programatically. ie there is currently no function called, it is simply dismissed by the user swiping down.
Is there some function that I can include to capture this dismissal, and use it to send data back to vc1?
I would prefer not to add any buttons to vc2 if possible.
Apologies again, and I appreciate all the help given already!
Try This
class VCOne: UIViewController {
//Create a shared instance of VCOne
static var sharedInstance:VCOne?
//Let the data to be passed back to VCOne is of type string
var dataToBePassedBack:String?
override func viewDidLoad() {
super.viewDidLoad()
//set the sharedInstance to self
VCOne.sharedInstance = self
}
}
Class VCTwo:UIViewController{
//function in which you are dismissing your current VC you can use the shared
instance to pass the data back
func dismissVC(){
//before dismissing the VCTwo you can set the value for VCOne
VCOne.sharedInstance?.dataToBePassedBack = "data"
}
}
Using Protocol And Delegate You Do or Other Option is NSotificationcenter.
One way yo do it is to create another file that it the controller of everything and then have a delegate that always notifies the view controllers when new changes are available. I will walk it through.
protocol HeadControllerDelegate {
// Create a function that sends out the data to the delegates when it is called
// You can use your custom struct here to pass more data easly
func didReciveNewData(myData: String?)
}
struct HeadController {
// Create a shared instance so that the viewcontroller that conforms to the view as well as when we sends out the data the delegate is correct
static var shared = HeadController()
// Creates the delegate, every view can asign it to
public var delegate: HeadControllerDelegate?
// Add all your values here you want to pass back
var myValue: String? {
// The didSet gets called every time this value is set, and then is it time to call the delegate method
didSet {
// Calls the delegates didReciveMethod to notify the delegates that new data exsists
delegate?.didReciveNewData(myData: myValue)
}
}
}
Now in your viewcontroller class where you would like the data to be avaiable (as you said when you swipe down)
class ViewController: UIViewController {
// Here you create a property of the shared instance
let headController = HeadController.shared
override func viewDidLoad() {
super.viewDidLoad()
// Set yourself as the delegate for the headController delegate to recive data
headController.delegate = self
}
}
extension ViewController: HeadControllerDelegate {
// here will the data be recived
func didReciveNewData(myData: String?) {
// handle the data here, you have now got newData
print(myData)
}
}
In the class where you want to pass data you just do it like this. The beauty of this is that you can have multiple classes or structs that writes to the head controllers data (just make sure you do it thought the shared instance). It is also a good pracice according to we to use the delegate pattern.
class Sender {
var headController = HeadController.shared
func sendData(data: String) {
// Here you change the data of the headcontroller wich will send the data to all the delegates
headController.myValue = data
}
}
Hope this answer helps. If you have any questions please let me know.
UPDATE -- EASIER SOLUTION
Here is an easier solution but is less scalable as the previous one according to me.
In prepareForSegue simply pass over your current viewContorller as a field in the destination view controller. Then when viewDidDissapear in the new view controller you can simply pass back the data. Not to worry, I will show you!
In prepare for Segue
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let dc = segue.destination as? SecondViewController {
dc.viewController = self
}
}
And declare the secondViewContorller as following. The ViewDidDisappear method will be called when the view has dismissed, and therefore can you pass over the data to the view controller you have set before using the prepare for segue method.
class SecondViewController: UIViewController {
var viewController: UIViewController?
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidDisappear(_ animated: Bool) {
(viewController as? ViewController)?.value = 2
}
}
Then you could update the UI using a didSet, which simply will be called when the property is set, which will be done in the view did disappear method.
var value: Int = 0 {
didSet {
print(value)
text?.text = "\(value)"
}
}
Hope this helps!
I want to refresh the whole page controller on back press.
I am navigating the viewcontroller using code.
My Code
let GTC = self.storyboard?.instantiateViewController(withIdentifier: "GoToCart")as! GoToCart
self.navigationController?.pushViewController(GTC, animated: true)
Using viewWillAppear to reload your UI. As you use navigationController?.pushViewController, the view will be retained and stored in stack.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Reload the UI
}
viewWillAppear(_:)
viewWillAppear is called the first time the view is displayed as well as when the view is displayed again, so it can be called multiple times during the life of the view controller object. It’s called when the view is about to appear as a result of the user tapping the back button, when the view controller’s tab is selected in a tab bar controller etc. Make sure to call super.viewWillAppear() at some point in the implementation. You can refresh your UI in this method
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Reload the UI
}
A better approach is to use protocol
Create protocol from where you want to pop back(GoToCart)
Create delegate variable in GoToCart
Extend GoToCart protocol in MainViewController
Give reference to GoToCart of MainViewController when
navigate
Define delegate Method in MainViewController
Then you can call delegate method from GoToCart
Example
In GoToCart: Write code below..
protocol GoCartControllerDelegate
{
func childViewControllerResponse(parameter)
}
class GoToCart:UIViewController
{
var delegate: ChildViewControllerDelegate?
....
}
Then in mainViewController implement the protocol function end extend to the protocol
class MainViewController:UIViewController,GoCartControllerDelegate
{
// Define Delegate Method
func childViewControllerResponse(parameter)
{
//...here update what you want to update according to the situation
}
}
2 Important thing
when navigating to the gocart controller code like this
let GTC = self.storyboard?.instantiateViewController(withIdentifier: "GoToCart")as! GoToCart
GTC.delegate = self
self.navigationController?.pushViewController(GTC, animated: true)
and when popping from gocartViewController
code like this
self.navigationController?.popViewController(animated:true)
self.delegate?.childViewControllerResponse(parameter)
I'm new to swift and my problem right now is I want to pass the value from RoutePreviewController page back to display on ViewController page after clicking on the Navigate Button. As below picture, you can see that the RoutePreviewController is not directly segue from the ViewController.
This is my story board of the swift project on xcode
However, I tried using protocol and delegate in the code.
Here are codes that I have add to the ViewController (MainPage)
protocol HandleSelectedPath{
func selectedPathDraw(selectedRoute:[(line:Int?,node:Int,time:Double)])
}
in viewDidLoad of ViewController
let routePreviewPage = storyboard!.instantiateViewControllerWithIdentifier("RoutePreviewController") as! RoutePreviewController
routePreviewPage.handleSelectedPathDelegate = self
and an extension outside the ViewController class
extension ViewController : HandleSelectedPath{
func selectedPathDraw(selectedRoute:[(line:Int?,node:Int,time:Double)]){
print("7687980")
selectedPath = selectedRoute
routeDraw()
}
}
And in RoutePreviewController, I have delegation of the protocol.
var handleSelectedPathDelegate: HandleSelectedPath? = nil
and the Navigate button action
#IBAction func navigateButtonAction(sender: AnyObject) {
handleSelectedPathDelegate?.selectedPathDraw(previewPath)
dismissViewControllerAnimated(true, completion: nil)
definesPresentationContext = true
}
As a result, after clicking the Navigate button, it does send me back to the ViewController page but the selectedPathDraw function of the protocol is not performed. I also tried printing some random string but nothing came up as an output.
The reference for your RoutePreviewController according to your code above only exist inside your viewDidLoad, you have to set as property instead like this:
var routePreviewPage: RoutePreviewController!
Always it's a good practice implement your delegate as a weak reference to avoid retain-cycles, so the correct way of implement your delegate and protocol should be as the following code:
protocol HandleSelectedPath: class {
func selectedPathDraw(selectedRoute:[(line:Int?,node:Int,time:Double)])
}
RoutePreviewController:
weak var handleSelectedPathDelegate: HandleSelectedPath?
#IBAction func navigateButtonAction(sender: AnyObject) {
handleSelectedPathDelegate?.selectedPathDraw(previewPath)
dismissViewControllerAnimated(true, completion: nil)
definesPresentationContext = true
}
viewDidLoad of ViewController:
routePreviewPage = storyboard!.instantiateViewControllerWithIdentifier("RoutePreviewController") as! RoutePreviewController
routePreviewPage.handleSelectedPathDelegate = self
I hope this help you.
Short explanation.
I have a ContainerViewController that I'm pushing to the navigationStack.
The ContainerViewController has 2 child ViewControllers. A SlidePanelViewController (a slide-out menu) and a CenterViewController (the content)
I have a button in my menu to "sign Out". When this button is clicked I want to push ContainerViewController (and it's 2 childViewControllers) to my LandingPageViewController.
Here's the function I am trying to call:
func signOut() {
println("signOut")
// Set up the landing page as the main viewcontroller again.
let mainTableViewController = LandingPageVC()
mainTableViewController.navigationItem.setHidesBackButton(true, animated: false)
mainTableViewController.skipView = false
self.navigationController!.pushViewController(mainTableViewController, animated: true)
// Disable menu access
menuEnabled = false
// change status bar style back to default (black)
UIApplication.sharedApplication().statusBarStyle = UIStatusBarStyle.Default
}
At first I tried putting this in my SlidePanelViewController. That didn't work. So I put it where I'm assuming it belongs in the ContainerViewController.
However when I click my signOutButton in my menu. I'm presented with the error:
fatal error: unexpectedly found nil while unwrapping an Optional value
When looking into the error. This is the line causing it:
self.navigationController!.pushViewController(mainTableViewController, animated: true)
After the error I checked that the function works, by adding a UINavigationBarButtonItem that called the function (in my ContainerViewController). It did exactly what I wanted.
However when I call this function from my Menu (again my menu is a childViewController of the ContainerViewController). It does not work.
I'm attempting to call it like so:
ContainerViewController().signOut()
I also tried adding a Delegate to my SidePanelViewController like this:
Before the class:
#objc protocol SidePanelViewControllerDelegate {
optional func needsSignOut(sender: SidePanelViewController)
optional func toggleLeftPanel()
optional func collapseSidePanels()
}
in viewDidLoad():
// Make sure your delegate is weak because if a ContainerViewController owns
// a reference to a SidePanelViewController and the container view controller
// is its delegate, you'll end up with a strong reference cycle!
weak var delegate: SidePanelViewControllerDelegate?
in my tap gesture function:
func signOutTapGesture() {
println("signOutTapGesture")
selectView(signOutView)
delegate?.needsSignOut?(self)
println(delegate)
}
before my ContainerViewController class:
var leftViewController: SidePanelViewController?
my ContainerViewController class:
class ContainerViewController: UIViewController, CenterViewControllerDelegate, SidePanelViewControllerDelegate, UIGestureRecognizerDelegate {
in my ContainerViewController's viewDidLoad()
leftViewController?.delegate = self
And I changed the signOut function in the ContainerViewController class to this:
func needsSignOut(sender: SidePanelViewController) {
println("needsSignOut called")
self.signOut()
}
However using the delegate like above, doesn't seem to do anything either.
Any help as to How I can successfully push my LandingPageVC from the menu would be greatly appreciated! (I'm not using storyboards)
You're attempting to call signOut with ContainerViewController().signOut(). This will create a new ContainerViewController and because you haven't pushed it onto the navigation controller's stack, navigationController is nil. Try just calling self.signOut(). (I'm assuming signOut in a method of ContainerViewController)
Update - delegates
Your delegate property should go in SidePanelViewController. I'll give you and example of how to implement it:
SidePanelViewController:
(Note - the protocol doesn't have to go here but I think it keeps things organised)
#objc protocol SidePanelViewControllerDelegate {
optional func needsSignOut(sender: SidePanelViewController)
}
class SidePanelViewController: UIViewController {
// Make sure your delegate is weak because if a ContainerViewController owns
// a reference to a SidePanelViewController and the container view controller
// is its delegate, you'll end up with a strong reference cycle!
weak var delegate: SidePanelViewControllerDelegate?
// Called when the UIButton is pressed.
func myButtonWasPressed() {
delegate?.needsSignOut?(self)
}
}
ContainerViewController:
class ContainerViewController: UIViewController {
var sidePanel: SidePanelViewController!
// Setup the side panel...
override func viewDidLoad() {
super.viewDidLoad()
sidePanel.delegate = self
}
func signOut() {
// Sign out stuff here.
}
}
// The ContainerViewController needs to conform to the SidePanelViewControllerDelegate
// protocol if we want the delegate to work. (This could have gone in the initial
// class declaration.)
extension ContainerViewController : SidePanelViewControllerDelegate {
func needsSignOut(sender: SidePanelViewController) {
self.signOut()
}
}
Hope that helps.
The problem seems to be that navigationController is nil and you're trying to force unwrap it (as indicated by your error).
One problem I discussed in my other answer.
Another problem may be you haven't added a navigation controller. To do this you need to:
If you're using Storyboards
You need to make sure you've embedded your UINavigationController. After that, when you use navigationController it won't be nil and you'll be able to push your view controller.
When you're on your storyboard:
Also, if you're using storyboards, have you considered using segues to move around instead of calling presentViewController? I've found it makes everything much easier.
If you're not using Storyboards
Have a look at this post: Programatically creating UINavigationController in iOS
I have a first tableViewController which opens up a second tableViewcontroller upon clicking a cell. The second view controller is presented modally (Show Detail segue) and is dismissed with:
self.dismissViewControllerAnimated(true, completion: {})
At this point, the second view controller slides away and reveals the first view controller underneath it. I would then like to reload the first view controller. I understand that this may require use of delegate functions, but not sure exactly how to implement it
Swift 5:
You can access the presenting ViewController (presentingViewController) property and use it to reload the table view when the view will disappear.
class: FirstViewController {
var tableView: UITableView
present(SecondViewController(), animated: true, completion: nil)
}
In your second view controller, you can in the viewWillDisappear method, add the following code:
class SecondViewController {
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if let firstVC = presentingViewController as? FirstViewController {
DispatchQueue.main.async {
firstVC.tableView.reloadData()
}
}
}
}
When you dismiss the SecondViewController, the tableview of the FirstViewController will reload.
I solved it a bit differently since I don't want that dependancy.
And this approach is intended when you present a controller modally, since the presenting controller wont reload when you dismiss the presented.
Anyway solution!
Instead you make a Singleton (mediator)
protocol ModalTransitionListener {
func popoverDismissed()
}
class ModalTransitionMediator {
/* Singleton */
class var instance: ModalTransitionMediator {
struct Static {
static let instance: ModalTransitionMediator = ModalTransitionMediator()
}
return Static.instance
}
private var listener: ModalTransitionListener?
private init() {
}
func setListener(listener: ModalTransitionListener) {
self.listener = listener
}
func sendPopoverDismissed(modelChanged: Bool) {
listener?.popoverDismissed()
}
}
Have you Presenting controller implement the protocol like this:
class PresentingController: ModalTransitionListener {
//other code
func viewDidLoad() {
ModalTransitionMediator.instance.setListener(self)
}
//required delegate func
func popoverDismissed() {
self.navigationController?.dismissViewControllerAnimated(true, completion: nil)
yourTableViev.reloadData() (if you use tableview)
}
}
and finally in your PresentedViewController in your viewDid/WillDisappear func or custom func add:
ModalTransitionMediator.instance.sendPopoverDismissed(true)
You can simply reaload your data in viewDidAppear:, but that might cause the table to be refreshed unnecessarily in some cases.
A more flexible solution is to use protocols as you have correctly guessed.
Let's say the class name of your first tableViewController is Table1VC and the second one is Table2VC. You should define a protocol called Table2Delegate that will contain a single method such as table2WillDismissed.
protocol Table2Delegate {
func table2WillDismissed()
}
Then you should make your Table1VC instance conform to this protocol and reload your table within your implementation of the delegate method.
Of course in order for this to work, you should add a property to Table2VC that will hold the delegate:
weak var del: Table2Delegate?
and set its value to your Table1VC instance.
After you have set your delegate, just add a call to the delegate method right before calling the dismissViewControllerAnimated in your Table2VC instance.
del?.table2WillDismissed()
self.dismissViewControllerAnimated(true, completion: {})
This will give you precise control over when the table will get reloaded.