How to trigger some code when back from "Over Current Content" ViewController? - ios

There are two ViewController in my app, ViewController and ViewController2
In ViewController, a button set Present Modally segue to "ViewController2"
And ViewController override viewWillAppear
override func viewWillAppear(_ animated: Bool) {
super.viewDidAppear(animated)
print("will appear")
}
In ViewController2, a button to go back
#IBAction func close(_ sender: Any) {
self.dismiss(animated: true, completion: nil)
}
Now it still can trigger viewWillAppear then I go back to ViewController from ViewController2
If I change ViewController2's presentation from Full Screen to Over Current Context, viewWillAppear will not be triggered
How can I trigger some code when go back?

You can do it without giving up storyboard segues, but you nevertheless had to setup will/did Disappear handler in ViewCOntroller2:
class ViewController: UIViewController {
...
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destination = segue.destination as? ViewController2 {
(segue.destination as? ViewController2).onViewWillDisappear = {
//Your code
}
}
}
}
class ViewController2: UIViewController {
var onViewWillDisappear: (()->())?
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
onViewWillDisappear?()
}
...
}

There are several ways to handle this operation. Here is one, which I used to use.
// ViewController1
class ViewController1: UIViewController {
#IBAction func presentOverCurrentContext(button: Button) {
let vc2 = // instantiate ViewController2
vc2.modalPresentationStyle = .overFullScreen
vc2.presentingVC = self // use this variable 'presentingVC' to connect both view controllers
self.present(vc2, animated: true)
}
}
// ViewController2
class ViewController2: UIViewController {
var presentingVC: UIViewController? // use this variable to connect both view controllers
#IBAction func close(button: Button) {
// handle operation here
presentingVC?.viewWillAppear(true)
self.dismiss(animated: true, completion: {
// or here
// presentingVC?.viewWillAppear(true)
})
}
}
You can also use, your own method to reload view/viewcontroller, but viewWillAppear is common accessible method for all view controllers (as part of super class life cycle) hence you may not need to specify custom type of view controller for presentingVC

While the answers so far provided do work I think it's a good idea to show how to do it using a protocol and delegate as that's a clean implementation which then also allows for further functionality to be added with minimal effort.
So set up a protocol like this:
protocol SecondViewControllerProtocol: class {
func closed(controller: SecondViewController)
}
Setup the second view controller like this:
class SecondViewController {
public weak var delegate: SecondViewControllerProtocol?
#IBAction func close(_ sender: Any) {
self.dismiss(animated: true, completion: nil)
self.delegate?.close(controller: self)
}
}
Setup the first view controller like this:
class FirstViewController: SecondViewControllerProtocol {
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "SecondViewControllerID",
let secondViewController = segue.destination as? SecondViewController {
secondViewController.delegate = self
}
}
func closed(controller: SecondViewController) {
// Any code you want to execute when the second view controller is dismissed
}
}
Implementing it like this does what the original request was and allows for extra methods to be put in the protocol so that the FirstViewController can respond to other actions in the SecondViewController.
Note:
You might want to move the delegate method call into the closure of the dismiss handler so that you know the method is not called until the SecondViewController is actually gone (in case you try to present another view which would fail). If that's the case you could do this:
#IBAction func close(_ sender: Any) {
self.dismiss(animated: true) {
self.delegate?.close(controller: self)
}
}
In fact you could have a will and did methods and call them like this:
#IBAction func close(_ sender: Any) {
self.delegate?.willClose(controller: self)
self.dismiss(animated: true) {
self.delegate?.didClose(controller: self)
}
}
Which would allow you to do something immediately while the second controller is animating away and then know when it has actually gone.

Best/Clean way to handle this scenario to use call back handler.
Example Code
typealias CloseActionHandler = ()-> Void
class TestController: UIViewController {
var closeActionHandler: CloseActionHandler?
func close(_ handler:#escaping CloseActionHandler) {
self.closeActionHandler = handler
}
#IBAction func closeButtonTapped(_ sender: Any) {
self.dismiss(animated: true, completion: nil)
self.closeActionHandler?()
}
}
class ViewController: UIViewController {
func loadTestController(viewController: TestController) {
viewController.close {
//will be called when user will tap on close button
}
}
}

Related

Can't pass datas back from UIView Popover Dismiss

I have set a "Show as popover" segue between a UIView (A) and another UIView (B) (embed in a Navigation Controller) activated on a button's clic.
i am trying to pass datas back from (B) to (A) when i dismiss it (i want to keep the popover animation on both ways).
I have tried many methods i found mostly here, on Stackoverflow, but as of now i never successfully retrieved my data on (A).
I tried Delegates and protocols as well as other simpler methods. The last in date is the following one:
In (A), i just try to print the variable that should be storing the datas in ViewWillAppear :
class SearchBarsController: UIViewController, UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate {
var testValue:String = ""
override func viewWillAppear(_ animated: Bool) {
print(testValue) // print is empty
super.viewWillAppear(animated)
}
}
In (B), i dismiss the popover and try to send the datas back on a button clic :
class SearchFilterViewController: UIViewController {
#IBAction func DismissPopoverOnClic(_ sender: Any) {
if let navController = presentingViewController as? UINavigationController {
let presenter = navController.topViewController as! SearchBarsController
presenter.testValue = "Test"
print("success") //never called
}
self.dismiss(animated: true, completion: nil)
}
}
on (B) i'd like to set up some filter that i'd use on (A) to present search results in a tableview. But actually the testValue's value is always blank.
oky so you can do it using unwind segue here is sample project :
sample projecct
process :
Add this method to SearchBarsController below viewWillAppear
#IBAction func unWindFromFilterViewController(_ sender: UIStoryboardSegue) {
}
Than go to Storyboard and go to SearchFilterViewController and then cntrl + Drag from DismissPopoverOnClic to top of the exit button then select unWindFromFilterViewController .
Than this the SearchFilterViewController write this method for passing data
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destVC = segue.destination as? ViewController {
destVC.testValue = "Test"
}
}
You will get your desired data back . thanks
When passing back data to a viewController, the most efficient way to implement it using delegate
protocol SearchFilterViewControllerDelegate {
func setTextValue(string : String)
}
class SearchFilterViewController: UIViewController {
var delegate : SearchFilterViewControllerDelegate?
#IBAction func DismissPopoverOnClic(_ sender: Any) {
self.dismiss(animated: true, completion: nil)
delegate?.setTextValue(string : "Test Value")
}
}
class SearchBarsController: UIViewController, UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate {
var filterViewController : SearchFilterViewController?
func popup() {
// your pop up code and init filterViewController
filterViewController.delegate = self **//without this line, the delegate will be nil, no nothing will happen.**
}
}
extension SearchBarsController : SearchFilterViewControllerDelegate {
func setTextValue(string : String) {
print(string)
}
}

How to call a first view controller function from second view controller on a button tap in swift ?

I have a requirement where I have to call a first view controller function from second view controller on a button tap.
class FirstViewController: UIViewController {
#IBAction func firstButtonPressed(_ sender: Any) {
// Doing ABC
}
#IBAction func showSecondVC_ sender: Any) {
// showingSecondVC
}
}
class secondViewController: UIViewController {
#IBAction func SecondButtonPressed(_ sender: Any)
// Dismiss second vc & call First View controller method so that it does ABC.
}
My first question is can we initiate First VC IBAction directly from second VC ? Is it possible ?
I am thinking to do following
class FirstViewController: UIViewController {
#IBAction func firstButtonPressed(_ sender: Any) {
// call DoABC
}
func DoABC {
// Doing ABC
}
}
class secondViewController: UIViewController {
#IBAction func SecondButtonPressed(_ sender: Any)
// Dismiss second vc
// Call Firstvc.DoABC ?? How to do this ??
}
How to call the first vc method from the second vc ??
You have a few options here:
Split out the logic, call the same code from each view controller
Use a closure callback
Use the delegate pattern as a method of calling back
Option 1 - Split out the logic:
class FirstViewController: UIViewController {
let abcPerformer = ABCPerformer()
#IBAction func firstButtonPressed(_ sender: Any) {
abcPerformer.doABC()
}
#IBAction func showSecondVC_ sender: Any) {
// showingSecondVC
}
}
class SecondViewController: UIViewController {
let abcPerformer = ABCPerformer()
#IBAction func SecondButtonPressed(_ sender: Any) {
// Dismiss second vc & call First View controller method so that it does ABC.
abcPerformer.doABC()
}
}
struct ABCPerformer {
func doABC() {
// do ABC
}
}
Option 2 - Create a callback:
class FirstViewController: UIViewController {
#IBAction func firstButtonPressed(_ sender: Any) {
doABC()
}
#IBAction func showSecondVC_ sender: Any) {
// showingSecondVC
secondVC.doABC = doABC
}
func doABC() {
// do ABC
}
}
class SecondViewController: UIViewController {
var doABC: (() -> Void)?
#IBAction func SecondButtonPressed(_ sender: Any) {
// Dismiss second vc & call First View controller method so that it does ABC.
doABC?()
}
}
Option 3 - Use a delegate:
protocol ABCProtocol {
func doABC()
}
class FirstViewController: UIViewController, ABCProtocol {
#IBAction func firstButtonPressed(_ sender: Any) {
doABC()
}
#IBAction func showSecondVC_ sender: Any) {
// showingSecondVC
secondVC.delegate = self
}
func doABC() {
// do ABC
}
}
class SecondViewController: UIViewController {
weak var delegate: ABCProtocol?
#IBAction func SecondButtonPressed(_ sender: Any) {
// Dismiss second vc & call First View controller method so that it does ABC.
delegate?.doABC()
}
}
There is probably more options too, but these should give you enough choice to make a decision
Create a protocol, say, SecondViewControllerDelegate.
Add a method signature to that protocol, something like secondViewControllerDidPressButton.
Add a var to secondViewController: var delegate: SecondViewControllerDelegate
Update firstViewController to implement that protocol.
In prepareForSegue of firstViewController, assign firstViewController as the delegate for the secondViewController that is about to be presented.
Update secondViewController to call self.delegate.secondViewControllerDidPressButton when the button is pressed.
You can Use custom delegate for that Like below and add function "presentPage" wherever you want to call.
protocol MainDelegate {
func presentPage(page : Int)
}
To present your second view controller from First, You can use push or present transition.
#IBAction func firstButtonPressed(_ sender: Any) {
// call DoABC
//presenting VC
let secondVC = SecondViewController() //change this to your class name
self.presentViewController(secondVC, animated: true, completion: nil)
//for push :
navigationController?.pushViewController(SecondViewController, animated: true)
}
You can use pop/dismiss VC accordingly in your second view to get back first view.
class secondViewController: UIViewController {
#IBAction func SecondButtonPressed(_ sender: Any)
// Dismiss second vc // Call Firstvc.DoABC ?? How to do this ??
//if you used Present in first step then use dismiss
self.dismissViewControllerAnimated(false, completion: nil)
//if you used push in first step then use pop
self.navigationController?.popViewController(animated: true) }

How to call a function defined in main ViewController from secondary view controller in swift without using delegates?

How can I call the playAgain() function which is defined in ViewController, from DisplayScoreController ?
ViewController.swift
class ViewController: UIViewController {
func playAgain() {
print("Play Again")
}
}
DisplayScoreController.swift
class DisplayScoreController: UIViewController {
#IBAction func playAgain(_ sender: Any) {
dismiss(animated: true, completion: nil)
// I want to call playAgain() in ViewController
}
You can do this by passing a closure to the DisplayScoreController that dismisses that controller and calls playAgain().
In the example below, I set up that closure in prepare(for:sender:). If you aren't launching DisplayScoreController with a segue, you could assign this closure just after you instantiate the DisplayScoreController and before you present it.
In my example, I trigger the calling of the closure when the user has pressed the Done button in DisplayScoreController. You could put the call to self.goPlayAgain?() anywhere you want to trigger that action.
class ViewController: UIViewController {
func playAgain() {
print("Play Again")
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showScore" {
if let dvc = segue.destination as? DisplayScoreController {
// assign closure to goPlayAgain property
// of destination view controller to dismiss
// the destination and call playAgain()
dvc.goPlayAgain = {
self.dismiss(animated: true, completion: nil)
self.playAgain()
}
}
}
}
}
class DisplayScoreController: UIViewController {
// property to hold closure which dismisses this
// view controller and calls playAgain() in
// ViewController
var goPlayAgain: (() -> ())?
// Time to return to ViewController and call playAgain()
#IBAction func done(_ sender: UIButton) {
self.goPlayAgain?()
}
}
You don't have to call it a delegate, but that's the idea. You need a pointer to the object that has the playAgain() function. Defining a protocol and adding an instance var to your DisplayScoreController that points to an object that conforms to that protocol enables DisplayScoreController to send the playAgain() message to some other object.
You can call the object fred if you want to:
protocol PlayAgainProtocol {
func playAgain()
}
class DisplayScoreController: UIViewController {
weak var fred: PlayAgainProtocol?
#IBAction func playAgain(_ sender: Any) {
dismiss(animated: true, completion: nil)
fred?.playAgain()
}
}
The variable is named fred rather than delegate. Does that mean you're not using delegates? No, not really, but you aren't using the term delegate...

How to call a function in the first controller after dismissing the second controller

I have two UIViewController, when I click a button, it goes from the first view controller to the second one. And before that, I animated a UIView to move to another place. After dismissing the second View Controller, I want to move the UIView in the first view controller back to where it originally was. However, when I call a function from the second View Controller to animate the UIview in the first view controller after dismissing the second one, It could not get the UIView's properties, and cannot do anything with it. I think because the first UIViewController is not loaded yet. Is that the problem? And How should I solve this?
There are two solutions you can either use swift closures
class FirstViewController: UIViewController {
#IBAction func start(_ sender: Any) {
guard let secondController = self.storyboard?.instantiateViewController(withIdentifier: "SecondController") as? SecondController else { return }
secondController.callbackClosure = { [weak self] in
print("Do your stuff")
}
self.navigationController?.pushViewController(secondController, animated: true)
}
}
//----------------------------
class SecondController: UIViewController {
var callbackClosure: ((Void) -> Void)?
override func viewWillDisappear(_ animated: Bool) {
callbackClosure?()
}
}
or you can use protocols
class FirstViewController: UIViewController {
#IBAction func start(_ sender: Any) {
guard let secondController = self.storyboard?.instantiateViewController(withIdentifier: "SecondController") as? SecondController else { return }
secondController.delegate = self
self.navigationController?.pushViewController(secondController, animated: true)
}
}
extension ViewController : ViewControllerSecDelegate {
func didBackButtonPressed(){
print("Do your stuff")
}
}
//--------------------------
protocol SecondControllerDelegate : NSObjectProtocol {
func didBackButtonPressed()
}
class SecondController: UIViewController {
weak var delegate: SecondControllerDelegate?
override func viewWillDisappear(_ animated: Bool) {
delegate?.didBackButtonPressed()
}
}
You can try to use a closure. Something like this:
class FirstViewController: UIViewController {
#IBOutlet weak var nextControllerButton: UIButton!
private let animatableView: UIView = UIView()
private func methodsForSomeAnimation() {
/*
perform some animation with 'animatableView'
*/
}
#IBAction func nextControllerButtonAction() {
// you can choose any other way to initialize controller :)
let storyboard = UIStoryboard(name: "Main", bundle: nil)
guard let secondController = storyboard.instantiateViewController(withIdentifier: "SecondViewController") as? SecondViewController else { return }
secondController.callbackClosure = { [weak self] in
self?.methodsForSomeAnimation()
}
present(secondController, animated: true, completion: nil)
}
}
class SecondViewController: UIViewController {
#IBOutlet weak var dismissButton: UIButton!
var callbackClosure: ((Void) -> Void)?
#IBAction func dismissButtonAction() {
callbackClosure?()
dismiss(animated: true, completion: nil)
/*
or you call 'callbackClosure' in dismiss completion
dismiss(animated: true) { [weak self] in
self?.callbackClosure?()
}
*/
}
}
When you present your second view controller you can pass an instance of the first view controller.
The second VC could hold an instance of the first VC like such:
weak var firstViewController: NameOfController?
then when your presenting the second VC make sure you set the value so it's not nil like so:
firstViewController = self
After you've done this you'll be able to access that viewControllers functions.
iOS 11.x Swift 4.0
In calling VC you put this code ...
private struct Constants {
static let ScannerViewController = "Scan VC"
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == Constants.ScannerViewController {
let svc = destination as? ScannerViewController
svc?.firstViewController = self
}
}
Where you have named the segue in my case "Scan VC", this is what it looks like in Xcode panel.
Now in scan VC we got this just under the class declaration
weak var firstViewController: HiddingViewController?
Now later in your code, when your ready to return I simply set my concerned variables in my firstViewController like this ...
self.firstViewController?.globalUUID = code
Which I have setup in the HiddingViewController like this ...
var globalUUID: String? {
didSet {
startScanning()
}
}
So basically when I close the scanning VC I set the variable globalUUID which in term starts the scanning method here.
When you are saying it could not get the UIView's properties it's because you put it as private ? Why you don't replace your UIView in the first controller when it disappears before to go to your secondViewController. I think it's a case where you have to clean up your view controller state before to go further to your second view controller.
Check IOS lifecycle methods : viewWillDisappear or viewDidDisappear through Apple documentation and just do your animation in one of these methods.
Very simple solution actually... Just put your animation in the viewDidAppear method. This method is called every time the view loads.
class firstViewController: UIViewController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// insert animation here to run when FirstViewController appears...
}
}

Passing data from modal segue to parent

I want to pass data (e.g. set var) from modal segue to parent, how can I do that?
I’m using that code to exit from modal segue:
#IBAction func doneClicked(sender: AnyObject) {
self.dismissViewControllerAnimated(true, completion: nil)
}
I can’t use segue.destinationViewController here to pass data as i’m used to do on push segues.
Create protocol on Modal ViewController
protocol ModalViewControllerDelegate
{
func sendValue(var value : NSString)
}
Also declare in you Modal ViewController class
var delegate:ModalViewControllerDelegate!
Include this protocol ModalViewControllerDelegate in ParentViewController
When you are Moving form one viewController to another
modalVC.delegate=self;
self.presentViewController(modalVC, animated: true, completion: nil)
Here you get your value in ParentViewcontroller
func sendValue(value: NSString) {
}
Finally on ModalViewController
#IBAction func doneClicked(sender: AnyObject) {
delegate?.sendValue("value")
self.dismissViewControllerAnimated(true, completion: nil)
}
In the second viewController (the one showed by the segue) declare a variable like
var parentVC : UIViewController?
then when you call segue from parent
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?)
{
if segue.identifier == "yourSegue" {
let secondController= segue.destinationViewController as UIViewController
secondController.parentVC = self
}
}
so you can use
#IBAction func doneClicked(sender: AnyObject) {
self.parentVC.yourVariable = 0
self.dismissViewControllerAnimated(true, completion: nil)
}

Resources