Swift 5:
I am trying to pass the result of a function, called in an embedded View, to the parent ViewController, so as to change a label at the bottom of the parent ViewController.
Other than descriptive text, my primary ViewController contains four elements.
In other words, there are 6 picker components visible on the screen (1 picker with 3 components and 3 1-component pickers). But the 3 single pickers are in an embedded view.
At this time, when I release the picker of any of the 3 components in the 3-component picker, the function is called, it correctly makes a set of calculations, based on the picker delegate selection and the 2 results of that function changes the two labels at the bottom of the screen accordingly. (i.e. 1 function passes 2 results.)
What I'm trying to do is trigger the same function calls, when any of the pickers in the embedded view are released and pass those two results to the appropriate two labels in the primary ViewController.
This is the code in the primary View Controller titled "SecondViewController" that works (I'm leaving out the data arrays, to save space):
@IBOutlet weak var spendingPickerView: UIPickerView!
@IBOutlet weak var taxRateLbl: UILabel!
@IBOutlet weak var taxPaidLbl: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
spendingPickerView.delegate = self
spendingPickerView.dataSource = self
spendingPickerView.selectRow(5, inComponent: 2, animated: true)
}
}
extension SecondViewController: UIPickerViewDataSource {
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 3
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return 10
}
/*func pickerView(_ spendingPickerView: UIPickerView, attributedTitleForRow row: Int, forComponent component: Int) -> NSAttributedString? {
return NSAttributedString(string: digits[row], attributes: [NSAttributedString.Key.foregroundColor : UIColor.white])
}*/
}
extension SecondViewController: UIPickerViewDelegate {
/*func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
if pickerView.tag == 2{
return adults[row]
}
if pickerView.tag == 3{
return minors[row]
}
if pickerView.tag == 4{
return region[row]
}
return ""
}*/
func pickerView(_ spendingPickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView {
// let view = UIView(frame: CGRect(x: 0, y: 0, width: 82, height: 28))
let digitsLbl = UILabel(frame: CGRect(x: 0, y: 0, width: 0, height: 107))
digitsLbl.text = digits[row]
digitsLbl.textColor = .white
digitsLbl.font = UIFont.boldSystemFont(ofSize: 24)
return digitsLbl
}
func pickerView(_ spendingPickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
spendingPickerView.reloadComponent(1)
// Calculation functions for output.
taxPaidLbl.text = "$\(rateAndTaxCalc(retailSpend: retailDollar, adultIH: adultInHome, minorIH: minorInHhome, regionOfUSA: regionOfUSA).1)"
taxRateLbl.text = "\(rateAndTaxCalc(retailSpend: retailDollar, adultIH: adultInHome, minorIH: minorInHhome, regionOfUSA: regionOfUSA).2)%"
}
This is the code from the embedded View titled SecondPickerViewController from which I'm trying to pass data to "SecondViewController":
@IBOutlet weak var adultPickerView: UIPickerView!
@IBOutlet weak var minorPickerView: UIPickerView!
@IBOutlet weak var regionPickerView: UIPickerView!
override func viewDidLoad() {
super.viewDidLoad()
adultPickerView.delegate = self
adultPickerView.dataSource = self
minorPickerView.delegate = self
minorPickerView.dataSource = self
regionPickerView.delegate = self
regionPickerView.dataSource = self
}
}
extension SecondPickerViewController: UIPickerViewDataSource {
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
var wheel = 0
if pickerView.tag == 2 {
wheel = 4 }
else if pickerView.tag == 3 {
wheel = 11 }
else if pickerView.tag == 4 {
wheel = 3 }
return wheel
}
}
extension SecondPickerViewController: UIPickerViewDelegate {
func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView {
let returnLbl = UILabel(frame: CGRect(x: 0, y: 0, width: 0, height: 107))
if pickerView.tag == 2 {
returnLbl.text = adults[row]
returnLbl.textColor = .white
returnLbl.textAlignment = .center
returnLbl.font = UIFont.boldSystemFont(ofSize: 24)
}
else if pickerView.tag == 3 {
returnLbl.text = minors[row]
returnLbl.textColor = .white
returnLbl.textAlignment = .center
returnLbl.font = UIFont.boldSystemFont(ofSize: 24)
}
else if pickerView.tag == 4 {
returnLbl.text = region[row]
returnLbl.textColor = .white
returnLbl.textAlignment = .center
returnLbl.font = UIFont.boldSystemFont(ofSize: 24)
}
// This output needs to be sent to "SecondViewController". /////////////////
// This output needs to be sent to "SecondViewController". /////////////////
// This output needs to be sent to "SecondViewController". /////////////////
taxPaidTmp = "$\(rateAndTaxCalc(retailSpend: retailDollar, adultIH: adultInHome, minorIH: minorInHhome, regionOfUSA: regionOfUSA).1)"
taxRateTmp = "\(rateAndTaxCalc(retailSpend: retailDollar, adultIH: adultInHome, minorIH: minorInHhome, regionOfUSA: regionOfUSA).2)%"
return returnLbl
}
}
The function that is being called (rateAndTaxCalc) is in a separate file and it outputs the proper data, when called from "SecondViewController".
All 6 of the picker components in both views appear to be functioning properly. I just need to get the output of the two function calls for the 3 pickers on the embedded view, to be passed to the "SecondViewController", to change "taxRateLbl" and "taxPaidLbl".
Full disclosure: I am new to Swift (2 weeks), but I have programmed in 19 other programming languages in my life, going all the way back to FortranIV, COBOL, and 8080 machine language. So although I'm new to Swift, picking up a new programming language is typically no more difficult for me than a mechanic picking up a different shaped pair of pliers. I suppose that what I'm saying is that I could probably figure this issue out on my own, if I had the time, but time is an issue. I'm trying to get this done before normal life resumes post-virus. So any help will be appreciated. Thank you.
If I have understood you correctly, what you want to achieve is to call the same functions that the system would call for the large picker view, but for the small pickers.
For instances like this, there are multiple ways that you can accomplish this, and I will list a few below:
The Straightforward Way (The Bad Way)
Since you know that FirstViewController
is a parent of the SecondViewController
, you can simply write (parent as? FirstViewController)?.myMethod()
in the second view controller and that will deliver the method to your first view controller.
However, you should almost NEVER write codes like this because it makes the assumption that SecondaryViewController
is always a subcontroller of FirstViewController
and making assumptions like this and having this fixed type casts will dramatically reduce the flexibility of your code.(You should know this already if you've coded for a long time, no matter which language)
The Proper Way
The proper way to pass any event in Swift, or the iOS system, is via one of the two methods: delegation or notification. The difference is that, while delegate can pass event one-to-one, using a notification can let you broadcast the event to multiple objects. As per the use case here, we'll stick to the delegation method since one-to-one is exactly what you need.
To do this, you would first declare a protocol, say, SecondViewControllerDelegate
and add a method like secondViewController(_ viewController: SecondViewController, pickerDidChange: UIPickerView)
. You can see that the style of this delegate method matches those found in the iOS API's, where the first parameter is always the object that's sending the call. Then, you'd add a property in SecondViewController
called delegate
and have it of type SecondViewControllerDelegate?
, and then you make FirstViewController
conform to this delegate and set itself as the delegate of the second view controller.
In code:
// the `class` here means only classes(not structs or enums) can conform to it
// so we later have a weak reference to it
protocol SecondViewControllerDelegate: class {
func secondViewController(_ viewController: SecondViewController, pickerDidChange: UIPickerView)
}
class FirstViewController: UIViewController, SecondViewControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
mySecondViewController.delegate = self
}
func secondViewController(_ viewController: SecondViewController, pickerDidChange: UIPickerView) {
// Here SecondViewController tell me about the change
}
}
class SecondViewController: UIViewController {
// use weak to avoid reference cycle and memory leaks
weak var delegate: SecondViewControllerDelegate?
func pickerChangedSomehow() {
// when your picker changes, call your delegate
delegate?.secondViewController(self, pickerDidChange: myPicker)
}
}
Bonus: The Clever Way
Since in the first view controller performs the function on UIPickerDelegate
event call, and so does the second view controller, the clever way here is to make your first view controller the delegate for ALL FOUR picker views(if you didn't know, an object, can be the delegate of many other objects, and that's why the sender of the delegate is passed as the first parameter), so no matter which picker view is changed, you'd always get the delegate call in your first view controller, making everything much easier.