Passing Data between view Controllers Using a segue from a view embedded in a navigation controller to a tabbarcontroller - swift

I have two views that I would like to pass data from one view to the next. The first view is where I have the data that I would like to pass to the next view lets call it SourceViewController. However SourceViewController is embedded in a NavigationViewController and the secondViewController lets call it DestinationViewController is the firstView in a TabViewController.
I have tried to use the answer from this question and it fails to go past navigation view it just skips the whole logic.
This is my code :
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if (segue.identifier == "loginSuccessSugue") {
if let tab = self.presentingViewController as? UITabBarController,
let nav = tab.viewControllers?[0] as? UINavigationController,
let destinationVC = nav.viewControllers.first as? HomeViewController {
destinationVC.currentBalance = serviceBalance
}
}
}
This is the HomeViewController:
class HomeViewController: UIViewController , UITableViewDelegate,UITableViewDataSource, UICircularProgressRingDelegate{
var currentBalance = 0.0
override func viewDidLoad() {
super.viewDidLoad()
circularBalance.maxValue = CGFloat(currentBalance)
print(currentBalance)
}
override func viewDidAppear(_ animated: Bool) {
print(currentBalance)
circularBalance.setProgress(value: CGFloat(currentBalance), animationDuration: 3)
}
}
This is how the storyboard looks like:

This is my view controller where you can check that I am sending 5 to tabbar first viewcontroller:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.performSegue(withIdentifier: "segueIdentifier", sender: self)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let barViewControllers = segue.destination as! UITabBarController
let destinationNv = barViewControllers.viewControllers?[0] as! UINavigationController
let destinationViewController = destinationNv.viewControllers[0] as! FirstViewController
destinationViewController.currentBalance = 5
}
}
Now You can check my firstview controller where you can check that what value we are getting.
class FirstViewController: UIViewController {
var currentBalance = 0
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
print(currentBalance)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Now, You can check my console and storyboard:

From Apple's UIViewController docs:
var presentingViewController: UIViewController?
The view controller that presented this view controller.
Which would work great for you, ** IF ** you were trying to go back in your navigational hierarchy, as did the guy in the SO post you referenced.
You are trying cast the VC THAT PRESENTED SOURCEVIEWCONTROLLER of your SourceViewController as a UITabBarController, which fails miserably, and is why you never hit a breakpoint inside your nested if let's.
If we look the next variable down from this in the docs we can see something that will take us forward to the UIViewController we are presenting:
var presentedViewController: UIViewController?
The view controller that is presented by this view controller, or one
of its ancestors in the view controller hierarchy.
So now to go over the code you need to solve your predicament. I'll give you the same code you posted, but fixing the tense of my verbs in the comments:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if (segue.identifier == "loginSuccessSugue") {
//ing -> ed
if let tab = self.presentingViewController as? UITabBarController,
let nav = tab.viewControllers?[0] as? UINavigationController,
let destinationVC = nav.viewControllers.first as? HomeViewController {
destinationVC.currentBalance = serviceBalance
}
}
Isn't it frustrating when the English language tricks you up more than swift?
EDIT:
Since you are passing the data in prepareForSegue: you will actually want to get the UITabBarController from segue.destination. And since the the UITabBarController's ViewControllers property will be nil or empty in prepare for segue. This is a bad approach for passing the data.
You may need to create custom subclass of UITabBarController, pass it the variable, and then pass that data to its viewControllers in viewDidLoad.
class MyTabBarController: UITabBarController {
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
var serviceBalance : Double?
override func viewDidLoad() {
super.viewDidLoad()
//Make sure vc is not null or empty before continuing.
guard let vcs = viewControllers, !vcs.isEmpty else {
return
}
if let navVC = vcs[0] as? UINavigationController, let destinationVC = navVC.viewControllers[0] as? UIViewController {
destinationVC.serviceBalance = destinationVC
}
}
}
Updated prepareForSegue:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let tabBarVC = segue.destination as? MyTabBarController {
tabBarVC.serviceBalance = serviceBalance
}
}
Don't forget to change the UITabBarController's class in the identity inspector of storyboard to MyTabBarController

You need to change if() condition code.
Use below code will get your HomeViewController in destination of segue.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if (segue.identifier == "loginSuccessSugue") {
if let destinationVC = segue.destination as? HomeViewController {
destinationVC.currentBalance = serviceBalance
}
}
}
As in segue.destination you will get your HomeViewController so no need to get it from Tab + Navigation stack.
Edit:
let destinationVC = segue.destination as? HomeViewController
Print(destinationVC)
Hope this solution will helps!

Related

Segueing from Child ViewController

I have a situation where I am presenting a child view controller "B" onto another view controller "A". View controller "B" has 5 buttons which segue(push) to a third view controller, "C". The problem is once I'm at view controller "C", I want to be able to use an unwind segue to go from C-->A without B showing up in the middle.
// view controller A class
class AViewController: UIViewController {
#IBAction func goToViewControllerB(_ sender: UIButton) {
let viewControllerB = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "viewControllerB_ID") as! BViewController
self.addChild(viewControllerB)
viewControllerB.view.frame = self.view.frame
self.view.addSubview(viewControllerB.view)
viewControllerB.didMove(toParent: self)
}
#IBAction func unwindToStart(segue: UIStoryboardSegue) {
print("back from view controller C!")
}
}
//view controller B class
class BViewController: UIViewController {
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let identifier = segue.identifier {
switch identifier {
case "segueFromBToC_1":
if let destVC = segue.destination as? CViewController {
//pass data
}
// ... cases 2-4
case "segueFromBToC_5":
if let destVC = segue.destination as? CViewController {
//pass data
}
default: break
}
}
}
}
// empty CViewController class
I know that putting the following code in my view controller B class will remove it from the parent view controller, "A", but then I can't segue from "B" to "C" anymore as "B" doesn't exist.
self.view.removeFromSuperview()
self.removeFromParent()
self.willMove(toParent: nil)
I was wondering where I should put the above code or if I should segue from a child view controller at all? I also haven't used navigation controllers as I don't know how to implement them with a child view controller. Should I have #IBAction outlets for each of the 5 buttons and put the above code in there?
UPDATE: I was able to fix it by detaching the 5 push segues from the buttons and have each of the 5 push segues go directly from view controller B to view controller C . I then had an #IBAction for each button where I had the following code in view controller B:
class BViewController: UIViewController {
#IBAction func button1ToVC3(_ sender: UIButton) {
segueAndRemoveSelf(segueName: "segueFromBToC_1")
}
#IBAction func button2ToVC3(_ sender: UIButton) {
segueAndRemoveSelf(segueName: "segueFromBToC_2")
}
#IBAction func button3ToVC3(_ sender: UIButton) {
segueAndRemoveSelf(segueName: "segueFromBToC_3")
}
#IBAction func button4ToVC3(_ sender: UIButton) {
segueAndRemoveSelf(segueName: "segueFromBToC_4")
}
#IBAction func button5ToVC3(_ sender: UIButton) {
segueAndRemoveSelf(segueName: "segueFromBToC_5")
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let identifier = segue.identifier {
switch identifier {
case "segueFromBToC_1":
if let destVC = segue.destination as? CViewController {
//pass data
}
// ... cases 2-4
case "segueFromBToC_5":
if let destVC = segue.destination as? CViewController {
//pass data
}
default: break
}
}
}
}
extension BViewController {
func segueAndRemoveSelf(segueName: String) {
self.performSegue(withIdentifier: segueName, sender: self)
self.view.removeFromSuperview()
self.removeFromParent()
self.willMove(toParent: nil)
}
}
Not sure if this is best practice though.

Prepare For Segue on return

So the code I am trying to implement in Swift is based upon this answer here for passing data back from a ViewController:
Passing Data with a Callback
Now my issue is after I have called:
self.navigationController?.popViewController(animated: true)
The Prepare For Segue function isn't called in my original View Controller. I assume it shouldn't be called anyway but from that answer I assume there is a possible way to do so?
First View Controller Snippets
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
//ignore this segue identifier here, this function works when I am showing a new VC
if(segue.identifier == "certSegue"){
let certVC = segue.destination as! CertificateViewController
certVC.profileModel = profileModel
}
//this is what I need to be called
if(segue.identifier == "dpSegue"){
print("dpSegue")
let dpVC = segue.destination as! DatePickerViewController
dpVC.callback = { result in
print(result)
print("Data")
// do something with the result
}
//dpVC.dailyBudgetPassedThrough = "Test"
}
}
func showDatePicker(){
let vc = UIStoryboard.init(name: "Main", bundle: Bundle.main).instantiateViewController(withIdentifier: "DatePickerVC") as? DatePickerViewController
self.navigationController?.pushViewController(vc!, animated: true)
}
Second View Controller
import UIKit
class DatePickerViewController: UIViewController {
var callback : ((String)->())?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
func sendBackUpdate(){
print("Callback")
callback?("Test")
}
#IBAction func cancelButton(_ sender: Any) {
self.navigationController?.popViewController(animated: true)
}
#IBAction func updateButton(_ sender: Any) {
sendBackUpdate()
self.navigationController?.popViewController(animated: true)
}
}
prepareForSegue is called if in Interface Builder a segue is connected
from a table/collection view cell to a destination view controller and the cell is tapped.
from a source view controller to a destination view controller and performSegue(withIdentifier:sender:) is called in the source view controller.
It's not called when a view controller is going to be presented with pushViewController
In your case assign the callback after instantiating the controller in showDatePicker, prepare(for segue is not needed.
func showDatePicker(){
let vc = UIStoryboard(name: "Main", bundle: .main).instantiateViewController(withIdentifier: "DatePickerVC") as! DatePickerViewController
vc.callback = { result in
print(result)
print("Data")
// do something with the result
}
self.navigationController?.pushViewController(vc, animated: true)
}

Accessing methods, actions and/or outlets from other controllers with swift

I'm working on a macOS project where I have a split view containing 2 other ViewControllers and I can't figure out how to access the ViewControllers from my primary window's ViewController.
this is the setup:
Basically what I'm trying to do is use the Button in my ViewController on the top-left to access the Label in my SectionController on the right, which is embedded in my split view.
Since I can't create an IBAction or IBOutlet for a control in a different ViewController, I can't figure out how to get these to be connected. My current workaround has been to have a property on my AppDelegate and then access the main shared application delegate, but that feels hacky and won't scale. I'm completely lost as to how to proceed. I'm ok with using a function to pass data or whatever to the other ViewController(s).
I'm using Swift 4 with Xcode 9 (beta).
Any ideas?
Of course you can't create IBAction or IBOutlet for a control in a different ViewController!!
But simply each view controller in the hierarchy has a reference for its child view controllers.
Method 1:
#IBAction func buttonTapped(_ sender: Any) {
let splitViewController = self.childViewControllers[0] as! YourSplitViewController
let targetViewController = splitViewController.childViewControllers[0] as! YourTargetViewController
targetViewController.label.text = "Whatever!"
}
Method 2:
It may be better if you took a reference for each child controller in your "prepare for segue" method
ContainerViewController:
var mySplitViewController: YourSplitViewController?
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "splitViewSegue" {
self.mySplitViewController = segue.destination as! YourSplitViewController
}
}
YourSplitViewController:
var aViewController: YourFirstViewController?
var bViewController: YourSecondViewController?
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "aViewSegue" {
self.aViewController = segue.destination as! YourFirstViewController
} else if segue.identifier == "bViewSegue" {
self.bViewController = segue.destination as! YourSecondViewController
}
}
So you can access it like that in your container view controller:
#IBAction func buttonTapped(_ sender: Any) {
self.mySplitViewController.firstViewController.label.text = "Whatever!"
}

Passing Data through multiple View Controllers with segue [Swift 3.0 Xcode]

NOTE: Question has been edited in an attempt to be more clear with my issue.
Hey, I am trying to pass data through multiple VCS. I want to pass data (arrays) from V2 -> V3 and then V3 -> V1 but I want to be able to only navigate through the VCs as such: V1 - V2 - V3 and V3 - V2 - V1.
So what I need to learn is how to pass data without navigating to a different VC as well as setting up two preparetosegue methods to pass data between V2 -> V3 and V3 -> V1 while also being able to navigate between all VCs. When I create my first preparetosegue, I am unable to use other segues associated in my VC to navigate to other VCs without getting a Fatal Error.
Can anyone help me?
Any input would be greatly appreciated!
Heres my attempt:
import UIKit
class ViewController: UIViewController {
var name = String()
var StopButInfo = [String]()
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.
}
}
import UIKit
class SecondViewController: UIViewController {
var StringArray = [String]()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBOutlet var PHeight: UITextField!
#IBOutlet var PName: UITextField!
#IBAction func Search(_ sender: Any) {
if PHeight.text != ""{
performSegue(withIdentifier: "SearchSegue", sender: self)}
let CDstart = String(describing: Date())
StringArray.append(CDstart)
StringArray.append(PName.text!)
StringArray.append(PHeight.text!)
}
override func prepare(for SearchSegue: UIStoryboardSegue, sender: Any?){
let thirdController = SearchSegue.destination as! ThirdViewController
thirdController.SearchButInfo = StringArray
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
import UIKit
class ThirdViewController: UIViewController {
var height = String()
var SearchButInfo = [String]()
var StringArray = [String]()
#IBAction func Stop(_ sender: Any) {
if StringArray.count != 0{
performSegue(withIdentifier: "SegueToStart", sender: self)
}
let CDStop = String(describing: Date())
StringArray.append(CDStop)
StringArray.append(height)
}
override func prepare(for SegueToStart: UIStoryboardSegue, sender: Any?){
let firstController = SegueToStart.destination as! ViewController
firstController.StopButInfo = StringArray}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
You need to check which view controller's segue is about to be performed, this can be done like this inside prepare(for segueMVH: UIStoryboardSegue, sender: Any?) method:
if let firstController = segueMVH.destination as! FirstViewController {
// Set first view controller's data
} else if let secondController = segueMVH.destination as! SecondViewController {
// Set second view controller's data
} else if let thirdController = segueMVH.destination as! ThirdViewController {
// Set third view controller's data
}
Note: It's better to use a different identifier for each segue to be able to distinguish between them.
The error you got on let secondController = segueMVH.destination as! ThirdViewController is saying that you are casing HC.ViewController into ThirdViewController and that's why it failed. It means that the destination view controller of your segue is not ThirdViewController.
To further help you understand segue: A segue is a connection between a source view controller and a destination view controller and can be only used between them two. See this picture:
In this case, I have a segue connection in my storyboard connected between my VC1's button and VC2. In this case, my button click will trigger this segue and prepare(for segue) method can only be in VC1 where it passes data to VC2.
So in your situation, you believe that the destination of your segue is ThirdViewController but it's actually not. So please check your segue with identifier segueMVH to see if it is connected between your current view controller and your ThirdViewController.
Hope this helps

Delegate using Container View in Swift

I'm developing an app for iPad Pro. In this app, containerView use to add additional views and interact with them.
First, I created a protocol:
protocol DataViewDelegate {
func setTouch(touch: Bool)
}
Then, I created my first view controller
import UIKit
class ViewController: UIViewController, DataViewDelegate {
#IBOutlet var container: UIView!
#IBOutlet var labelText: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
func setTouch(touch: Bool) {
if touch == true {
labelText.text = "Touch!"
}
}
}
And finally, I created a view that will be embedded in containerView.
import UIKit
class ContainerViewController: UIViewController {
var dataViewDelegate: DataViewDelegate?
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func touchMe(sender: AnyObject) {
dataViewDelegate?. setTouch(true)
}
}
But for some reason, nothing happened, the first view controller receives nothing in setTouch function.
My question is: In this case, using container, how can I make the communication between two ViewsControllers?
Like #nwales said you haven't yet set the delegate. You should do set the delegate in prepareForSegue function on your first viewController (who contain the viewContainer)
First select the embed segue and set an identifier in the attributes inspector.
Then in the parentViewController implement the func prepareForSegue like this:
Swift 4+:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if (segue.identifier == "the identifier") {
let embedVC = segue.destination as! ViewController
embedVC.delegate = self
}
}
Below:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
if (segue.identifier == "the identifier") {
let embedVC = segue.destinationViewController as! ContainerViewController
embedVC.dataViewDelegate = self
}
}
Looks like you defined the delegate, but have not set the delegate. This happens to me all the time.