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.
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
}
}
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
}
Related
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)
}
}
I try to use delegate to reset my ViewControllerA (HomePage) property "type" value when I logout.
But I set breakpoint and my delegate function work success.
When I login again, and print my property "type" in ViewWillAppear. It's also cache old value before I logout.
Please tell me what's wrong with me.
Thanks.
class ViewControllerA: UIViewController, CustomDelegate {
enum Type: Int {
case book = 0
case pen
}
var tmpType: Type?
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
printBBLog("tmpType: \(tmpType)") //before I logout the value is "pen",and I login again the value is "pen".
}
func clearType() {
printBBLog("clear")
self.tmpType = nil
printBBLog("\(self.tmpType)")
}
#objc func bookBtnClicked(sender: UIButton) {
self.tmpType = .book
}
#objc func penBtnClicked(sender: UIButton) {
self.tmpType = .pen
}
}
class ViewControllerB: UIViewController {
var delegate: CustomDelegate?
func doLogout() {
let vc = ViewControllerA()
self.delegate = vc
self.delegate?.clearType()
}
}
You are creating a new instance of your ViewControllerA. since you are using the UITabBarController you can access you ViewControllerA from your ViewControllerB and assign the delegate. after that you will get your desired result. for reference please check below code.
class ViewControllerB: UIViewController {
var delegate: CustomDelegate?
func doLogout() {
let viewControllers = self.tabBarController?.viewControllers
if let vc1 = viewControllers[0] as? ViewControllerA {
self.delegate = vc1
self.delegate?.clearType()
}
}
}
if you are using UINavigationController inside the UITabBarcontroller then use:
if let vc1 = ((self.tabBarController?.viewControllers?[0] as? UINavigationController)?.viewControllers[0] as? ViewControllerA)
I have a tabBar with a tab that contains a NavVC that has a root as a ParentVC. The ParentVC has a segmentedControl that manages two childVCs, RedVC and BlueVC. Both the RedVC and BlueVC contain a button to push on a YellowVC.
The issue is when the YellowVC gets pushed on, and in it's viewDidLoad I check to see the view controllers on the stack, the two controllers that appear are the ParentVC and the YellowVC, there is no mention of either The RedVC (if it pushes it on) or the BlueVC (if it pushes it on).
for controller in navigationController!.viewControllers {
print(controller.description) // will only print ParentVC and YellowVC
}
I understand that since the tabBar has a navVC as it's tab and it's root is the ParentVC then that's the root of the push but I need to know which one of either the RedVC or the BlueVC triggered it when the YellowVC gets pushed on.
I can use some class properties in the YellowVC but I want to see if there's another way via the navigationController:
var pushedFromRedVC = false // I'd prefer not to use these if I don't have to
var pushedFromBlueVC = false
How can I get a reference to the RedVC or BlueVC when either of them push on the YellowVC?
class MyTabBarController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
let parentVC = ParentVC()
let navVC = UINavigationController(rootViewController: parentVC)
navVC.tabBarItem = UITabBarItem(title: "Parent", image: nil), tag: 0)
viewControllers = [navVC]
}
}
class ParentVC: UIViewController {
var segmentedControl: UISegmentedControl! // switching segments will show either the redVC or the blueVC
let redVC = RedController()
let blueVC = BlueController()
override func viewDidLoad() {
super.viewDidLoad()
addChildViewController(redVC)
view.addSubview(redVC.view)
redVC.didMove(toParentViewController: self)
addChildViewController(blueVC)
view.addSubview(blueVC.view)
blueVC.didMove(toParentViewController: self)
}
}
class RedVC: UIViewController {
#IBAction func pushYellowVC(sender: UIButton) {
let yellowVC = YellowVC()
yellowVC.pushedFromRedVC = true // I'd rather not rely on this
navigationController?.pushViewController(yellowVC, animated: true)
}
}
class BlueVC: UIViewController {
#IBAction func pushYellowVC(sender: UIButton) {
let yellowVC = YellowVC()
yellowVC.pushedFromBlueVC = true // I'd rather not rely on this
navigationController?.pushViewController(yellowVC, animated: true)
}
}
class YellowVC: UIViewController {
var pushedFromRedVC = false // only sample, I'd rather not use these if I don't have to
var pushedFromBlueVC = false
override func viewDidLoad() {
super.viewDidLoad()
for controller in navigationController!.viewControllers {
// even though the push is triggered from either the RedVC or BlueVC it will only print ParentVC and YellowVC
print(controller.description)
}
}
}
Since RedVC and BlueVC are contained in ParentVC you won't find any reference to them in the UINavigationController stack.
An alternative to using the two booleans, is to use a sourceVC property:
class YellowVC: UIViewController {
var sourceVC: UIViewController?
override func viewDidLoad() {
super.viewDidLoad()
if self.sourceVC is BlueVC {
print("We came from Blue")
} else if self.sourceVC is RedVC {
print("We came from Red")
} else {
print("We came from somewhere else"
}
}
}
class RedVC: UIViewController {
#IBAction func pushYellowVC(sender: UIButton) {
let yellowVC = YellowVC()
yellowVC.sourceVC = self
navigationController?.pushViewController(yellowVC, animated: true)
}
}
class BlueVC: UIViewController {
#IBAction func pushYellowVC(sender: UIButton) {
let yellowVC = YellowVC()
yellowVC.sourceVC = self
navigationController?.pushViewController(yellowVC, animated: true)
}
}
You may need to think about whether YellowVC needs to invoke some method on Red/BlueVC or just needs to know some state that is implied by the Red/Blue source.
If it is the latter then RedVC and BlueVC should probably use a delegation pattern to let ParentVC know that it should push YellowVC and set the appropriate state properties based on the source of the delegation call.
You could also consider putting the state into your model that is passed down to the VCs rather than discrete properties on the VCs themselves.
I've got two View Controllers. Main and Temporary one. The second one performs an action on the different screen (is called by pushViewController) and then I'm popping (popViewController) and would like to present the returned value which is String.
I've tried using protocol but it's nil.
Here is my code:
SecondVC.swift:
protocol ValueDelegate {
func append(_ text: String)
}
class SecondViewController: UIViewController{
var delegate: ValueDelegate!
...
...
private func function(){
if let delegate = self.delegate{
delegate.append(value.stringValue)
}
navigateBack()
}
private func navigateBack(){
if let navigation = self.navigationController{
navigation.popViewController(aniamted: true)
}
}
MainVC.swift:
class MainViewController: UIViewController, ValueDelegate {
var secondVC = SecondViewController()
...
func append(_ value: String) {
textField.text?.append(barcode)
}
...
override func viewDidLoad(){
super.viewDidLoad()
self.secondVC.delegate = self
}
}
Use these links to understand exactly how to use Protocols in swift:
Passing data between two ViewControllers (delegate) - Swift
Passing Data between View Controllers
You have to implement below line of code in first view controller :-
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showSecondViewController" {
let secondViewController = segue.destination as! SecondViewController
secondViewController.delegate = self
}
}
I've tried using protocol but it's nil.
Because you never set it to anything. It was your job, when you pushed the SecondViewController, to set its valueDelegate to the MainViewController. But you didn't.
What you did do was set the valueDelegate of another SecondViewController to the MainViewController:
var secondVC = SecondViewController()
self.secondVC.delegate = self
That was silly, because secondVC is a different, newly made instance of SecondViewController having nothing at all to do with your real interface. In particular, it is not the SecondViewController instance that gets pushed. But that is the instance you need to set the delegate of.
I read a few posts about working with delegates in Swift, but mostly they advise to call the viewcontroller which receives the delegate with a segue. I am wondering how to do so without a segue, e.g. in a TabBar app. Here is the code for FirstViewController.swift
// FirstViewController.swift
import UIKit
protocol FirstViewControllerDelegate {
func didSendMessage(message: String)
}
class FirstViewController: UIViewController {
var delegate: FirstViewControllerDelegate?
override func viewDidLoad() {
super.viewDidLoad()
delegate?.didSendMessage("Hello from FirstViewController")
}
}
And here for SecondViewController.swift
// SecondViewController.swift
import UIKit
class SecondViewController: UIViewController, FirstViewControllerDelegate {
#IBOutlet weak var secondSubTitleLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// how to set delegate here?
}
func didSendMessage(message: String) {
secondSubTitleLabel.text = message
}
}
How should I set the receiving delegate here?
this is typically not a scenario where a delegate would fit what you are trying to achieve. If you simply want to call some method in SecondViewController from FirstViewController you can get a reference by
if let vc = self.tabBarController!.viewControllers[1] as? SecondViewController {
vc.didSendMessage("hello")
}
or you might want to send a NSNotification instead to avoid the tight coupling which is introduced by the above code
In your AppDelegate:
NSNotificationCenter.defaultCenter().postNotification("ReceivedAppWatchData", object: self, userInfo: theData)
In any view controller where you want to recieve the data:
func viewDidLoad() {
...
// subscribe to notification
NSNotificationCenter.defaultCenter().addObserver(self, selector: "watchDataReceived:", name: "ReceivedAppWatchData",object: nil)
...
}
func watchDataReceived(notif: NSNotification) {
// handle data
}
deinit {
// unsubscribe to notifications
NSNotification.defaultCenter().removeObserver(self)
}
This way any view controller can access the data without knowing about each other.
Your tab bar controller is controlling the view controllers for each tab, so you should set the delegates in the tab bar controller.
class TabBarController: UITabBarController {
func viewDidLoad() {
super.viewDidLoad()
let firstVC = viewControllers[0] as! FirstViewController
let secondVC = viewControllers[1] as! SecondViewController
firstVC.delegate = secondVC
}
}
This code obviously has some type safety issues and is assuming that viewControllers[0] and viewControllers[1] are FirstViewController and SecondViewController respectively. Also, you should wait to call the delegate method after viewDidLoad in this example. The SecondViewController may or may not be loaded yet.