How to reload UIViewController from another Controller in swift 5 - ios

I have 2 controllers A and Controller B . Controller A has a TableView and Controller B is a subview that when clicked opens a form and on Submit it enters data into the database. I want to reload my TableView from Controller B from the user hits submit.
Controller A
func btnQTYClickedFromcell(selectedIP: IndexPath) {
let storyboard = UIStoryboard(name: "Dashboard", bundle: nil)
let myAlert = storyboard.instantiateViewController(withIdentifier: "EnterQTYView")
myAlert.modalPresentationStyle = UIModalPresentationStyle.overCurrentContext
myAlert.modalTransitionStyle = UIModalTransitionStyle.crossDissolve
self.present(myAlert, animated: true, completion: nil)
}
```
popup(Controller B) will appear.after click on submit button ,get a dictionary as a response.
I want to update my Controller A with that response.
how should I do?Please Help

By using a simple notification
Add this on submit button action (Controller A)
NotificationCenter.default.post(name: Notification.Name("reloadTable"), object: nil)
add this Controller B viewdidload()
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(
self.reloadMyTable(notification:)), name: Notification.Name("reloadTable"),
object: nil)
}
Now add this function somewhere on Controller B
#objc func reloadMyTable(notification: Notification) {
self.Table.reloadData()
/// Table is IBoutlet name of your tableview
}
OR You can use on Controller B
override func viewWillAppear(_ animated: Bool) {
self.Table.reloadData()
}

Custom Delegate // Make a custom Delegate
protocol CustomDelegate {
func response(_ res:Dictionary)
}
In VC2
class VC2 : UIViewController {
var delegate : CustomDelegate?
var resp : Dictionary? //which comes on click on button
... //Your code
#IBAction func submitButtonAction(_ sender: UIButton) {
self.delegate?.response(resp ?? [:])
}
}
VC1
class VC1 : UIViewController {
......
//MARK:- Delegate
func response(_ res:Dictionary) {
//Customize "res" as per your need.
// reload your tableView
tableView.reloadData()
}
}
.....
func btnQTYClickedFromcell(selectedIP: IndexPath) {
let storyboard = UIStoryboard(name: "Dashboard", bundle: nil)
let vc2 = storyboard.instantiateViewController(withIdentifier: "VC2 identifier")
.......
vc2.delegate = self
self.present(myAlert, animated: true, completion: nil)
}

Related

How to reload present view controller swift?

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)
}
}
}

NotificationCenter is not working first time when it is posted from viewcontroller to another ViewController

I have 2 ViewControllers called ViewController1 and ViewController2.
I am posting notification from ViewController1 to ViewController2, but at first time when it is posting, at first time NotificationCenter is not working.
When I came back from ViewController2 to ViewController1 and then again if try to move ViewController2 then that time NotificationCenter it is working,
// ViewController1.swift
import UIKit
class ViewController1: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
//Move to next VC
#IBAction func nextButtonClicked(_ sender: Any) {
NotificationCenter.default.post(name: Notification.Name("callMethodPrint1FromVC2"), object: nil)
let storyBoard = UIStoryboard(name: "Main", bundle: nil)
let vc2 = storyBoard.instantiateViewController(withIdentifier: "ViewController2Id") as? ViewController2
navigationController?.pushViewController(vc2!, animated: true)
}
}
// ViewController2.swift
import UIKit
class ViewController2: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func viewWillAppear(_ animated: Bool) {
//Recieve notification
NotificationCenter.default.addObserver(self, selector: #selector(self.print1Method(notification:)), name: Notification.Name("callMethodPrint1FromVC2"), object: nil)
}
#objc func print1Method(notification: Notification) {
print("Notification came from VC 1")
}
#IBAction func backToVC1(_ sender: Any) {
let storyBoard = UIStoryboard(name: "Main", bundle: nil)
let vc2 = storyBoard.instantiateViewController(withIdentifier: "ViewController1Id") as? ViewController1
navigationController?.pushViewController(vc2!, animated: true)
}
//Remove notification object
deinit {
NotificationCenter.default.removeObserver(self, name: Notification.Name("callMethodPrint1FromVC2"), object: nil)
}
}
Actual Output:
When I move ViewController1 to ViewController2 at first time and every time, print1Method method from ViewController2 has to call.
But it is not working as expeted.
Is there Anu issues in my code or I am missing something?
It wont call because ViewController2 is not loaded, and It will call for the second time because you are doing Push instead of Pop while moving backwards which in turns remains the ViewController1 in the memory.
Instead remove notifications and call print1Method from ViewController2 Viewdidload/ViewWillappear/Viewdidappear based on your needs. keep some public variable in ViewController2 and assign the value in prepareforsegue method in ViewController1 if you want to pass some data from ViewController1 to ViewController2
and replace your:
#IBAction func backToVC1(_ sender: Any) {
let storyBoard = UIStoryboard(name: "Main", bundle: nil)
let vc2 = storyBoard.instantiateViewController(withIdentifier: "ViewController1Id") as? ViewController1
navigationController?.pushViewController(vc2!, animated: true)
}
to
#IBAction func backToVC1(_ sender: Any) {
navigationController?.popViewController(animated: true)
}
You can do this by adding just one step.
What we are doing here is calling the home view controller's method addObserver() to register the observer before navigating, and after that, we are posting the NotificationCenter.default.post. By doing this way the observer gets registered first.
In First View Controller add this function in your button click
func navigate(){
let storyBoard = UIStoryboard(name: "travelApp", bundle: nil)
let VC = storyBoard.instantiateViewController(identifier: "home") as! homeViewController
VC.addObserver()
NotificationCenter.default.post(name:Notification.Name("myNotificationName"), object: nil, userInfo: ["hello": "world"])
self.navigationController?.pushViewController(VC, animated: true)
}
In Second View Controller do this
class homeViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
func addObserver(){
print("addObserverCalled")
NotificationCenter.default.addObserver(self, selector: #selector(onNotification(notification:)), name: Notification.Name("myNotificationName"), object: nil)
}
#objc func onNotification(notification:Notification){
print("NotificationCenter")
print(notification.userInfo!)
}
}
you are posting notification before your view controller is initialized.
there is no need to post notification for calling the method of vc2. instead, directly call the method.
In VC1
#IBAction func nextButtonClicked(_ sender: Any) {
let storyBoard = UIStoryboard(name: "Main", bundle: nil)
let vc2 = storyBoard.instantiateViewController(withIdentifier:"ViewController2Id") as ViewController2
vc2.print1Method()
navigationController?.pushViewController(vc2!, animated: true)
}
}
In VC2
func print1Method() {
print("Method called from VC 1")
}
also for the back button, you are doing it wrong. simply pop the view controller from the stack
#IBAction func backToVC1(_ sender: Any) {
self.navigationController?.popViewController(animated: true)
}

how to enable the customview controller with view after dismiss Second viewcontroller in swift

I have two view controller
imag
1.first view controller is main and second is CustomAlertviw controller
main view controller is like above image, when I click continues am showing the customalertview controller, like image. when click the enter pin button I have to dismiss view contoller and show the view controller with uiview on main view controller but tried, it's showing the view controller, there one more custom view is not showing it crashing my app
main view code
#IBAction func backButtonClicked(_ sender: UIButton) {
let myAlert = UIStoryboard(name: "CustomAlertview", bundle: nil).instantiateViewController(withIdentifier: "CustomAlertViewController") as? CustomAlertViewController
myAlert?.modalPresentationStyle = UIModalPresentationStyle.overCurrentContext
myAlert?.modalTransitionStyle = UIModalTransitionStyle.crossDissolve
self.present(myAlert!, animated: true, completion: nil)
}
Customalertview controller
#IBAction func enterPinButtonClick(_ sender: Any) {
self.dismiss(animated: true, completion: nil)
let myAlert = UIStoryboard(name: "ScanAndPay", bundle: nil).instantiateViewController(withIdentifier: "ScanAndPayDetailsEntryViewController") as? ScanAndPayDetailsEntryViewController
myAlert?.modalPresentationStyle = UIModalPresentationStyle.overCurrentContext
myAlert?.modalTransitionStyle = UIModalTransitionStyle.crossDissolve
self.present(myAlert!, animated: true, completion: nil)
myAlert.valuespass()
}
inside main view controller one function calling from the customalrerview
func valuespass()
{
DispatchQueue.main.async {
self.authType = 1
self.authStatus = false
print("this is calling")
self.pinStatusLabel.text = "Please enter a 4-digit Security PIN"
}
when I am calling this function from the custom alert view application is crashing and showing the hread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value this error. how to can show the all information after dismissing the custom view controller.
You are almost done. present ScanAndPay from FirstView instead of CustomAlertview.
I have done using Delegate as I think it will be fit in this scenario. Follow below steps -
Step 1:
In FirstViewController ,
Add - CustomAlertviewDelegate something like this -
class FirstViewController: UIViewController, CustomAlertviewDelegate {
Now replace your backButtonClicked method -
#IBAction func backButtonClicked(_ sender: UIButton) {
let myAlert = UIStoryboard(name: "CustomAlertview", bundle: nil).instantiateViewController(withIdentifier: "CustomAlertViewController") as? CustomAlertViewController
myAlert?.modalPresentationStyle = UIModalPresentationStyle.overCurrentContext
myAlert?.modalTransitionStyle = UIModalTransitionStyle.crossDissolve
myAlert.delegate = self
self.present(myAlert!, animated: true, completion: nil)
}
Add a new Delegate method of CustomAlertviewDelegate -
func ScanAndPayView(){
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
let myAlert = UIStoryboard(name: "ScanAndPay", bundle: nil).instantiateViewController(withIdentifier: "ScanAndPayDetailsEntryViewController") as? ScanAndPayDetailsEntryViewController
myAlert?.modalPresentationStyle = UIModalPresentationStyle.overCurrentContext
myAlert?.modalTransitionStyle = UIModalTransitionStyle.crossDissolve
self.present(myAlert!, animated: true, completion: nil)
}
}
Step 2:
Now time for CustomAlertview ,
Add protocol for delegate top of the Class -
protocol CustomAlertviewDelegate: class {
func ScanAndPayView()
}
class CustomAlertview: UIViewController{
weak var delegate : CustomAlertviewDelegate!
...
override func viewDidLoad() { // Rest of your code
}
Now time to dismiss current view controller and notify FirstViewController for presenting ScanAndPay view Controller -
#IBAction func enterPinButtonClick(_ sender: Any) {
delegate.ScanAndPayView()
self.dismiss(animated: true, completion: nil)
}
Hope it will help you to achieve what you want.

How to present programmatically a view controller from a UIVIew

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.

How to dismiss the current ViewController and change to the new ViewController in Swift?

I am new to iOS swift. I have three ViewController.
The page-A is root controller and it will present to the page-B. It has a timer in page-B. After 5 sec, it will change View from page-B to page-C , and close the page-B at the same time.
In the ViewControll-B
class AViewController: UIViewController {
var timer: Timer?
override func viewDidLoad() {
super.viewDidLoad()
//set the timer , and chagne view to C ViewController
Timer.scheduledTimer(timeInterval: 5,
target: self,
selector: #selector(self.changeToAnswerView),
userInfo: nil,
repeats: false)
}
#objc func changeToAnswerView() {
dismissLoader()
}
func dismissLoader() {
dismiss(animated: true) {
print("Dismissing Loader view Controller")
}
}
override func viewWillDisappear(_ animated: Bool) {
//change view to Answer ViewController
let filterVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "CViewControllerID")
filterVC.modalPresentationStyle = UIModalPresentationStyle.custom
self.present(filterVC, animated: true, completion: nil)
}
}
After timer execute for 5 sec , the BViewController will dismiss itself and present to the BViewController.
But the following error will happened:
whose view is not in the window hierarchy
Did I missing something?
Question:
How to dismiss the current ViewController and change to the new ViewController in Swift?
Thanks in advance.
Here is the Working code that you can Try
Your Controller which is Dismissed and Tend to make a new controller being presented
import UIKit
class pdfVC: UIViewController
{
var timer : Timer?
override func viewDidLoad()
{
super.viewDidLoad()
timer = Timer.scheduledTimer(timeInterval: 3, target: self, selector: #selector(pdfVC.timerAction), userInfo: nil, repeats: false)
}
#objc func timerAction()
{
if timer != nil {
timer?.invalidate()
dismiss(animated: true, completion: {
print("Dismissed")
})
}
}
override func viewWillDisappear(_ animated: Bool) {
if self.isBeingDismissed {
let filterVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "demoViewController")
filterVC.modalPresentationStyle = UIModalPresentationStyle.custom
print("called")
self.presentingViewController?.present(filterVC, animated: true, completion: nil)
}
}
}
Output
Try changing dismissLoader function to something like:
func dismissLoader() {
dismiss(animated: true) {
print("Dismissing Loader view Controller")
if let presentingController = self.presentingViewController {
let filterVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "BViewControllerID")
filterVC.modalPresentationStyle = UIModalPresentationStyle.custom
presentingController.present(filterVC, animated: true, completion: nil)
}
}
}
and remove the viewWillDisappear function.
The problem is that you are trying to present the BViewController from the loader after being dismissed (i.e removed from window hierarchy) which at that point doesn't exist.
So, the solution is that you could get a reference to the presenting view controller which is presenting the loader and which will appear after dismissing the loader and present the new view controller from there.
in your B view controller after its dismissed,
in its completion handler
self.dismiss(animated: true) {
if let presentingVC = self.presentingViewController {
present c view controller here
}
}
this above is one way to do so, another way is, through delegate
in A view controller:
if let bVC = self.storyboard.instantiateViewController(withIdentifier: B.controllerIdentifier) as? B {
bVC.delegate = self
self.present(bVC, animated: true, completion: nil)
}
inside B View Controller
add delegate method for Protocol
protocol BProtocol: class {
func didClose()
}
and in B dismiss
var delegate: BProtocol?
self.dismiss(animated: true) {
self.delegate?.didClose()
}
this delegate will be implemented by A ViewController as
extension AViewController: BProtocol {
func didClose() {
//present C
}
}
You are trying use a reference (instance) of view controller, that would no longer exist in memory.
Try this
if let presentingVC = self.presentingViewController {
let filterVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "CViewControllerID")
filterVC.modalPresentationStyle = UIModalPresentationStyle.custom
presentingVC.present(filterVC, animated: true, completion: nil)
}
Note: Present a new view controller (filterVC) once your current view controller is dismissed. A view controller can present only a one view controller at time (if you choose present modally). Perform this operation after a delay of 1 second..
Edit
Try with this edited code.
Class AViewController: UIViewController {
var timer: Timer?
var presentingVC: UIViewController?
override func viewDidLoad() {
super.viewDidLoad()
//set the timer , and chagne view to C ViewController
Timer.scheduledTimer(timeInterval: 5,
target: self,
selector: #selector(self.changeToAnswerView),
userInfo: nil,
repeats: false)
}
#objc func changeToAnswerView() {
dismissLoader()
}
func dismissLoader() {
dismiss(animated: true) {
print("Dismissing Loader view Controller")
}
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
//change view to Answer ViewController
if let presentingVC = self.presentingViewController {
self.presentingVC = presentingVC
}
}
override func viewWillDisappear(_ animated: Bool) {
//change view to Answer ViewController
super.viewWillDisappear(animated)
if let presentingVC = self.presentingVC {
let filterVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "CViewControllerID")
filterVC.modalPresentationStyle = UIModalPresentationStyle.custom
presentingVC?.present(filterVC, animated: true, completion: nil)
} else {
print("Presenting View controller is nil")
}
}
}

Resources