How to pass delegate thru Navtigation Controller? - ios

I have 2 VC's, and one navigation controller between them. How can i set First screen as a delegate of Second?
What i tried:
Present SecondVC from frist (it presents it without navigation)
Setting delegate in NavVC viewDidLoad()
FirstVC:
class MainVC: UIViewController, SecondVCDelegate {
func passData(text: String) {
// do stuff
}
#IBAction func openNextVC(_ sender: Any) {
let nextVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "NavVC") as! NavVC
present(nextVC, animated: true, completion: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
}
}
Navigation Controller:
class NavVC: UINavigationController {
override func viewDidLoad() {
super.viewDidLoad()
}
}
SecondVC:
protocol SecondVCDelegate {
func passData(text: String)
}
class SecondVC: UIViewController {
var delegate: SecondVCDelegate?
#IBAction func save(_ sender: Any) {
// do stuff
}
override func viewDidLoad() {
super.viewDidLoad()
}
}

Main task here would be to access second view controller instance from navigation controller instance(navC). And to achieve this, you have to first access the rootViewController from your navigation controller instance(nextVC) as below:
let nextVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "NavVC") as! NavVC
let secondVC = nextVC?.viewControllers.first as? SecondVC
secondVC.delegate = self
present(nextVC, animated: true, completion: nil)
The above code is applicable when second view controller is the root of your navigation controller. If second view controller is not the root controller to navC, then you have to iterate the controllers to get second view controller instance. Once second controller instance is there, then you can assign the delegate to the same as below.
for controller in navC.viewControllers {
if controller.isKind(of: SecondVC.self) {
if let secondVc = controller as? SecondVC {
secondVc.delegate = self
}
break
}
}

I handled the same thing. Try this on MainVC:
class MainVC: UIViewController, SecondVCDelegate {
func passData(text: String) {
// do stuff
}
#IBAction func openNextVC(_ sender: Any) {
guard let secondController = self.storyboard?.instantiateViewController(withIdentifier: "AddGroupVC") as? AddGroupVC else { return }
secondController.delegate = { [weak self] in
print("call your delegates here")
}
self.navigationController?.present(secondController, animated: true, completion: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
}
}
On second View Controller, call the delegates in viewWillDisappear :
class SecondVC: UIViewController {
var delegate: SecondVCDelegate?
#IBAction func save(_ sender: Any) {
// do stuff
self.dismiss(animated: true, completion: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillDisappear(_ animated: Bool) {
self.delegate
}
}

Related

How to assign value to an outlet after delegating it?

So my app has 3 screens: Main, Second, and Result (very simple)
On the Main screen, i show a label and button to change it
On the Second one I change label with textinput and pass it to Result
(has
navigation controller)
On the Last screen I show the result and 2 buttons: save and
cancel
My problem is I can't assign value to Main's outlet because it's nil, and I can't do anything with viewDidLoad() because it works only once when the app starts.
What can I do to fix this? is there any function to reload view so I can assign value in viewDidLoad?
Storyboard screenshot
The whole app is here: https://drive.google.com/file/d/1mvL2fVxjOHbL4dReCwJ8poIq9G9-ezny/view
MainVC:
class MainVC: UIViewController, ResultVCDelegate {
func passData(text: String) {
// label.text = text -- throws error
}
#IBOutlet weak var label: UILabel!
#IBAction func change(_ sender: Any) {
let nextVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "NavVC")
present(nextVC, animated: true, completion: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
}
}
SecondVC:
class SecondVC: UIViewController {
#IBOutlet weak var inputText: UITextField!
#IBAction func save(_ sender: Any) {
let nextVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "ResultVC") as! ResultVC
nextVC.labelText = inputText.text!
navigationController?.pushViewController(nextVC, animated: true)
}
override func viewDidLoad() {
super.viewDidLoad()
}
}
ResultVC:
protocol ResultVCDelegate {
func passData(text: String)
}
class ResultVC: UIViewController {
var delegate: ResultVCDelegate?
var labelText = ""
#IBOutlet weak var label: UILabel!
#IBAction func saveAndGoHome(_ sender: Any) {
let mainVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "MainVC") as! MainVC
self.delegate = mainVC
delegate?.passData(text: labelText)
dismiss(animated: true, completion: nil)
}
#IBAction func cancel(_ sender: Any) {
dismiss(animated: true, completion: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
label.text = labelText.isEmpty ? label.text : labelText
}
}
BTW: I did similar app with two screens and it worked like a charm... strange
Following on from my comment.
Looking at your code, the issue is that you are not passing a reference to your MainVC to your ResultVC, and instead you are creating a new instance of MainVC, this results in a a crash because the view controller hasn't been properly created. But you don't want to create a new instance, as you need a reference to the original MainVC that you created.
You can get this by passing the reference, you will need to update all three of your ViewControllers. Something like this should work. Note I haven't tested this but this is the general principle.
class MainVC: UIViewController, ResultVCDelegate {
func passData(text: String) {
label.text = text
}
#IBOutlet weak var label: UILabel!
#IBAction func change(_ sender: Any) {
let nextVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "NavVC")
nextVC.delegate = self // Here we add the delegate (the reference to MainVC)
present(nextVC, animated: true, completion: nil)
}
}
We need to add the delegate here and then forward it on to the ResultVC.
class SecondVC: UIViewController {
weak var delegate: ResultVCDelegate? // Add the delegate that will hold a reference to MainVC
#IBOutlet weak var inputText: UITextField!
#IBAction func save(_ sender: Any) {
let nextVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "ResultVC") as! ResultVC
nextVC.labelText = inputText.text!
nextVC.delegate = delegate // Here we pass the reference to MainVC
navigationController?.pushViewController(nextVC, animated: true)
}
override func viewDidLoad() {
super.viewDidLoad()
}
}
We can remove the code for instantiating the MainVC from the storyboard and instead just use the delegate to pass the value back to MainVC.
class ResultVC: UIViewController {
weak var delegate: ResultVCDelegate?
var labelText = ""
#IBOutlet weak var label: UILabel!
#IBAction func saveAndGoHome(_ sender: Any) {
// Just use the delegate no need to create a new instance
delegate?.passData(text: labelText)
dismiss(animated: true, completion: nil)
}
#IBAction func cancel(_ sender: Any) {
dismiss(animated: true, completion: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
label.text = labelText.isEmpty ? label.text : labelText
}
}
-> Main VC (Where you need to get data)
class MainVC: UIViewController, ResultVCDelegate {
func passData(text: String) {
// label.text = text -- throws error
print(text)
}
#IBOutlet weak var label: UILabel!
#IBAction func change(_ sender: Any) {
let nextVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "NavVC")
nextVC.delegate = self
present(nextVC, animated: true, completion: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
}
}
-> Result VC (from where you need to send data)
protocol ResultVCDelegate: AnyObject {
func passData(text: String)
}
class ResultVC: UIViewController {
weak var delegate: ResultVCDelegate?
var labelText = "Please Enter Something"
#IBOutlet weak var label: UILabel!
#IBAction func saveAndGoHome(_ sender: Any) {
self.delegate?.passData(text: labelText)
dismiss(animated: true, completion: nil)
}
#IBAction func cancel(_ sender: Any) {
dismiss(animated: true, completion: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
label.text = labelText.isEmpty ? label.text : labelText
}
}

UITableView not refreshing after dismissing ViewController above

I have a ViewController being presented using Apple's new default ModalTransitionStyle. And when that ViewController is dismissed, the ViewController below (that has a TableView) doesn't update the table.
I have tried ViewWill/DidAppear but in both cases, I get the error "Unexpectedly found nil while unwrapping an optional Value" when trying to access the table.
I have checked what methods get called and I found that numberOfRowsInSection gets called and cellForRowAtIndexPath doesn't get called. The table is for sure visible. It is not height 0 and numberOfRowsInSection doesn't get 0 returned.
I made a quick test project to demonstrate what I mean.
Image when App Runs
When Popup button clicked
When i go back
import UIKit
var cells = 5
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var table: UITableView!
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return cells
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "test")
return cell!
}
override func viewDidLoad() {
super.viewDidLoad()
table.reloadData()
// Do any additional setup after loading the view.
}
#IBAction func popup(_ sender: Any) {
performSegue(withIdentifier: "popover", sender: self)
}
}
import UIKit
class PopoverViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
cells += 1
// Do any additional setup after loading the view.
}
#IBAction func back(_ sender: Any) {
let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(identifier: "vc") as! ViewController
self.dismiss(animated: true)
}
}
What should happen is that everytime i go back to the view 1 more cell is being displayed. I checked if the count "Cell" is being increased and it is. I just cant figure out how to reload the table.
Old answer:
DispatchQueue.main.async { [weak self] in
self?.table.reloadData()
}
New Answer:
One of the your mistakes is that
let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(identifier: "vc") as! ViewController
self.dismiss(animated: true, completion: {
vc.table.reloadData()
})
The vc is not your living Main Controller, you've to send living controller's reference.
By the way.
class PopoverViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
cells += 1
// Do any additional setup after loading the view.
}
#IBAction func back(_ sender: Any) {
let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(identifier: "vc") as! ViewController
self.dismiss(animated: true, completion: {
vc.table.reloadData()
})
}
}
And your viewDidLoad method should be like:
override func viewDidLoad() {
super.viewDidLoad()
//table.reloadData()
// Do any additional setup after loading the view.
view.addSubview(table)
table.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
table.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
table.heightAnchor.constraint(equalTo: view.heightAnchor).isActive = true
table.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
NotificationCenter.default.addObserver(self,
selector: #selector(notify),
name: .notifier,
object: nil)
self.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem.action,
target: self,
action: #selector(hh))
}
hh function is just for programmatically present Popover
#objc private func notify(notification: NSNotification){
//do stuff using the userInfo property of the notification object
print("notification has called")
DispatchQueue.main.async {
self.table.reloadData()
}
}
extension Notification.Name {
static let notifier = Notification.Name("notifier")
}
And change your cells variable like this:
var cells = 5{
didSet{
NotificationCenter.default.post(name: .notifier, object: nil)
}
}
I've created a Notification pattern to messaging between pages. It worked for me. When you increase cells instance Notification has notified and cell's has updated.
As i understand ViewController is parent, PopoverViewController is child. After child is dismissed, parent should update tableview.
viewDidLoad is called once for parent, it wont be called for dismissal of child.
So, you may define any delegate or closure for child view controller, which reload tableview at parent. you can call it at completion of dismiss.
dismiss(animated: Bool, completion: (() -> Void)?)
class PopoverViewController: UIViewController {
var onDismiss: (() -> Void)?
//...
}
// Also add this method to viewcontroller
class ViewController: UIViewController {
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier = "popover" {
guard let popoverVC = segue.destination as? PopoverViewController else {
return
}
popoverVC.onDismiss = { [unowned self] in
self.tableView.reloadData()
}
}
}
Also you don't need to create parent for dismissal.
let vc = UIStoryboard(name: "Main".. this line is unnecessary. this new vc is not shown so if you try to reach it, crash will occur due to outlets' nil value.

Passing back data to first viewController

I have a basic understanding of how to handle swift protocols to pass data between two viewcontrollers. My situation is this: I wanted to pass data back to my first viewcontroller.
For example:
First ViewController
class ViewControllerA: UIViewController, ViewControllerCResult {
func set(data: String) {
}
}
Second ViewController
class ViewControllerB: UIViewController {
}
Third ViewController
protocol ViewControllerCResult {
set(data: String)
}
class ViewControllerC: UIViewController {
var delegate: ViewControllerCResult?
}
ViewControllerA -> ViewControllerB -> ViewControllerC
I would like to pass data from ViewControllerC to ViewControllerA.
Can anyone help me with this?
You can pass Delegates from Controller A -> B -> C , and when popViewcontroller will be called you can check the self.delegate in ViewController C, if it exist just call function like this self.delegate?.set("data from c"). Check
popControllerPressed Function in ViewController3
First View controller
class ViewController: UIViewController, ViewControllerCResult {
var delegate: ViewControllerCResult?
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 pushControllerPressed(_ sender: Any) {
if let viewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "ViewController2") as? ViewController2 {
if let navigator = navigationController {
viewController.delegate = self
navigator.pushViewController(viewController, animated: true)
}
}
}
func set(data: String) {
}
}
Second ViewController
class ViewController2: UIViewController, ViewControllerCResult {
var delegate: ViewControllerCResult?
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 pushControllerPressed(_ sender: Any) {
if let viewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "ViewController3") as? ViewController3 {
if let navigator = navigationController {
viewController.delegate = self.delegate
navigator.pushViewController(viewController, animated: true)
}
}
}
func set(data: String) {
print(data)
}
}
Third ViewController
protocol ViewControllerCResult {
func set(data: String)
}
class ViewController3: UIViewController, ViewControllerCResult {
var delegate: ViewControllerCResult?
func set(data: String) {
print(data)
}
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 popControllerPressed(_ sender: Any) {
for controller in self.navigationController!.viewControllers as Array {
if controller.isKind(of: ViewController.self) {
delegate?.set(data: "data from C")
self.navigationController!.popToViewController(controller, animated: true)
break
}
}
}
}
Here is Code Link: Pass data forth and back

How to use UINavigationController in UITabBarController

I have a UITabBarController that each tab has an UINavigationController. One of them has a blank rootViewController that pushed another viewController, in viewDidLoad and viewDidAppear functions (I use this presenterVC because I need to reset the AddVC when user uses another tab)
class PresenterVC: UIViewController { // This is root viewController
override func viewDidLoad() {
super.viewDidLoad()
self.PresentCheckAddVC()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.PresentCheckAddVC()
}
func PresentCheckAddVC() {
if self.navigationController?.viewControllers.count == 1 {
let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "AddVC")
self.navigationController?.pushViewController(vc, animated: false)
}
}
}
The problem is that I need to navigate to another viewController in AddVC, but it automatically navigates back to AddVC.
I tried both segue and manually pushing viewController, and they have same results.
class AddVC: UIViewController {
#IBAction func btnPayeeSelect_Pressed(_ sender: Any) {
PresentPayeeAddVC()
}
func PresentPayeeAddVC() {
let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "PayeeAddVC")
self.navigationController?.pushViewController(vc, animated: true)
}
}

Swift / how to call delegate with popViewController

I have read this thread (and similar others) from bottom to top, but it doesn't fit my needs at all.
I have a UIViewController inside UIPageViewController within a UINavigationController. Navigating to a 2nd ViewController. Navigating to a 3rd ViewController and want to pop back to 2nd ViewController delivering data.
My code currently:
protocol PassClubDelegate {
func passClub(passedClub: Club)
}
class My3rdVC: UIViewController {
var clubs: [Club] = []
var passClubDelegate: PassClubDelegate?
....
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let club = clubs[indexPath.row]
self.passClubDelegate?.passClub(club)
navigationController?.popViewControllerAnimated(true)
}
My 2nd VC:
class My2ndVC: UIViewController, PassClubDelegate {
var club = Club()
func passClub(passedClub: Club) {
SpeedLog.print("passClub called \(passedClub)")
club = passedClub
}
passClub is not called. I'm sure it's because I didn't set the delegate to the My2ndVC, but how would I do that? All the solutions I have found wanting me to use a) segue or b) instantiate a My2ndVC new, what doesn't make any sense since it's still in memory and I want to pop back to go back in hierarchy. What am I missing? What are my possibilities? Help is very appreciated.
PS: I'm not using any segues. My3rdVC is called by:
let vc = stb.instantiateViewControllerWithIdentifier("My3rdVC") as! My3rdVC
self.navigationController?.pushViewController(vc, animated: true)
You can set the delegate of My3rdVC in the prepareForSegue method of My2ndVC.
class My2ndVC: UIViewController, PassClubDelegate {
...
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
super.prepareForSegue(segue, sender: sender)
switch segue.destinationController {
case let controller as My3rdVC:
controller.passClubDelegate = self
}
}
}
This is assuming you have created a segue in your storyboard that pushes My3rdVC from My2ndVC onto the navigation controller stack, which I'm assuming you have. So just try simply pasting this prepareForSegue method into My2ndVC and see if it works.
UPDATE
let vc = stb.instantiateViewControllerWithIdentifier("My3rdVC") as! My3rdVC
vc.passClubDelegate = self
navigationController?.pushViewController(vc, animated: true)
When pop one VC to another you can pass data using protocol by declare delegate variable as static. Here in the following example we pop SecondVC to FirstVC and we pass a string.
class FirstVC: UIViewController,getDataDelegateProtocol {
#IBOutlet weak var label: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
SecondVC.delegate = self
}
func getData(tempStr: String) {
label.text = tempStr
}
#IBAction func buttonClick(_ sender: UIButton){
let nav = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "SecondVC") as! SecondVC
self.navigationController?.pushViewController(nav, animated: true)
}
}
Another View Controller
protocol getDataDelegateProtocol{
func getData(tempStr: String)
}
class SecondVC: UIViewController {
#IBOutlet weak var label: UILabel!
static var delegate: getDataDelegateProtocol?
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func buttonClick(_ sender: UIButton){
SecondVC.delegate?.getData(tempStr: "Received data from SecondVC")
self.navigationController?.popViewController(animated: true)
}
}

Resources