How to update a UIPickerview after changing it's data source contents - ios

I have 2 UIPickerViews in an ios program which I am running on an iPad simulator.
They have one component in each.
I find the relevant picker view by using a switch on the tag. The two single component views need to be changed by adding or deleting components.
This is easy enough in the data source with
pickerData.append(textInput)
pickerData.sort()
pickerData.reloadAllComponents
and
pickerData.remove(at: lastDataSelected)
picker.reloadAllComponents()
where lastDataSelected is the row integer.
This works to change the data source but not entirely when transferred to the UIPickerViews.
The UIPickerView display is not updated until I scroll the view. To be more precise, the item selected is correct but the text label is not updated. After scrolling the data labels are all showing correctly.
I have tried to programatically scroll from one end to the other but this does not help.
So how can I tell the program to update the view without the user scrolling it?
picker.reloadInputViews() does not help.
Apart from this the number of items (rows) isn't changed to reflect the changes in the picker data so the last item falls off the list when adding a new one.
So the second question is how to get the UIPickerView functions to update the number of rows?
I haven't been able to find any examples of dynamically updated picker views so hope someone can help or point me in the right direction.
The remaining code is fairly standard I believe but I'm obviously missing something in the update process.
override func viewDidLoad() {
super.viewDidLoad()
flvPicker = UIPickerView()
flvPicker.delegate = self
flvPicker.dataSource = self
flvPicker.tag = 0
}
func numberOfComponents(in pickerView: UIPickerView) -> Int {
switch pickerView.tag {
case 0:
return 1
case 1:
etc...
}
}
var numberOfRowsInComponent = 0
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
switch pickerView.tag {
case 0:
return flvPickerData.count
case 1:
etc...
}
}
func pickerView(_
pickerView: UIPickerView,
titleForRow row: Int, forComponent component: Int) -> String? {
switch pickerView.tag {
case 0:
return flvPickerData[row]
case 1:
etc...
}
}
func pickerView( _ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
switch pickerView.tag {
case 0:
flavourSelected = flvPickerData[row]
lastFlavourSelected = row
case 1: etc...
}
}
I think the question is really how to get the UIPickerView to update correctly after making changes to it's data source and therefore row count.

You can use reloadComponent(_:) method from UIPickerView.

A little late to the party, but if you want to update a picker view, there's also the method selectRow. This also has the benefit of an animation property, so you can animate any updates.
Example:
for (index, day) in weeklyOptions[0].enumerated() {
if scheduledTime.contains(day) {
weeklyDatePicker.selectRow(index, inComponent: 0, animated: true)
}
}

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])
}
}

UIPickerView selecting row in wrong component (exorcism required?)

I don't know what I've done to anger the gods but my UIPickerView is behaving abnormally. It has 2 components, for month and year values, and selecting a value on the right component (year) causes the left component (month) to change value as well.
I initially create the UIPickerView using the following code:
pickerView = UIPickerView(frame: ...)
pickerView.delegate = self
pickerView.dataSource = self
Then I implement some simple delegate / data source methods to handle the picker view, as follows:
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
if component == 0 {
expirationMonth = months[row]
} else {
expirationYear = years[row]
}
expirationTextField?.text = "\(expirationMonth ?? "")/\(expirationYear?.substring(from: 2) ?? "")"
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
if component == 0 {
return months[row]
} else {
return years[row]
}
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
if component == 0 {
return months.count
} else {
return years.count
}
}
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 2
}
Additionally, the UIPickerView data source method numberOfComponents is being called even when I don't set the picker view's data source.
I have no idea what's causing this behavior. I've tried pretty much everything and nothing is stopping it from changing both column's values. Does anyone have any ideas to fix this?
Turns out it's a bug in the iOS simulator, because when running it on my iPhone it worked like a charm. I thought I was going crazy there for a second!
I'm going to file a bug complaint in the morning...Good luck to anyone else with this issue.

PickerView Default row selected but returns zero unless the picker view is moved

In my picker view I want the default row to be zero. This row has a value of 1. I want to be able to touch nothing on the view contoller except a button. I know there are similar questions but they did not work for me.
override func viewWillAppear(_ animated: Bool) {
self.pickerView.delegate = self
self.pickerView.dataSource = self
self.pickerView.selectRow(0, inComponent: 0, animated: true)
}
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return String(numbers[row])
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return numbers.count
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
therow = pickerView.selectedRow(inComponent: 0) + 1
}
then
#IBAction func submitTapped(_ sender: Any) {
Print (therow)
}
When I tap submit and print the value at row 0 it is 0, but if I wiggle the picker view and put it back on row 0 then it prints 1. I need to be able to touch nothing on the picker view and have it return the proper value of the default row.
You should use the row that the pickerview delegate method gives you , so you should modify your code as follows:
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
therow = numbers[row]
//theRowIndex = row //this is the index of row that you selected
}
e.g if numbers array is numbers = [1, 2, 3, 4], when you click on first row above code will set therow to be 1 and if you click on second row, it will set therow to be 2 and so on.
if you want to use the code that you wrote then you can use as follows:
therow = numbers[pickerView.selectedRow(inComponent: 0)]
this will give you the number for selected row , but I think you dont need it inside the above method.
Now if you dont want to touch the picker then I think you need to do this:
#IBAction func submitTapped(_ sender: Any) {
therow = numbers[self.pickerView.selectedRow(inComponent: 0)]
print(therow)
}
Use this statement once you load your picker view with data.
yourPicker.selectRow(0, inComponent:0, animated:true)
You can change the default selected value by changing the first parameter of selectRow.
I think the reason why this happens is that didSelectRow is somehow not called if you selected the row programmatically. As per the docs:
Called by the picker view when the user selects a row in a component.
So you need to set your therow property programmatically after you call selectRow:
self.pickerView.selectRow(0, inComponent: 0, animated: true)
therow = 1 // <--- this line

Return numberOfRowsInComponent & titleForRow in UIPickerview based on another UIpickerview's didSelectRow

Good day to all,
I tried explaining my query in the subject line.
As I understand and from what I see in the code, all pickerviews are initialized in ViewDidLoad(), which means that all have their data set.
As part of my data validation, which in this case is extremely important, I want to set the following pickerviews according to the previous ones selection.
Where I get stuck is to populate the following pickerview's titles with the 'new' data and reloading the view.
Ideally I would like the views to appear as previous selections are made.
If you know how to do this logically please let me know, I will appreciate any help I can get at the moment!
I'm not really sure what you mean by logically, but you just use if statements and switches to determine what's been selected and what to present based on that selection. Then you reload the data.
func pickerView(pickerView: UIPickerView,
didSelectRow row: Int,
inComponent component: Int)
{
if component == 0
{
switch row{
case 0:
self.titlesForComponentOne = ["One","Two","Three","Four"]
case 1:
self.titlesForComponentOne = ["One","Two"]
default:
self.titlesForComponentOne = ["One","Three","Four"]
}
}else if component == 1
{
switch row{
case 0:
self.titlesForComponentTwo = ["Three", "Four"]
case 1:
….
self.picker.reloadAllComponents() // or just ones that need to be updated
}
func pickerView( pickerView: UIPickerView,
numberOfRowsInComponent component: Int) -> Int
{
switch component{
case 0:
return self.titlesForComponentZero.count
case 1:
return self.titlesForComponentOne.count
case 2:
….
}

UIPickerView is Selecting the Wrong Row-Swift

So I have UIPickerView that isn't working the way I want it to. It works correctly about 3/4 of the time, but the other 1/4 it doesn't.
func numberOfComponentsInPickerView(pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return listsArray.count
}
func pickerView(pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusingView view: UIView!) -> UIView
{
activeQuizPlace = row
var pickerLabel = UILabel()
pickerLabel.textColor = UIColor.whiteColor()
pickerLabel.text = listsArray[row]
pickerLabel.font = UIFont(name: "Georgia", size: 22)
pickerLabel.textAlignment = NSTextAlignment.Center
return pickerLabel
}
The listsArray contains 5 items(which are strings). Rows 0-2 work correctly 100% of the time. However when row 3 is selected it occasionally says that row 1 was, and when row 4 is selected it sometimes says that row 2 was. I think the problem has something to do with the last function I have that controls the UIPickerView. I just copied that code from this website as a way to change the color and font of the text. Before I was using this function I never had a problem with it, or at least never noticed one. Side note: I am using Xcode 6.3.2
I need a way for the picker view to work correctly, as well as to be able to change its font and color. I would love help with this I've been looking for answers online for 2 hours and can't figure out what I'm doing wrong, thanks in advance!
I think you need to remove the line activeQuizPlace = row from the code you have shown and then add this function below.
func pickerView(pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
activeQuizPlace = row
}
The function you were using is not called with the index that the user selects, it is called with the index that the system needs for drawing the views.
The suggested function should be the one you are after (hopefully).

Resources