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.
3-component pickerView
Embedded View that contains 3 separate 1-element pickerViews
1st Label that contains the 1st of 2 numbers calculated and output from a function
2nd Label that contains the 2nd of 2 numbers calculated and output from a function
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.
I am working on CoreData with Swift 3 and I am almost done. I did the CoreData entities and now I am trying to save data from a form on the app and I got this error:
import UIKit
import CoreData
class ItemDetailsVC: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource {
#IBOutlet weak var storePicker: UIPickerView!
#IBOutlet weak var titleField: CustomTextField!
#IBOutlet weak var priceField: CustomTextField!
#IBOutlet weak var detailsField: CustomTextField!
var stores = [Store]()
override func viewDidLoad() {
super.viewDidLoad()
storePicker.delegate = self
storePicker.dataSource = self
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
let store = stores[row]
return store.name
}
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return stores.count
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
// update
}
func getStores() {
let fetchRequest: NSFetchRequest<Store> = Store.fetchRequest()
do {
self.stores = try context.fetch(fetchRequest)
self.storePicker.reloadAllComponents()
} catch {
// some code
}
}
#IBAction func savePressed(_ sender: UIButton) {
let item = Item(context: context)
if let title = titleField.text {
item.title = title
}
if let price = priceField.text {
item.price = (price as NSString).doubleValue
}
if let details = detailsField.text {
item.details = details
}
item.toStore = stores[storePicker.selectedRow(inComponent: 0)] error: cannot assign value of type 'Store' to value of type NSSet?
}
I am not asking anyone to solve the error, I just wanna know what the error is telling me to do. Error is on the item.toStore = stores[storePicker... line
When you set a relationship on CoreData model to To Many it starts behaving like an Array. And like so you need to append the data to that array instead of assigning an class to it. You can do so with:
.addingObjects(from: [Any])
I am not asking anyone to solve the error, I just wanna know what the
error is telling me to do.
From what I have understood is that toStore is a to many relationship from Item to Store entity in CoreData.
Now since you need to know about the error only ,if I am correct and the relationship is exactly above then it means that Item entity has a set of stores associated with it not a single Store , hence you are getting this error
error: cannot assign value of type 'Store' to value of type NSSet?
The problem is your entity has one-to-many relationship, if you want to fix this issue try to change it "to one". I know you don't want the answer but here is the hint on how you can fix this. If you go to your *.xcdatamodeld file and locate your Item entity you can see the attributes relationships...find appropriate relationship and modify it from "to-many" to "to-one"
I have 2 UIPickerViews with different datasource arrays. My problem is that I can't figure out how to display both datasources to my 2 picker views.
func pickerView(pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return firstArray[row]
}
How can I return both of my datasources to my picker views? The logical solution is to recognize if the first picker view is tapped, use firstArray as datasource, and if the second picker view is tapped, use another array.
Guide me on which properties and methods to use please.
The simplest way is to declare both UIPickerViews as #IBOutlet stored properties at the top of the class (be sure to link these up properly in StoryBoard):
class MyVC: UIViewController, UIPickerViewDataSource {
#IBOutlet weak var picker1: UIPickerView?
#IBOutlet weak var picker2: UIPickerView?
/* set up the delegates ... */
func pickerView(pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
switch pickerView {
case picker1: return firstArray[row]
case picker2: return secondArray[row]
default: /* print an error or assertion failure */ return nil
}
}
}
I'm not sure I set this up correctly: you can navigate between calculator view controller to settings view controller and back using the nav bar, however...upon setting either the picker or the segmented control, the data isn't passed back to the calculator view controller via the code below. I think this is because I don't have a corresponding segue on my storyboard pointing back to the calculator view controller. I would do that, but why doesn't the back button show on the story board on settings view controller? If it did I could just control drag back to calculator view controller? I must be missing something?
Also I don't just want the picker/segmented control data to be used once, i.e. I don't want to reset when the user goes back to settings viewcontroller, I want these choices saved as long as the app is open. How do I do that?
The only other existing answers I could find were in Objective C
code:
class settingsViewController: UIViewController, UIPickerViewDataSource, UIPickerViewDelegate {
let titleData = TitleData()
var selectedCounty = String?("Allegany")
var priceMax = Float(1000000)
var priceMin = Float(0)
var payoffMax = Float(1000000)
var payoffMin = Float(0)
#IBOutlet weak var countyPicker: UIPickerView!
#IBOutlet weak var segmentedControl: UISegmentedControl!
override func viewDidLoad() {
super.viewDidLoad()
countyPicker.delegate = self
countyPicker.dataSource = self
}
#IBAction func indexChanged(sender: UISegmentedControl) {
switch segmentedControl.selectedSegmentIndex
{
case 0:
priceMax = 500000.0
priceMin = 0.0
case 1:
priceMax = 1000000.0
priceMin = 500000.0
case 2:
priceMax = 2000000.0
priceMin = 1000000.0
case 3:
priceMax = 5000000.0
priceMin = 2000000.0
default:
break
}
}
func numberOfComponentsInPickerView(pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return titleData.mdCounties.count
}
func pickerView(pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
selectedCounty = titleData.mdCounties[row].name
}
func pickerView(pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String! {
return titleData.mdCounties[row].name!
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
var destViewController: FirstViewController = segue.destinationViewController as! FirstViewController
destViewController.selectedCounty = selectedCounty
destViewController.newPriceMax = priceMax
destViewController.newPriceMin = priceMin
destViewController.newPayoffMax = payoffMax
destViewController.newPayoffMin = payoffMin
}
}
This is a FAQ on stack overflow. It can be generalized as "how do I pass data between ViewControllers." It isn't specific to Swift. The techniques are identical in Objective-C and swift. The only thing that changes is the language you use to implement it.
The most global, flexible way to do this is to create a data container singleton that holds your app state. Set up your singleton to save app state to a file (user defaults, or to a file in your app's documents directory.
Any time you want to save app data, save it to the singleton. Any time you want to read app data, read it from the singleton.
If you have a more specific case where you want to pass data between one view controller and another one that you segue to, you can pass a data object to the destination view controller in prepareForSegue. Then you can change the data object in the second view controller, and when you return from the segue, the data will be changed in the data object.
It appears to be nowhere found. Ive got Core data and needs them load to picker view. How to do it? I think it needs to work with array[String] and not array[AnyObject].
var myList : Array<String> = []
func pickerView(pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String! {
var appDelegate: AppDelegate = UIApplication.sharedApplication().delegate as AppDelegate
var context: NSManagedObjectContext = appDelegate.managedObjectContext!
var request = NSFetchRequest(entityName: "Activity")
var results: Array = context.executeFetchRequest(request, error: nil)!
//???
return myList[row]
}
func pickerView(pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int){
itemActivityValue = myList[row]
}
Do not make fetch requests in the pickerView delegate/datasources. You only need to do fetch once, and you're storing your data in a view controller, so you should do it in viewDidLoad.
The array type is NSManagedObject, not String.
You can't dump NSManagedObject into the pickerView rows. NSManagedObjects will likely have your own variables, for instance, a name attribute. In titleForRow, you want to return something like return myList[row].name, if you've made a subclassed NSManagedObject.
If not, you'll need to do KVC to grab the value.