Creating 3 columns in UIPickerView, also using append function - swift

This is another question of an app I am trying to develop for a small company. Essentially, I want to make it easier for people to find account numbers. The first column in the PickerView is essentially the department. The second will be the task that he/she is going to do, and the third will be the location. I've figured out how to visually make 3 columns, but i cannot figure out how to put it into the code. By that, I mean how to actually set values/selections for the third column. Furthermore, I'm having some issues trying to connect it with the .append function so that only certain options are presented with certain selections in the column. The append function isn't a necessity, but it would be nice to use it if possible.
This is the first app I have ever coded and I "trained myself", hence my amateur mistakes if any. Any help is appreciated.
import UIKit
cllass Account {
var account: String
var jlCode: [String]
var location: [[String]]
init(account:String, jlCode:[String], location:[[String]]) {
self.jlCode = jlCode
self.account = account
self.location = location
}
}
class ViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource {
#IBOutlet weak var pickerView: UIPickerView!
#IBOutlet weak var accountLbl: UILabel!
#IBOutlet weak var infoLbl: UILabel!
#IBOutlet weak var imageView: UIImageView!
var accounts = [Account]()
override func viewDidLoad() {
pickerView.delegate = self
pickerView.dataSource = self
infoLbl.text = """
test
"""
//A. PARKS - Tasks for account number 12345678910
accounts.append(Account(account: "12345678910", jlCode: ["task 1"], location: [["here"]]))
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 3
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
if component == 0 {
return accounts.count
} else if component == 1 {
let selectedAccount = pickerView.selectedRow(inComponent: 0)
return accounts[selectedAccount].jlCode.count
} else if component == 2 {
let selectedAccount = pickerView.selectedRow(inComponent: 0)
let selectedjlCode = pickerView.selectedRow(inComponent: 1)
let location = pickerView.selectedRow(inComponent: 2)
return accounts[selectedAccount].jlCode[selectedjlCode].location.count
}
return 3
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
if component == 0 {
return accounts[row].account
} else if component == 1 {
let selectedAccount = pickerView.selectedRow(inComponent: 0)
return accounts[selectedAccount].jlCode[row]
} else if component == 2 {
let selectedAccount = pickerView.selectedRow(inComponent: 0)
let selectedjlCode = pickerView.selectedRow(inComponent: 1)
let selectedLocation = pickerView.selectedRow(inComponent: 2)
return accounts[selectedAccount].jlCode[selectedjlCode].location[selectedLocation]
}
return nil
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
pickerView.reloadComponent(1)
let selectedAccount = pickerView.selectedRow(inComponent: 0)
let selectedjlCode = pickerView.selectedRow(inComponent: 1)
let account = accounts[selectedAccount].account
let jlCode = accounts[selectedAccount].jlCode[selectedjlCode]
accountLbl.text = "Account Number: \(account)\nJL Code: \(jlCode)"
}
}

It seems your titleForRow function is somewhat lacking. You are currently only serving component 0 (accounts) and... anything that is not accounts. You need to be more explicit and deliver the texts accordingly.
To help you on your way, and make it easier for you to comprehend what is happening might I suggest that you create these constants in the top of your class?
private let accountsComponent = 0
private let tasksComponent = 1
private let locationsComponent = 2
Then you should alter the titleForRow function to cater for all the above tasks:
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
if component == accountsComponent {
return accounts[row].account
} else if component == tasksComponent {
let selectedAccount = pickerView.selectedRow(inComponent: accountsComponent)
return accounts[selectedAccount].jlCode[row]
} else if component == locationsComponent {
// THIS IS WHAT IS MISSING
// You need to return the location strings from this place.
}
return nil
}
You will need to return the appropriate strings from the if component == locationsComponent part...
[edit2]
OK, since you're just starting out I'll go the extra mile:
It's not obvious that you've grasped exactly what the numberOfRowsInComponent and titleForRow functions are for(?)
The numberOfRows tells the component how many values each component ("column") contains. The titleForRow is where you return the actual text-strings that should be displayed for each row in each component.
So, as I mentioned in my comments, create a Location struct:
struct Location {
var identifier: Int
var label: String
}
In your controller, create an array of this struct.
let locations: [Location] = [Location(identifier: 1, label: "Outdoors"),
Location(identifier: 2, label: "Indoor"),
Location(identifier: 3, label: "Narnia")]
Then your numberOfRows should lean on this array to return the appropriate number of rows for the location-component:
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
// Only including the parts to return the location-stuff
} else if component == 2 {
return locations.count
}
return 0 // This will never be reached as long as the numberOfComponents returns 3
}
and the titleForRow:
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
// Only including the parts to return the location-stuff
} else if component == 2 {
return locations[row].label
}
return nil
}

Related

How to assign numbers for each UIpicker row

I am trying to develop a function in which the user enters a number in a TextField and based on the selection of the picker, it will calculate the number and display it either in a label or another text field. Now keep in mind, the picker has to retain String titles, but it will have different numbers for each row.
import UIKit
class ViewController: UIViewController, UIPickerViewDelegate,
UIPickerViewDataSource {
#IBOutlet weak var numberInput: UITextField!
#IBOutlet weak var pickerControl: UIPickerView!
#IBOutlet weak var pickerControl2: UIPickerView!
#IBOutlet weak var resultField: UILabel!
var pickerData = ["Logistic","Administration","Clinic"]
var pickerData2 = ["Days","Weeks","Months"]
override func viewDidLoad() {
super.viewDidLoad()
pickerControl.delegate = self
pickerControl2.delegate = self
pickerControl.delegate = self
pickerControl2.delegate = self
}
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView,
numberOfRowsInComponent component: Int) -> Int {
if pickerView == pickerControl {
return pickerData.count
} else if pickerView == pickerControl2 {
return pickerData2.count
}
return 1
}
func pickerView(_ pickerView: UIPickerView, titleForRow row:
Int, forComponent component: Int) -> String? {
if pickerView == pickerControl {
return pickerData[row]
} else if pickerView == pickerControl2 {
return pickerData2[row]
}
return ""
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row:
Int, inComponent component: Int) -> String? {
let entry = Float(numberInput.text!) ?? 0.0
if pickerView == pickerControl {
return (Array(pickerData)[row])
}
if pickerView == pickerControl2 {
if Array(pickerData2)[row] == "Days" {
self.resultField.text = String(entry * 3)
}
if Array(pickerData2)[row] == "Weeks" {
self.resultField.text = String(entry * 4)
}
if Array(pickerData2)[row] == "Months" {
self.resultField.text = String(entry * 6)
}
return Array(pickerData2)[row]
}
return ""
}
}
The function signature for pickerView(didSelect...) isn't correct, so it's not getting called. You'll see that Xcode is giving you a warning about it. That function shouldn't be returning anything.
Here's an example of how to change it:
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
let entry = Float(numberInput.text ?? "") ?? 0.0
if pickerView == pickerControl {
return
}
switch pickerData2[row] {
case "Days":
self.resultField.text = String(entry * 3)
case "Weeks":
self.resultField.text = String(entry * 4)
case "Months":
self.resultField.text = String(entry * 6)
default:
break
}
}
You'll note that I also used a switch statement, which I think is a little cleaner looking. But, if you didn't and stuck to your if/else, you don't have to use Array() for everything -- you can do things like if pickerControl2[row] == "Days" { }
You could clean things up even further by defining pickerData2 as:
var pickerData2 : [(title: String, value: Float)] = [(title: "Days",value: 3),(title: "Weeks",value: 4),(title: "Months", value:6)]
Then, in titleForRow, you could return pickerData2[row].title
and later, in didSelectRow:
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
let entry = Float(numberInput.text ?? "") ?? 0.0
if pickerView == pickerControl {
return
}
self.resultField.text = String(entry * pickerData2[row].value)
}

multiple pickerview didSelectRow error in Swift

I have 2 pickerviews, 1 pickerview is to select a crypto-currency from an Array and the other pickerview is used to select a currency from an array. The problem I am having is when I run the app on simulator and select the cryptocurreny in cryptoPicker the app it also selects the same array value from currencyPicker and vice a versa. I don't want the array value of 0 to be pulled from both arrays unless it is selected by the user.
#IBOutlet weak var cryptoCurrentRate: UILabel!
#IBOutlet weak var currencyLabel: UILabel!
#IBOutlet weak var currencyPicker: UIPickerView!
#IBOutlet weak var cryptoPicker: UIPickerView!
#IBOutlet weak var cryptoLabel: UILabel!
var coinManager = CoinManager()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
coinManager.delegate = self
currencyPicker.dataSource = self
currencyPicker.delegate = self
cryptoPicker.dataSource = self
cryptoPicker.delegate = self
}
}
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
if pickerView == currencyPicker {
return coinManager.currencyArray.count
}
if pickerView == cryptoPicker {
return coinManager.cryptoArray.count
}
return 0
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
if pickerView == currencyPicker {
return coinManager.currencyArray[row]
}
if pickerView == cryptoPicker {
return coinManager.cryptoArray[row]
}
return ""
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
let selectedCurrency = coinManager.currencyArray[row]
let selectedCrypto = coinManager.cryptoArray[row]
coinManager.fetchCryptoCoin(assetIdBase: selectedCrypto, assetIdQuote: selectedCurrency)
}
}
Do the same check for picker and in 'didSelectRow', you can do it like this:
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
var selectedCurrency: CurrencyModel? = nil
var selectedCrypt: CryptModel? = nil
if pickerView == currencyPicker {
selectedCurrency = coinManager.currencyArray[row]
}
if pickerView == cryptoPicker {
selectedCrypto = coinManager.cryptoArray[row]
}
coinManager.fetchCryptoCoin(assetIdBase: selectedCrypt == nil ? coinManager.cryptoArray[.zero] : selectedCrypt!, assetIdQuote: selectedCurrency == nil ? coinManager.currencyArray[.zero] : selectedCurrency!)
}
By default it will get the first value from 'coinManager.currencyArray' or 'coinManager.cryptoArray'

Dynamic PickerView with multiple components: Index out of range

I have a PickerView with 3 components based on this code. The two picker to the right change their content based on the previous selection to the left.
Unfortunately the app crashes when I change a parent picker to another one with less rows than the previews one: 🛑 Fatal Error: Index out of range.
I tried to return the child picker to their first position after selecting another parent picker, but it didn't worked out. (Please see under didSelectRow
Do you have another idea?
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 3
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
if component == 0 {
return countries.count
} else if component == 1 {
let selectedCountry = pickerView.selectedRow(inComponent: 0)
return countries[selectedCountry].cities.count
} else {
let selectedCountry = pickerView.selectedRow(inComponent: 0)
let selectedCity = pickerView.selectedRow(inComponent: 1)
return countries[selectedCountry].cities[selectedCity].posts.count
}
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
if component == 0 {
return countries[row].name
} else if component == 1{
let selectedCountry = pickerView.selectedRow(inComponent: 0)
return countries[selectedCountry].cities[row].name
} else {
let selectedCountry = pickerView.selectedRow(inComponent: 0)
let selectedCity = pickerView.selectedRow(inComponent: 1)
return countries[selectedCountry].cities[selectedCity].posts[row]
}
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
pickerView.reloadAllComponents()
if component == 0 {
pickerView.selectRow(0, inComponent: 1, animated: true)
pickerView.selectRow(0, inComponent: 2, animated: true)
} else if component == 1 {
pickerView.selectRow(0, inComponent: 2, animated: true)
}
}
When you call reloadAllComponents() in your didSelectRow you haven't updated the selected rows yet so it's crashing.
Change the order around and it should be fine.
Reset the value of the 1st component of UIPickerView when component 0 is changed might be of interest here as well.
Note pickerView.reloadAllComponents() isn't strictly necessary, as pointed out in the post above. You could just reload the ones that need to change.
A suggestion as well: Your code would be easier to read/maintain if you used say an enum to organize your components so instead of if component == 0 you could have if componentType == .country (or better yet, a switch), for example.

How to use multiple UIPickerViews in one view controller

I am trying to set up 2 UIPickerViews in 1 view controller. I have not been able to find resources that solve my problem.
I have tried following the answer from Diavel Rider on How to use 2 UIPickerView's in one View Controller?. I got some errors from that. I have also tried adding tags but was not able to successfully do that. I am using Swift 4.
Here is the code of my view controller:
import UIKit
class FirstViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource {
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ foodTypeUIPickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
var countrows : Int = nutritionPickerData.count
if pickerView == foodTypeUIPickerView { **[Error Here: "Binary operator '==' cannot be applied to operands of type '_' and 'UIPickerView'"]**
countrows = self.foodTypePickerData.count
}
return countrows
}
func pickerView(_ foodTypeUIPickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
if pickerView == nutritionUIPickerView { **[Error Here: "Binary operator '==' cannot be applied to operands of type '_' and 'UIPickerView'"]**
let titleRow = nutritionPickerData[row]
return titleRow
} else if pickerView == foodTypeUIPickerView { **[Error Here: "Binary operator '==' cannot be applied to operands of type '_' and 'UIPickerView'"]**
let titleRow = foodTypePickerData[row]
return titleRow
}
return ""
}
#IBOutlet weak var nutritionUIPickerView: UIPickerView!
#IBOutlet weak var foodTypeUIPickerView: UIPickerView!
var nutritionPickerData = ["Protein", "Fiber", "Iron", "Fat", "Sodium", "Calcium/Vitamin D", "Energy", "Carbohydrates", "Cholestorol"]
var foodTypePickerData = ["Fruits", "Vegetables", "Legumes and Beans", "Grain", "Meat", "Dairy"]
#IBAction func submitUIButton(_ sender: Any) {
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.nutritionUIPickerView.delegate = self
self.nutritionUIPickerView.dataSource = self
self.foodTypeUIPickerView.delegate = self
self.foodTypeUIPickerView.dataSource = self
}
}
I need to use 2 picker views in 1 view controller. How do I do this?
You are referencing a non-existent variable named pickerView. You should update the parameter name to pickerView to avoid issues.
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
if pickerView == foodTypeUIPickerView {
return self.foodTypePickerData.count
} else {
return self. nutritionPickerData.count
}
}
Make a similar change to the other delegate/data source methods.
You should separate your data sources and delegates and not put them inside your view controller. Set those as the delegate and datasource. Code written in swift 4.2 xcode 10.1
class ViewController: UIViewController {
#IBOutlet weak var foodPicker: UIPickerView!
#IBOutlet weak var nutritionPicker: UIPickerView!
var foodData = FoodData()
var nutritionData = NutritionData()
override func viewDidLoad() {
super.viewDidLoad()
foodPicker.delegate = foodData
foodPicker.dataSource = foodData
nutritionPicker.delegate = nutritionData
nutritionPicker.dataSource = nutritionData
}
#IBAction func actionButton(_ sender: UIButton) {
let food = foodPicker.delegate?.pickerView!(foodPicker, titleForRow: foodPicker.selectedRow(inComponent: 0), forComponent: 0)
let nutrition = nutritionPicker.delegate?.pickerView!(nutritionPicker, titleForRow: nutritionPicker.selectedRow(inComponent: 0), forComponent: 0)
print("You picked: \(food) and \(nutrition)")
}
}
class FoodData: NSObject, UIPickerViewDelegate, UIPickerViewDataSource {
var foodTypePickerData = ["Fruits", "Vegetables", "Legumes and Beans", "Grain", "Meat", "Dairy"]
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return foodTypePickerData.count
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return foodTypePickerData[row]
}
}
class NutritionData: NSObject, UIPickerViewDataSource, UIPickerViewDelegate {
var nutritionPickerData = ["Protein", "Fiber", "Iron", "Fat", "Sodium", "Calcium/Vitamin D", "Energy", "Carbohydrates", "Cholestorol"]
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return nutritionPickerData.count
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return nutritionPickerData[row]
}
}

Two pickers in the same UIViewcontroller

I am making an app where a user has the option to use two UIPickers in the same view controller. How can this be done. I am wanting one picker to display beach names and another to display animals living at the beach.
Thanks for your help
Here's a quick guide to doing this:
1.Initialize pickers, and picker data sets in the class:
var pickerView1 = UIPickerView()
var pickerView2 = UIPickerView()
var pickerView1Data: [String] = ["Waikiki", "Long Beach", ...]
var pickerView2Data: [String] = ["Crab", "Seal", ...]
2.Set delegates, data sources, and tags (in viewDidLoad).
pickerView1.dataSource = self
pickerView1.delegate = self
pickerView1.tag = 1
pickerView2.dataSource = self
pickerView2.delegate = self
pickerView2.tag = 2
3.Set number of rows
func pickerView(pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
var returnIndex: Int = 0
if pickerView.tag == 1 {
returnIndex = pickerView1Data.count
} else if pickerView.tag == 2 {
returnIndex = pickerView2Data.count
}
return returnIndex
}
4.Return data for each row
func pickerView(pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
var returnRow: String!
if pickerView.tag == 1 {
returnRow = pickerView1Data[row]
} else if pickerView.tag == 2 {
returnRow = pickerView2Data[row]
}
return returnRow
}
5.Capture pickerView selection
func pickerView(pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
// This method is triggered whenever the user makes a change to the picker selection.
if pickerView.tag == 1 {
beachTextField.text = pickerView1Data[row]
} else if pickerView.tag == 2 {
animalTextField.text = pickerView2Data[row]
}
}
Of course, this is on top of everything else you have to do to set up picker views, but this should be everything that needs to be taken care of for two picker views.