I have implemented delegate in LoginViewController defined in RegistrationViewController. Callback function is calling but the problem is that I am not able to update the textfield of LoginViewController in delegate method.
LoginViewController.swift
import UIKit
class LoginViewController :UIViewController,RegisterViewDelegate {
#IBOutlet weak var mobileNumber: UITextField!
#IBAction func showRegistrationView(_ sender: Any) {
let controller = storyboard?.instantiateViewController(withIdentifier: "registration") as! RegistrationViewController
controller.delegate = self
present(controller, animated: false, completion: nil)
}
func onUserRegistrationCompletion(number: String) {
print(number) // output is 05010101010
DispatchQueue.main.async {
self.mobileNumber.text! = number
print(self.mobileNumber.text!) . // output is empty
}
}
}
RegistrationViewController.swift
import UIKit
class RegistrationViewController: UIViewController {
weak var delegate:RegisterViewDelegate?
#IBAction func register(_ sender: Any) {
self.delegate?.onUserRegistrationCompletion(number: "05010101010")
let controller = self.storyboard?.instantiateViewController(withIdentifier: "login")
present(controller!, animated: false, completion: nil)
}
}
protocol RegisterViewDelegate:class {
func onUserRegistrationCompletion(number:String)
}
it means you allocating the memory again on self.storyboard?.instantiateViewController(withIdentifier: "login") in Register VC, that the reason your delegate is by default goes to nil.
#IBAction func register(_ sender: Any) {
self.delegate?.onUserRegistrationCompletion(number: "05010101010")
self.dismiss(animated: true, completion:nil)
}
I suggest you to use navigationController.
and you can push to next view this way:
let controller = storyboard?.instantiateViewController(withIdentifier: "registration") as! RegistrationViewController
controller.delegate = self
self.navigationController?.pushViewController(controller, animated: true)
and when you want to go back to previous view use this code:
self.delegate?.onUserRegistrationCompletion(number: "05010101010")
self.navigationController?.popViewController(animated: true)
And your result will be:
Check THIS sample for more info.
And don't forget to Embed In your LoginViewController into navigationController from storyboard. as shown in demo project.
Related
I have two Present view controllers. The thing i want to do is when the second Present view controller is dismissed it will automatically reload the first present view controller(Table view). note: first view controller holds a table view, basically i want to reload the table view of first controller.
ViewWillAppear code:
override func viewWillAppear(_ animated: Bool) {
tableViewReloadFromCreateProductVC()
}
func tableViewReloadFromCreateProductVC () {
tableView.reloadData()
}
Calling from second view controller code:
SecondViewController.tableViewReloadFromCreateProductVC()
navigationController?.popViewController(animated: true)
dismiss(animated: true, completion: nil)
FirstViewController calling 2nd view controller
#IBAction func CallSecondViewButton(_ sender: Any) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "YourViewControllerIdentifier") as! YourViewController
controller.modalPresentationStyle = .fullScreen
self.present(controller, animated: true, completion: nil)
}
just write the code in viewWillAppear() method of the view controller that you want to reload like this
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
//perform api call if any
yourTableView.reloadData()
}
2nd view controller
#IBAction func CloseButton(_ sender: Any) {
self.dismiss(animated: true, completion: nil)
}
after dissmissing the viewWillAppear method of firstViewController will autometically called.
The First two snippets are for first view controller and the last one is for second view controller
Reloading the entire table view could sometimes be costly and it also sounds like you're making an API call as well so unless you want your table view to be reloaded and the API call made every time the view controller becomes visible whether or not you've made changes to it, you want the reloading to be done only when it's necessary.
You can try it in a few different ways:
class CreateProductVC: UITableViewController {
#IBAction func presentSecondVC() {
if let secondVC = storyboard?.instantiateViewController(identifier: "SecondVC") as? SecondViewController {
secondVC.delegate = self
present(secondVC, animated: true, completion: nil)
}
}
}
class SecondViewController: UIViewController {
weak var delegate: CreateProductVC?
#IBAction func dismissSecondVC() {
self.dismiss(animated: true) {
self.delegate?.tableView.reloadData()
}
}
}
or
class CreateProductVC: UITableViewController {
#IBAction func presentSecondVC() {
if let secondVC = storyboard?.instantiateViewController(identifier: "SecondVC") as? SecondViewController {
secondVC.isDismissed = { [weak self] in
self?.tableView.reloadData()
}
present(secondVC, animated: true, completion: nil)
}
}
}
class SecondViewController: UIViewController {
var isDismissed: (() -> Void)?
#IBAction func dismissSecondVC() {
self.dismiss(animated: true) {
self.isDismissed?()
}
}
}
or if you want more fine-grained control over what to do with the new data:
protocol ReloadVC {
func reload(_ value: String)
}
class CreateProductVC: UITableViewController, ReloadVC {
var dataSource: [String]! {
didSet {
tableView.reloadData()
}
}
#IBAction func presentSecondVC() {
if let secondVC = storyboard?.instantiateViewController(identifier: "SecondVC") as? SecondViewController {
secondVC.delegate = self
present(secondVC, animated: true, completion: nil)
}
}
func reload(_ value: String) {
dataSource.append(value)
}
}
class SecondViewController: UIViewController {
var delegate: ReloadVC?
#IBAction func dismissSecondVC() {
self.dismiss(animated: true) {
let someValue = "Some Value"
self.delegate?.reload(someValue)
}
}
}
My Scenario, In my project I am maintaining three ViewController (Main, VC1 and VC2). In main ViewController I am maintaining UIButton, This button click to VC1 presenting model view controller. Again, VC1 I am maintain UIButton with action click to present model to VC2. After VC2 presenting I need to dismiss VC1.
// presenting ViewController
var presentingViewController: UIViewController! = self.presentingViewController
self.dismissViewControllerAnimated(false) {
// go back to MainMenuView as the eyes of the user
presentingViewController.dismissViewControllerAnimated(false, completion: nil)
}
Try this in VC2's close button action
var vc = self.navigationController?.presentingViewController
while vc?.presentingViewController != nil {
vc = vc?.presentingViewController
}
vc?.dismiss(animated: true, completion: nil)
Hope this will work:
You need to have a UIViewController as a base, in this case MainViewController is the base ViewController. You need to use a protocol to call the navigation between Controllers.
you can do using protocol:-
In to your FirstViewController setting Protocol :
protocol FirstViewControllerProtocol {
func dismissViewController()
}
class FirstViewController: UIViewController {
var delegate:FirstViewControllerProtocol!
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
#IBAction func goBack(sender: AnyObject) {
self.dismissViewControllerAnimated(true) {
self.delegate!.dismissViewController()
}
}
Now in your MainViewController
class MainViewController: UIViewController, FirstViewControllerProtocol {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func goToFirstViewController(sender: AnyObject) {
let viewController = self.storyboard?.instantiateViewControllerWithIdentifier(String(FirstViewController)) as! FirstViewController
viewController.delegate = self
self.presentViewController(viewController, animated: true, completion: nil)
}
//MARK: Protocol for dismiss
func dismissViewController() {
if let viewController = self.storyboard?.instantiateViewControllerWithIdentifier(String(SecondViewController)){
self.presentViewController(viewController, animated: true, completion: nil)
}
}
To solve the problem statement, what you can do is present VC2 using Main instead of VC1.
We can get the reference to Main in VC1 using
self.presentingViewController
When VC2 is presented, dismiss VC1 in the completionHandler of present(_:animated:completion:) method
class Main: UIViewController {
#IBAction func onTapButton(_ sender: UIButton) {
let vc1 = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "VC1")
self.present(vc1, animated: true, completion: nil)
}
}
class VC1: UIViewController {
#IBAction func onTapButton(_ sender: UIButton) {
let vc2 = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "VC2")
vc2.view.backgroundColor = .blue
self.dismiss(animated: false, completion: nil)
self.presentingViewController?.present(vc2, animated: true, completion: nil)
}
}
class VC2: UIViewController {
}
This approach is giving the expected output. Let me in case anything else is required.
You need to open vc2 from the mainVC - In order to do this you need a delegate method which will tell mainVC to close the current opened vc (i.e. vc1) and in the success block open vc2.
Code Snipet:-
dismiss(animated: false) {
//Open vc2
present(vc2)
}
In this case you need to call dismiss from the view controller over which your other view controllers are presented(Main in your case).
As you stated your situation in the question above Main presents VC1 and VC1 then presents VC2:
then calling Main.dismiss(animated: true, completion: nil) will dismiss VC1 and VC2 simultaneously.
If you don't have a reference to the root controller(Main), you could chain a couple of presentingViewController properties to access it; Something like this in the topmost controller(VC2):
presentingViewController?.presentingViewController?.dismiss(animated: true)
I hope this helps.
There is a present(_:animated:completion:) method which takes a completion. Inside that you can dismiss your VC1.
Your code will look something like this
#IBAction func ButtonTapped(_ sender: Any) {
present(VC2, animated: true) {
dismiss(animated: true, completion: nil)
}
}
I have a parent UIViewController(MainVC). From there I have 2 segue to 2 UIViewControllers: FirstVC (identifier: goFirstVC) and SecondVC (identifier: goSecondVC)
In FirstVC I have a button Save and when I click it I want to dismiss the FirstVC and to go on SecondVC.
Here is my code:
#IBAction func saveBtnTapped(_ sender: UIButton) {
//navigationController?.popViewController(animated: true)
let destinationController = self.storyboard?.instantiateViewController(withIdentifier: "goSecondVC") as! SecondVC
let presentingVC = self.presentingViewController
self.dismiss(animated: false, completion: { () -> Void in
presentingVC!.present(destinationController, animated: true, completion: nil)
})
}
Here is the design for my issue:
You can use setViewControllers to keep the parent only and the SecondVC
let destinationController = self.storyboard?.instantiateViewController(withIdentifier: "goSecondVC") as! SecondVC
self.navigationController?.setViewControllers([self.navigationController!.viewControllers.first!,destinationController], animated: true)
There are many methods but one of the generic one is to use delegates and protocols. Use the following code in your classes.
Add Following code in the first VC
protocol SecondVCDelegate : AnyObject {
func goToSecondVC()
}
class FirstVC: UIViewController {
var Delegate : SecondVCDelegate!
#objc func save() {
Delegate.goToSecondVC()
}
}
//In second view
Add following code in MinVC
class MainVC: UIViewController {
override func viewDidAppear() {
self.performSegue(withIdentifier: <yourSegueIdentifierToFirstVC>, sender: nil)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == <yourSegueIdentifierToFirstVC> {
let cont = segue.destination as! FirstVC
cont.Delegate = self
}
}
}
extension MainVC : SecondVCDelegate {
func goToSecondVC() {
self.performSegue(withIdentifier: <yourSegueIdentifierToSecondVC>, sender: nil)
}
}
//This is MainVC
I think you should present second from first rather than dismissing first then presenting second.
when you will present second from first then if you wanto go back to main you can simply dismiss 2 veiwcontrollers at once without any hack.
try it:
We can control our presented controllers with navigation controller calling pushViewController and popViewController methods.
FirstVC.navigationController?.popViewController(animated: animated) // pops the top view controller
self.navigationController?.pushViewController(SecondVC, animated: true) // Pushes a view controller onto navigation's stack of controllers
In your case:
let destinationController = self.storyboard?.instantiateViewController(withIdentifier: "goSecondVC") as? SecondVC
self.navigationController?.popViewController(animated: animated)
self.navigationController?.pushViewController(destinationController, animated: true)
I want to use UISwitch from that when isON use it should be in ViewController1 isOFF then it has to go next ViewController. Can someone help me on this?
import Foundation
import Firebase
import FirebaseAuth
class HomeViewController: UIViewController {
#IBOutlet weak var citiPostSwitch:UISwitch!
#IBAction func switchToCitiPost (switchState: UISwitch){
if citiPostSwitch.isOn{
print("your in ViewConroller 1")
citiPostSwitch.setOn(false, animated: true)
} else{
citiPostSwitch.setOn(true, animated: true)
print("your in ViewController 2")
}
}
#IBAction func logOutAction(sender: AnyObject) {
if Auth.auth().currentUser != nil {
do {
try Auth.auth().signOut()
let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "SignUp")
present(vc, animated: true, completion: nil)
} catch let error as NSError {
print(error.localizedDescription)
}
}
}
}
In your function , just off the switch of this and second controller, in which your are navigating.
#IBAction func switchToCitiPost (switchState: UISwitch){
if citiPostSwitch.isOn {
citiPostSwitch.setOn(false, animated: true)
let vc = SecondViewController()
vc.switch.setOn(false, animated: true)
self.navigationController?.pushViewController(vc, animated: true)
}
}
When your come to your controller you will get your switch in off state.
class SecondViewController: UIViewController {
#IBOutlet weak var `switch`: UISwitch!
override func viewDidLoad() {
super.viewDidLoad()
self.switch.addTarget(self, action: #selector(self.switchAction(_:)), for: .valueChanged)
}
#objc func switchAction(_ switchState: UISwitch) {
self.navigationController?.popViewController(animated: true)
}
}
In SecondView Controller , on switch action just dismiss or pop from Controller.
Hope it helps
I am using a Class which is a subclass of MessageView (Swift Message Library) which is inherit from UIView. Inside, I have a UIButton and I want to present programmatically another ViewController through it.
Here is my code below :
import Foundation
import SwiftMessages
import UIKit
class MyClass: MessageView {
var hideBanner: (() -> Void)?
#IBAction func helpButtonPressed(_ sender: UIButton) {
let storyBoard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let newViewController = storyBoard.instantiateViewController(withIdentifier: "newViewController") as! NewViewController
self.present(newViewController, animated: true, completion: nil)
#IBAction func tryAgainButtonPressed(_ sender: UIButton) {
hideBanner?()
}
open override func awakeFromNib() {
}
}
I have tried this, but it is not working since the UIView do not have the present method.
First get top ViewController using this. Then you can present your viewController.
if var topController = UIApplication.sharedApplication().keyWindow?.rootViewController {
while let presentedViewController = topController.presentedViewController {
topController = presentedViewController
}
// topController now can use for present.
let storyBoard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let newViewController = storyBoard.instantiateViewController(withIdentifier: "newViewController") as! NewViewController
topController.present(newViewController, animated: true, completion: nil)
}
.present is a method in UIViewController class, that's the reason you cannot present view controller from UIView class.
To achieve this, get the root view controller and present the controller as follows:
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let viewController = appDelegate.window!.rootViewController as! YourViewController
let storyBoard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let newViewController = storyBoard.instantiateViewController(withIdentifier: "newViewController") as! NewViewController
viewController .present(newViewController, animated: true, completion: nil)
The iOS convention is that only a ViewControllers presents another ViewController.
So the answers above - where the View is finds the current ViewController via UIApplication.sharedApplication().keyWindow?.... will work but is very much an anti-pattern.
The preferred way would be:
Your MyClass view has presentation code only
You must have a ViewController which has a reference to this MyClass view
This ViewController has the #IBAction func tryAgainButtonPressed
From there, you can present the next ViewController
Try this #simple code.
import Foundation
import SwiftMessages
import UIKit
class MyClass: MessageView {
var hideBanner: (() -> Void)?
#IBAction func helpButtonPressed(_ sender: UIButton) {
let storyBoard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let newViewController = storyBoard.instantiateViewController(withIdentifier: "newViewController") as! NewViewController
UIApplication.shared.keyWindow?.rootViewController?.present(newViewController, animated: true, completion: nil)
}
#IBAction func tryAgainButtonPressed(_ sender: UIButton) {
hideBanner?()
}
open override func awakeFromNib() {
}
}
Here is the example code using delegation pattern.
class YourViewController: UIViewController {
var yourView: MyClass // may be outlet
override func viewDidLoad() {
super.viewDidLoad()
yourView.delegate = self
}
}
protocol MyClassDelegate:class {
func tryAgainButtonDidPressed(sender: UIButton)
}
class MyClass: MessageView {
weak var delegate: MyClassDelegate?
#IBAction func tryAgainButtonPressed(_ sender: UIButton) {
delegate?.tryAgainButtonDidPressed(sender: sender)
}
}
You can achieve this by two ways
Protocol
By giving reference of that view controller to the view when you are initializing view
Sorry for the late reply. MessageView already provides a buttonTapHandler callback for you:
/// An optional button tap handler. The `button` is automatically
/// configured to call this tap handler on `.TouchUpInside`.
open var buttonTapHandler: ((_ button: UIButton) -> Void)?
#objc func buttonTapped(_ button: UIButton) {
buttonTapHandler?(button)
}
/// An optional button. This buttons' `.TouchUpInside` event will automatically
/// invoke the optional `buttonTapHandler`, but its fine to add other target
/// action handlers can be added.
#IBOutlet open var button: UIButton? {
didSet {
if let old = oldValue {
old.removeTarget(self, action: #selector(MessageView.buttonTapped(_:)), for: .touchUpInside)
}
if let button = button {
button.addTarget(self, action: #selector(MessageView.buttonTapped(_:)), for: .touchUpInside)
}
}
}
which is automatically invoked for any button you connect to the button outlet. So the recommended method for presenting another view controller is to have the presenting view controller configure the presentation logic in this callback:
messageView.tapHandler = { [weak self] in
guard let strongSelf = self else { return }
let storyBoard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let newViewController = storyBoard.instantiateViewController(withIdentifier: "newViewController") as! NewViewController
strongSelf.present(newViewController, animated: true, completion: nil)
}
If your view has more than one button, you can handle them all through buttonTapHandler since it takes a button argument. You just need to configure the target-action mechanism for each button:
otherButton.addTarget(self, action: #selector(MessageView.buttonTapped(_:)), for: .touchUpInside)
Or you can add one callback for each button by duplicating the above pattern.