0

I have searched through the net for couple of hours now thought this would be an easy task, but things are not going as planned. This question is edited, some of great fella has given great answer but I am still struggling.

Here is the scenario, I have a viewControllerA which is always visible. There is a small button on top of this view, when I click it there comes viewControllerB as a static tableViewController. Its a slide view from left to right to be honest like other apps.

There is one section and couple of rows in the tableView, when I tap 4th row I present viewControllerC, there is a UISwitch button there. When I dismiss the viewControllerC, viewControllerA appear again. viewControllerB is my menu controller therefore its not my greater concern .Now I want to pass data from viewControllerC to viewControllerA. Here is my broken code:

for viewControllerC :

class viewControllerC: UIViewController {
 ..//
 @IBAction func switchTapped(_ sender: UISwitch) {
     let vc = viewControllerA()
     if sender.isOn == true {
         vc.state = true
     } else if sender.isOn == false {
         vc.state = false
     }   
  }
 ..//
}

for viewControllerA :

class viewControllerA: UIViewController, GMSMapViewDelegate {
  var state:Bool?
  ...//
  if self.state == true {
      self.mapView.isTrafficEnabled = true
    } else {
      self.mapView.isTrafficEnabled = false
   } 
}

But its not working an I know I am not heading to the right direction. As you can see from the example I want to send ture when UISwitch is on and false when its off from viewControllerC to viewControllerA. Some of the folks have suggested delegate method but I am still struggling. I was following this link , I think "Passing data backwards through the shared state of the app" section meets my criteria. Although I need help. Thanks in advance.

Arafin Russell
  • 1,487
  • 1
  • 18
  • 37
  • Possible duplicate of [Passing Data between View Controllers](https://stackoverflow.com/questions/5210535/passing-data-between-view-controllers) – dan Nov 13 '17 at 19:11
  • Not related to the issue but you can reduce the code to one line respectively: `vc.state = sender.isOn` and `self.mapView.isTrafficEnabled = self.state`. The cause of the issue is most likely that the result of `viewControllerA()` is not the instance designed in Interface Builder. – vadian Nov 13 '17 at 19:17

2 Answers2

4

You can pass data with Delegate pattern, here's an idea:

import UIKit

// Make a delegate protocol
protocol ViewControllerBDelegate: class {
    func didTapSwitch(isOn: Bool)
}

class ViewControllerB: UIViewController {

    // Make a weak delegate reference in VC B
    weak var delegate: ViewControllerBDelegate?

    var state: Bool?

    // On action trigger delegate method:
    @IBAction func switchTapped(_ sender: UISwitch) {
        delegate?.didTapSwitch(isOn: sender.isOn)
    }

}

class ViewControllerA: UIViewController {

    var stateFromSwitch: Bool?
    // In this VC you are instantiating viewController B
    // ... code ...
    // set delegate: viewControllerB.delegate = self
}

// implement ViewControllerBDelegate
extension ViewControllerA: ViewControllerBDelegate {
    func didTapSwitch(isOn: Bool) {
        stateFromSwitch = isOn
    }
}
Oleh Zayats
  • 2,423
  • 1
  • 16
  • 26
  • @Oleh could you please have a look at the edited question, I am struggling mate :( – Arafin Russell Nov 13 '17 at 21:35
  • @ArafinRussell when you tap 4-th row you instantiate a ViewController that has a delegate reference (look at my code), your taping on Switch in it, IBAction triggers delegate method with Bool (true/false) it's passed back to the ViewController where you've set viewcontrollerc.delegate = self and implemented the delegate method, that's it. Did you try to implement the pattern? I don't see the updated code in your question bro, try to copy paste first and figure out how it works and then write where you've failed :) – Oleh Zayats Nov 13 '17 at 21:49
1

First of all when you initiate viewControllerA :

let vc = viewControllerA()

You are creating new instance of viewcontrol which doesn’t reference to your first view control.

You can pass data to viewcontrollers in different ways. You can use delage pattern or you can use unwind.

In delegate method first you define a class type protocol with a function definition for changing something in viewControllerA.

protocol ViewControllerBDelegate: class {
    func changeSwitch(toValue: Boolean)
}

Then in ViewControllerB you define a weak reference to delegate

weak var delegate: ViewControllerBDelegate?

Then you adopt this protocol on ViewControlA:

extension ViewControllerA: ViewControllerBDelegate {
    func changeSwitch(toValue: Boolean) {
         state = toValue
     }
}

When you want to present or push to ViewControllerB you should set this variable to self

let vc = ViewControllerB()
vc.delegate = self
present(vc, animated: true, completion: nil)
// or navigationController. pushViewController(vc, animated: true)

if you are using segue to navigate from one viewcontroller to another, you should set delegate variable in prepare(for segue, sender). Override this function in ViewControllerA

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if segue.identifier == "mySegue" , 
       let vc = segue.destination as? ViewControllerB {
        vc.delegate = self
    }
}

Then when switch value changed you can use delegate to change value in ViewControllerA

delegate?.changeSwitch(toValue: sender.isOn)

on wind let you pop or dismiss child viewcontrollers to a certain parent and then do something. You can read a full tutorial here

EDIT

for chain delegates you can pass a delegate to ViewController B, then pass the same delegate to ViewController C.

in view controller C you define the same type delegate

weak var delegate: ViewControllerBDelegate?

then in view controller B when you are navigating to view controller c you pass the same delegate

let vc = ViewControllerC()
vc.delegate = self.delegate
present(vc, animated: true, completion: nil)

EDIT 2

SWRevealViewController is different scenario. revealController have an property named frontViewController. which can be your ViewControllerA if you dont push any other controllers on reveal. handling it with frontViewController is tricky you should be sure if frontController is ViewControllerA.

so i suggest you use another method to communicate with ViewControllerA. you can use NotificationCenter.

extension Notification.Name {
    static let updateMap = Notification.Name("updateMap")
}

in ViewControllerA

override func viewDidLoad() {
    super.viewDidLoad()
    NotificationCenter.default.addObserver(self, selector: #selector(updateMap(_:)), name: .updateMap, object: nil )
}

@objc func updateMap(notification: NSNotification) {
  if let state = notification.userInfo?["state"] as? Bool {
      // do something with state
  }
}

and in ViewControllerC you post a notification when switch value is Changed:

let userInfoDic:[String: Bool] = ["state": sender.isOn]
  // post a notification
  NotificationCenter.default.post(name: .updateMap, object: nil, userInfo: userInfoDic)

if frontViewController in reveal is pushed again. reveal will initiate new ViewControllerA for frontViewController. in this scenario you have to set settings in UserDefault and in ViewControllerA read this settings.

using UserDefaults :

in ViewControllerC

UserDefaults.standard.setValue(sender.isOn, forKey: "mapState")

in ViewControllerA

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    let state = UserDefaults.standard.value(forKey: "mapState") ?? false
    self.mapView.isTrafficEnabled = state
}
Mohammadalijf
  • 1,387
  • 9
  • 19
  • could you please have a look at the edited question, I am struggling mate :( – Arafin Russell Nov 13 '17 at 21:36
  • It really depends on how you implemented side menu. Are you using library to create side menu? If yes please tell me which library so i can help you. In general you can chain delegates to pass data through view controllers. Im gonna edit my answer for chain delegates – Mohammadalijf Nov 13 '17 at 22:01
  • @ArafinRussell notification – Mohammadalijf Nov 13 '17 at 22:02
  • SWRevealViewController mate – Arafin Russell Nov 13 '17 at 22:11
  • @ArafinRussell updated answer for RevealViewController. if your revealController dont push or set another ViewController then you can use NotificationCenter. but if it pushes or sets a new FrontViewController then you have to save state (the boolean you are passing) in UserDefaults and in ViewControllerA read state from UserDefaults and configure your map – Mohammadalijf Nov 13 '17 at 22:38
  • May Allah bless you, you have shown every possible way and finally I got my project up & running. I couldn't be more thankful. Thank you so much brother :) – Arafin Russell Nov 14 '17 at 06:28