Setting Picker to default to CoreData value? - ios

I have 2 pickers, the user sets their desired int on them and then saves the object to core data.
Later when they want to edit the object they reload its data into the editor.
I am having an issue where the pickers cannot pull the int back from core data and default themselves to this value, they always default to '0'
Is there a method to make the pickers default to the desired int stored in the object they are editing?
I have the following delegate methods in place:
repsPicker.dataSource = self
repsPicker.delegate = self
setsPicker.dataSource = self
setsPicker.delegate = self
I then try to take the properties from the object and apply them to the pickers, however im using NUMBER? in place of where id assume i need ot put code to access the default values of the pickers, here is where im stuck...
setsPicker.selectedRow(inComponent: Int(userExercise.sets))
repsPicker.selectedRow(inComponent: Int(userExercise.reps))
The data type of userExercise.sets or .reps is Int64 if that helps?
Many thanks for your time and assistance
edit-
here is the component override
func numberOfComponents(in pickerView: UIPickerView) -> Int {
if pickerView == repsPicker {
return 1
} else if pickerView == setsPicker {
return 1
}
return 1
}

you should use selectRow func not selectedRow
func selectRow(_ row: Int,
inComponent component: Int,
animated animated: Bool)
so, it should be
setsPicker.selectRow(Int(userExercise.sets), inComponent :0,animated:false)
repsPicker.selectRow(Int(userExercise.reps), inComponent :0,animated:false)

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 to update a UIPickerview after changing it's data source contents

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

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

How to get the text of a UIPickerView?

I would like to know if i could get the selected String of a PickerView because when using the method func pickerView(pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) i only know the row selected and what i am using is if i know the row i know the position on the array i used to load the PickerViews but i wanted to do this pickerView.text to do something like this
if (pickerView.text == "hello"){
doSomething(pickerView.text);
}
Because now i have to do much more code lines to do something like this
Sorry i forget to mention, i have 3 PickerView and to know the String is in the third picker view i have to see what row is selected in each one
i only know the row selected and what i am using is if i know the row i know the position on the array
And that's the answer. Don't fight the framework; use it. The framework uses Model-View-Controller. The UIPickerView is view; it has no data. You have the data, and given the row, you can fetch it from the model.
extension UIPickerView {
func selectedTitleForComponent(component: Int) -> String? {
let row = selectedRowInComponent(component)
return dataSource.pickerView(pickerView, titleForRow:row, forComponent:component)
}
}
Then:
if pickerView.selectedTitleForComponent(0) == "hello" {
...
}

How to build a variable from another variable

I hope the title makes some sense but what I am trying to do is to set a field value to the item selected variable from my dataPicker. I have been able to make this work when there is only one field to set but my project will have multiple fields on each view that will call data from the dataPicker based on what field it is. I hope that is clear. Maybe as you look at the code it will.
I have set up a test project to limit things to this issue only. So my variable to tell the view what array to populate in the dataPicker is either season or sport. the field that will receive the data from the season/sport array is enterSeason and enterSport. When the picker has returned a value from season, I want to combine it with enter to create the var enterSeason to set that == itemSelected. This language is very new to me so I am trying the only way I have used before to combine text and variables in one value. It is obviously not working. Help is appreciated.
import UIKit
class ViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource, UIGestureRecognizerDelegate {
#IBOutlet var enterSeason: UITextField!
#IBOutlet var enterSport: UITextField!
var dataPickerView = UIPickerView()
var season = ["2013", "2014", "2015"] //multi-season
//var season = ["2015"] //single-season
var sport = ["Baeball", "Football", "Basketball", "Hokey"]
var activeField = []
override func viewDidLoad() {
super.viewDidLoad()
enterSeason.inputView = dataPickerView
dataPickerView.delegate = self
dataPickerView.dataSource = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func numberOfComponentsInPickerView(pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return activeField.count
}
func pickerView(pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String! {
return activeField[row] as! String
}
func pickerView(pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
var itemSelected = activeField[row] as! String
self.enter"\activeField".text = itemSelected
}
}
EDIT : How do you show and hide the picker? Your code anly shows variable declarations and the delegate methods... answers could vary accordingly..
Since you show the picker as text field's input view, set UITextFieldDelegate for each of these text fields .. and in the textFieldDidBeginEditing check which field becomes active with simple if else
func textFieldDidBeginEditing(textField: UITextField) {
if textField === enterSeason {
activeField = season
}
else if textField === enterSport {
activeField = sport
}
}
And in the picker selector, set value of the relevant text field as per current activeField object
func pickerView(pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
if activeField === season {
enterSeason.text = season[row] as! String
}
else if activeField === sport {
enterSeason.text = sport[row] as! String
}
}
Setting the delegate for your text fields in storboard/xib :
P.S.
- Rename activeField to activeDataArray or somethiong more appropriate
EDIT 2 : As you mentioned, second approach i have mentioned below is not suitable for you because there are too many of these fields i am still keeping it as part of the answer as it may help someone else
But what you are trying to achieve is very simple and approach is too convoluted / weird. So heres another way you can implement the whole thing..
The easiest (but still probably not the best) way is to have two instances of the UIPickerView for each field. you can directly check pickerView == seasonPickerView OR pickerView == sportPickerViewin an if else block and do the conditional programming and you wont need the activeField variable..

Resources