Treat an UIPickerView like a combo/select box - ios

I'm working with XCode 8.2.1, Swift 3 and iOS10.
I've a list of items with the following format:
ID | Name
---------
1 | John
2 | Maria
3 | Peter
4 | Roger
The code looks like this:
var formsList = [1:"John", 2:"Maria", 3:"Peter", 4:"Roger"]
What I want to do is to set that data into an UIPickerView, so when someone chooses for example John, the ID 1 is returned, or if someone chooses Peter, the ID 3 is returned.
I do other stuff once I get that ID, that's why I need it.
Any idea or suggestion on how I can achieve this?
Thanks!

You just need to sort your dictionary by its keys and use it as your picker data source:
let formsList = [1:"John", 2:"Maria", 3:"Peter", 4:"Roger"]
let dataSource = formsList.sorted{$0.key<$1.key}
This way you have all your dictionary names sorted in an array including their IDs. Your picker should look something like this:
class ViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource {
#IBOutlet weak var pickerView: UIPickerView!
#IBOutlet weak var label: UILabel!
let formsList = [1:"John", 2:"Maria", 3:"Peter", 4:"Roger"]
var dataSource: [(key: Int, value: String)] = []
override func viewDidLoad() {
super.viewDidLoad()
dataSource = formsList.sorted{$0.key<$1.key}
pickerView.delegate = self
pickerView.dataSource = self
label.text = "id: " + String(dataSource[pickerView.selectedRow(inComponent: 0)].key) + " - " + "name: " + dataSource[pickerView.selectedRow(inComponent: 0)].value
}
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return dataSource.count
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return dataSource[row].value
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
label.text = "id: " + String(dataSource[row].key) + " - " + "name: " + dataSource[row].value
}
}
sample

Your approach of using a dictionary of your data structure is problematic. Dictionaries are, by design, unordered.
There are lots of ways to do this. Most involve creating an array of some sort containing items for each entry from which you want the user to pick.
For example, create an array of tuples:
typealias NameTuple = (id: Int, name: String)
var namesArray: [NameTuple]
Feed your picker view with the name field of each entry in your array. When the user selects an item, use the selected index to fetch that tuple and then get the ID.
You could also use an array of structs, or an array of name objects.

Related

UIPickerView where selected 1st component decides contents of 2nd component is out of sync

I have encountered some synchronisation/graphic update problems with my UIPickerView.
I want a view with 2 components, where the content of the second component depends on the selected row of the first component.
My code is inspired from: Swift UIPickerView 1st component changes 2nd components data
However, while it seems to work, sometimes (not every time) there are some visual problems, as seen on the screenshots below. (on the second screenshot, you can see that the rows of the second component are not really correct, and are a mix of the rows from the first and the second component)
Here is the code:
import UIKit
class AddActivityViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource {
#IBOutlet weak var typePicker: UIPickerView!
var pickerData: [(String,[String])] = []
override func viewDidLoad() {
super.viewDidLoad()
self.typePicker.delegate = self
self.typePicker.dataSource = self
pickerData = [("sport",["bike", "run", "soccer", "basketball"]),
("games",["videogame", "boardgame", "adventuregame"])]
// not sure if necessary
typePicker.reloadAllComponents()
typePicker.selectRow(0, inComponent: 0, animated: false)
// pickerData = [("sport",["bike", "run", "soccer"]),
// ("games",["videogame", "boardgame", "adventuregame"])]
}
// number of columns in Picker
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 2
}
// number of rows per column in Picker
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
print("function 1 called")
if component == 0 {
return pickerData.count
} else {
let selectedRowInFirstComponent = pickerView.selectedRow(inComponent: 0)
return pickerData[selectedRowInFirstComponent].1.count
}
}
// what to show for a specific row (row) and column (component)
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
print("function 2 called with values: component: \(component), row: \(row)")
if component == 0 {
// refresh and reset 2nd component everytime another 1st component is chosen
pickerView.reloadComponent(1)
pickerView.selectRow(0, inComponent: 1, animated: true)
// return the first value of the tuple (so the category name) at index row
return pickerData[row].0
} else {
// component is 1, so we look which row is selected in the first component
let selectedRowInFirstComponent = pickerView.selectedRow(inComponent: 0)
// we check if the selected row is the minimum of the given row index and the amount of elements in a given category tuple array
print("---",row, (pickerData[selectedRowInFirstComponent].1.count)-1)
let safeRowIndex = min(row, (pickerData[selectedRowInFirstComponent].1.count)-1)
return pickerData[selectedRowInFirstComponent].1[safeRowIndex]
}
//return pickerData[component].1[row]
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
// This method is triggered whenever the user makes a change to the picker selection.
// The parameter named row and component represents what was selected.
}
}
Is this a problem with my code or generally a complicated aspect of UIPickers that can not be trivially solved?
Additionally, is there a nicer way to develop this functionality?
I solved the error, however I do not understand why this solves it.
The solution is to imlement the func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int)method, which I did not believe to be necessary just to show the fields.
In other words, just add this to my existing code:
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
if component == 0 {
pickerView.reloadComponent(1)
} else {
let selectedRowInFirstComponent = pickerView.selectedRow(inComponent: 0)
print(pickerData[selectedRowInFirstComponent].1[row])
}
}

How do I select ip using UIPickerView using Swift?

I own a vpn app. Our app comes predefined with one server, this server is hard coded into the app. It looks like this; let serverAddress = "0.0.0.0" (but with a real server ip).
I have made a picker view using the following:
let IPs = ["GER1", "UK1", "GER2", "UK2"]
I now want to know how to say " GER1=192.168.1.2 UK1=192.168.1.3 GER2=192.168.1.4 UK2=192.168.1.5 "
and then parse it to let serverAddress = "0.0.0.0" replacing 0.0.0.0 with the selected ip.
In summary:
I only have 1 server specified, If I ever wanted to change it I'd change the let serverAddress = "0.0.0.0" but I would like to add more servers as an option. so I somehow need to change 0.0.0.0
I personally think picker view is the best way of doing it. But I am open to ideas.
The source code I am working with is here: https://github.com/lxdcn/NEPacketTunnelVPNDemo
First off, you should change the serverAddress to a var instead of a let.
Then, you should use the delegate function: didSelectRow, to change serverAddress to which server address that was selected.
To achieve this, I recommend using a dictionary to store all of your servers to select from like so:
class YourViewController {
// MARK: - Server Address
var serverAddress = "0.0.0.0"
// MARK: Picker View Resources
let serverPicker = UIPickerView() {
didSet {
serverPicker.delegate = self
serverPicker.dataSource = self
}
}
var serverPickerData:[(key: String, value: String)] = [("base", "0.0.0.0"), ("ger1", "192.68.1.2"), ("uk1", "192.68.1.3"), ("ger2", "192.68.1.4"), ("uk2", "192.68.1.5")]
....
}
extension YourViewController: UIPickerViewDelegate, UIPickerViewDataSource {
// MARK: - UIPicker View Delegate
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return serverPickerData.count
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
serverAddress = serverPickerData[row].value
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return "\(serverPickerData[row].key) - \(serverPickerData[row].value)"
}
}
Then whenever you need to add a server to the UIPicker, you can simply push a new dictionary value onto your dictionary

Data in array not showing up in second UIPickerView

I'm a newbie with IOS development so please keep this in mind with my question. I'm working on an app that will show an initial array. Based on the selection of the array, the second pickerview (shown in the next view controller) will use a dictionary and the selected option from the first pickerview to populate the second pickerview with a list of subtopics. The console is showing that all of the data is passing correctly through the process, however the data is not populating within the second pickerview. Can someone take a look at my code and tell me where I'm going wrong?
import UIKit
import Foundation
class ViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource {
#IBOutlet weak var pickerView: UIPickerView!
#IBAction func SubjectSelected(_ sender: UIButton, forEvent event: UIEvent) {
SubjectHandler = subSubjects[SelectedSubject]!
print(SubjectHandler)
print(Subjects)
}
//Create instances of the selected subject & the subject handler (in case SelectedSubject is not empty)
var SelectedSubject: String = ""
var SubjectHandler: Array<String> = []
//Define primary values for both subjects and subSubjects//
let Subjects: Array = ["Macroeconomics", "Microeconomics", "Financial Economics", "Game Theory", "Econometrics", "Law Economics", "Public Sector Economics", "International Economics", "General Statistics"]
let subSubjects = [
"Macroeconomics":
["GDP", "Accounting Methods", "Labor Market", "DMP Job Search Model", "Keynesian Economics", "Sticky-Price Model", "Elasitcity", "Supply and Demand"],
"Microeconomics":
["Elasticity", "Consumer Theory", "Preference Curves", "Competitive Models", "Scarcity"],
"Financial Economics":
["Interest", "Dividends Returns", "Return & Expected Return"],
"Game Theory":
["Nash Equilibrium", "Dominant Strategies", "Iterated Games", "Backwards Induction", "Extensive Form Games"],
"Econometrics":
["Capital Asset Pricing Model", "Regressional Analysis", "Modeling Rules", "Central Limit Theorem", "Probablitiy & Distribution","Heteroscedasticity", "Weighted Least Squares", "Sampling Distributions"],
"Law Economics":
["Externalities", "Damages Calculations", "Property Rights Ownership", "Claims", "Ownership Principles"],
"Public Sector Economics": ["Valuations", "Tax Income Calculations", "Budget Analysis"], "International Economics": ["Taxing Methods", "Importing and Exporting", "Currency Exchange Rates", "Tarrifs", "GDP", "Product Purchase Parity"],
"General Statistics":
["Z-Scores", "Summary Statistics", "One-Tailed Hypothesis Testing", "Two-Tailed Hypothesis Testing", "Confidence Intervals", "Upper and Lower Bounds"]
]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
pickerView.delegate = self
pickerView.dataSource = self
if pickerView.tag == 2 {
pickerView.reloadAllComponents()
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
if pickerView.tag == 1 {
return Subjects.count
}
else {
return SubjectHandler.count
}
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
if pickerView.tag == 1 {
return Subjects[row]
}
else {
return SubjectHandler[row]
}
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
SelectedSubject = Subjects[row]
}
}
As you have said, you use a separate viewController object at the second screen. When it gets loaded it will have an empty SubjectHandler array.
You sould pass the array between viewControllers. See this tutorial

UIPickerView not displaying data even after requery

Let's say I have a stream of dogs private var dogs: Observable<[Dogs]>. Every time a new value is produced my block is called where I create a new dataSource and delegate for my UIPickerView and then within the block I call pickerView.reloadAllComponents() but my view appears with an empty pickerView, even though the dataSource and delegate are queried.
Example code:
self.dataStream
.subscribeNext {
self.dataSource = PickerViewDataSource(data: $0)
self.pickerView.dataSource = self.dataSource
self.delegate = PickerViewDelegate(data: $0, selectedRow: self._selectedRowStream)
self.pickerView.delegate = self.delegate
self.pickerView.reloadAllComponents()
}.addDisposableTo(self.disposeBag)
Debugging the dataSource and delegate I know these are queried and the reason I am keep dataSource and delegate reference in the UIViewController is due to the fact that the UIPickerView holds a weak reference for these.
This one of the last strategies I have tried and still get the same result. Any help would be appreciated. Thanks.
Update:
DogPickerViewDataSource:
class DogPickerViewDataSource: NSObject, UIPickerViewDataSource {
private var dogs: [Dog]
init(
dogs: [Dog]
) {
self.dogs = dogs
}
func numberOfComponentsInPickerView(pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return dogs.count
}
}
DogPickerViewDelegate:
class DogPickerViewDelegate: NSObject, UIPickerViewDelegate {
private var selectedRow: BehaviorSubject<Int>
private var dogs: [Dog]
init(
dogs: [Dog],
selectedRow: BehaviorSubject<Int>
) {
self.dogs = dogs
self.selectedRow = selectedRow
}
func pickerView(pickerView: UIPickerView, attributedTitleForRow row: Int, forComponent component: Int) -> NSAttributedString? {
let dogName = (self.dogs[row].name)!
return NSAttributedString(string: dogName)
}
func pickerView(pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
self.selectedRow.onNext(row)
}
}
If I understand you correctly, on viewDidLoad you're initialising your pickerView with new delegate and data source, calling reloadAllComponents and expecting that everything should be displayed properly. But you're still displaying pickerView from previous view controller. You should try to hide picker view on previous view controller's viewWillDisappear method and display new one in viewDidAppear method of new view controller.

Update Label by using pickerview and text field

I have a PickerView that deposit values. This values will be calculate with a user textfield input. The result will show in a label. That works for me.
By starting the app the textfield have a fix number of 1.
How can I now update my label automatically by use the Pickerview?
I read and try many things but it don´t works.
Maybe it is a little thing, but i don´t see it.
import UIKit
class BalkenbewehrungVC: UIViewController, UIPopoverPresentationControllerDelegate, UIPickerViewDataSource, UIPickerViewDelegate, UITextFieldDelegate {
// Deklarationen Pickerview
var multiplicator : Double = 0.0
let multiplicators = [0.0,6.0,8.0,10.0,12.0,14.0,16.0,20.0,25.0,26.0,28.0,30.0,32.0,36.0,40.0,50.0]
let PI = 3.1415
// Pickerview füllen
var pickerDataSource = ["---","Ø 6","Ø 8","Ø 10","Ø 12","Ø 14","Ø 16","Ø 20","Ø 25","Ø 26","Ø 28","Ø 30","Ø 32","Ø 36","Ø 40","Ø 50"]
// Picker im aktivieren
#IBOutlet weak var Pickerview: UIPickerView!
// Ausgabewert As = xxxx cm
#IBOutlet var AusgabePicker: UILabel!
// Eingabe Anzahl Stäbe
#IBOutlet var AnzahlStab: UITextField!
#IBAction func test(sender: UITextField) {
if AnzahlStab.text!.isEmpty {
AnzahlStab.text = "1"
let result1 = (multiplicator / 10) * (multiplicator / 10) * PI / 4 * 1
AusgabePicker.text! = String(format:"%.3f", result1)
} else {
let result = (multiplicator / 10) * (multiplicator / 10) * PI / 4 * (AnzahlStab.text! as NSString).doubleValue
// let resultString = "\(result)"
AusgabePicker.text = String(format:"%.3f", result)
}
}
// Picker - feste Reihen
func numberOfComponentsInPickerView(pickerView: UIPickerView) -> Int {
return 1
}
// Picker - Aufaddierung neuer Reihen
func pickerView(pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return pickerDataSource.count
}
// Picker - Datenübernahme multiplicator in Picker
func pickerView(pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return pickerDataSource[row]
}
// Picker - Wertefestlegung Picker
func pickerView(pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
multiplicator = multiplicators[row]
_ = NSNumberFormatter.localizedStringFromNumber(NSNumber(double: multiplicator), numberStyle:.DecimalStyle)
}
In the didSelectRow method for the pickerView, set the label's text to your newly calculated value.
Update: To update the label's text depending on the textField's input, inside your ViewController's viewDidLoad method, add this line
AnzahlStab.delegate = self
and then add the textFieldDidEndEditing method, and inside it update the label's text to the new value. This method is called when you are done with editing the text field.
Need to call function test in picker view didSelectRow. Or Simply Replace your didSelectRow with following code.
func pickerView(pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
multiplicator = multiplicators[row]
_ = NSNumberFormatter.localizedStringFromNumber(NSNumber(double: multiplicator), numberStyle:.DecimalStyle)
self.test(AnzahlStab)
}
Hope this Answer will help you.

Resources