I have 4 UIPickerViews, which I act on changes in each individual picker views by using if statements for each picker view name. For one of the pickers, a timer with h:m:s, I am trying to add labels for the time units using this answer.
It works great with a single picker, but when I add into my main code with the other 3 pickers, I only want to return a value for the alarmPicker. I need a return of type UIView outside the if statement, but I'm struggling on what it would be if I'm only creating UILabels for the alarmPicker. If I return UIView() the other pickers do not populate their titleForRow strings.
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
if pickerView == picker1 {
// code
}
if pickerView == picker2 {
// code
}
if pickerView == picker3 {
// code
}
if pickerView == alarmPicker {
if let label = pickerView.view(forRow: row, forComponent: component) as? UILabel {
if component == 0, row > 1 {
label.text = String(row) + " hours"
}
else if component == 0 {
label.text = String(row) + " hour"
}
else if component == 1 {
label.text = String(row) + " min"
}
else if component == 2 {
label.text = String(row) + " sec"
}
}
}
}
func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView {
if pickerView == alarmPicker {
let label = UILabel()
label.text = String(row)
label.textAlignment = .center
return label
}
return UIView()
}
I think it would be best to create a dedicated delegate for each picker, at least for the alarm picker. This will improve your code, because you don't need all these if pickerView == picker1 / picker2 / picker3 / picker4 cascades.
You could also create a base class for the common picker delegate work, and derive a alarm picker delegate with individual code.
Related
I have mutliple textFields in a view that have there input view set to a pickerView, however depending on the textField there is a different amount of rows of selection that should appear. This works fine when you click outside the picker view and then click on the next textField but causes a crash when you just click on the next textField (This is because the pickerView does not change and so the index becomes out of range). Thanks for the help!
To dismiss the pickerView whenever tapped outside (does not do anything if another textField is tapped):
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self.view, action: #selector(UIView.endEditing(_:))))
What I mean by when I say different textFields show different amount of rows:
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
if ticketOneTextField.isFirstResponder == true {
if shownEvent?.ticketTypeOnePrice == "0.00" {
return 2
} else {
return 11
}
} else if ticketTwoTextField.isFirstResponder == true {
if shownEvent?.ticketTypeTwoPrice == "0.00" {
return 2
} else {
return 11
}
} else if ticketThreeTextField.isFirstResponder == true {
if shownEvent?.ticketTypeThreePrice == "0.00" {
return 2
} else {
return 11
}
} else {
return 2
}
}
Add in viewDidLoad()
when a user starts editing below function is called.
textField.addTarget(self, action:#selector(textDidBeginEditing(_:)), forControlEvents: UIControlEvents.EditingDidBegin)
Then implement this:
func textDidBeginEditing(sender:UITextField) -> Void
{
if sender == ticketOneTextField {
//show pickerview
}
else if sender == ticketOneTextField {
//show pickerview
}
else if sender == ticketOneTextField {
//show pickerview
}
}
// this pickerview delegate is called when user selects a row.
optional func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
//after user selects the value end the editing
self.view.endEditing(true)
}
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.
How to restrict the UIPickerView component scrolling.
I have two components in UIPickeView one second component ends scroll allows user to touch the first component to change the value in it.
How can I restrict the user to touch component one until and unless the second component scrolling complemented.
Using this extension, you can check UIPickerView is scrolling or not. Depend on scrolling, you can enable/disable interaction with UIPickerView.
extension UIView {
func isScrolling () -> Bool {
if let scrollView = self as? UIScrollView {
if (scrollView.isDragging || scrollView.isDecelerating) {
return true
}
}
for subview in self.subviews {
if subview.isScrolling() {
return true
}
}
return false
}
}
UIPickerViewDelegate to detect scrolling or not.
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
if pickerView.isScrolling() {
pickerView.isUserInteractionEnabled = false
} else {
pickerView.isUserInteractionEnabled = true
}
//Use this for reduce lines
//pickerView.isUserInteractionEnabled = !pickerView.isScrolling()
return "Row \(row)"
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
pickerView.isUserInteractionEnabled = true
}
The only problem is you can not touch current scrolling component, until component completed scrolling. HOPE, someone solve this also.
Attempting to right/left align the text in two components in a UIPickerView. It's being displayed correctly inside of the selected zone, but the text is overlapping elsewhere.
Any idea what I'm doing wrong here?
func pickerView(pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusingView view: UIView?) -> UIView {
let pickerLabel = UILabel()
var titleData: String!
if component == 0 {
if pickerView.tag == 0 {
titleData = weightData[row].description
} else {
titleData = bodyFatData[row].description
}
pickerLabel.textAlignment = .Right
} else {
titleData = decimalData[row].description
pickerLabel.textAlignment = .Left
}
let myTitle = NSAttributedString(string: titleData, attributes: [NSFontAttributeName:UIFont.systemFontOfSize(22.0),NSForegroundColorAttributeName:
UIColor.blackColor()])
pickerLabel.attributedText = myTitle
return pickerLabel
}
EDIT:
When I add this line
pickerLabel.frame.size = CGSizeMake(165, 26)
It does have an effect, but the selected/unselected rows are still misaligned.
I think you are simply overlooking the possibilities of this function:
// Swift
func pickerView(pickerView: UIPickerView, widthForComponent component: Int) -> CGFloat {
return CGFloat(50.0)
}
By default the components will center align, and it appears the whole picker will also center align. If you omit the textAlignment, all the values in a component (column) will line up.
In my app I have a text field (Distance) that has segmented control to choose the unit reference (Km/Mi):
I have another text field (pace) that show a UIPickerView. This picker view can be populated with 2 arrays based on what unit the user selected. Everything works except that, even when the picker view is not shown and I change the selection, the array change only when I start scroll the picker view. So initially it shows the array in km even if I selected Mi and then it changes when I start moving the picker.
Initially I set a variable for the unit reference and the 2 arrays
var unitReference = "Km" // this is the initial selection on the segmented control
let paceKmArray = [" ", "Relaxing(less than 15km/h)", "Easy(15km/h-20km/h)","Medium(20km/h-25km/h)","Nice(25km/h-30km/h)","Fast(30km/h-35km/h)", "Very Fast(>35km/h)"]
let paceMiArray = [" ", "Relaxing(less than 10 mi/h)", "Easy(10mi/h-13mi/h)","Medium(13mi/h-16mi/h))","Nice(16mi/h-19mi/h))","Fast(19mi/h-12mi/h))", "Very Fast(>22mi/h)"]
then in viewDidLoad
unitReferenceSegmentedControl.addTarget(self, action: "unitChanged:", forControlEvents: .ValueChanged);
which call this method to changed the unit reference
func unitChanged(sender:UISegmentedControl){
if sender.selectedSegmentIndex == 0{
unitReference = "Km"
print(unitReference)
}
if sender.selectedSegmentIndex == 1{
unitReference = "Mi"
print(unitReference)
}
}
And the picker view methods
func numberOfComponentsInPickerView(pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
if pickerView.tag == 0{
return rideTypeArray[row]
}
if pickerView.tag == 1{
if unitReference == "Km"{
return paceKmArray[row]
}
if unitReference == "Mi"{
return paceMiArray[row]
}
}
return ""
}
func pickerView(pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int{
if pickerView.tag == 0{
return rideTypeArray.count
}
if pickerView.tag == 1{
if self.unitReference == "Km"{
return paceKmArray.count
}
if self.unitReference == "Mi"{
return paceMiArray.count
}
}
return 0
}
func pickerView(pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int)
{
if pickerView.tag == 0{
rideTypeTextField.text = rideTypeArray[rideTypePickerView!.selectedRowInComponent(0)] as String
}
if pickerView.tag == 1{
if unitReference == "Km"{
paceTextField.text = paceKmArray[pacePickerView!.selectedRowInComponent(0)] as String
}
if unitReference == "Mi"{
paceTextField.text = paceMiArray[pacePickerView!.selectedRowInComponent(0)] as String
}
}
}
I am not sure this is the best way to do it. If there is a more elegant way to do it, I will be more than happy to learn it.
You are not seeing the changes in your picker view because you are not reloading the data after the datasource is changed. Just call reloadAllComponents on your pickerView once you have changed the datasource and you are good to go.
func unitChanged(sender:UISegmentedControl) {
//Your previous code for changing the datasource array.
//Now reload the UIPickerView
pacePickerView.reloadAllComponents()
}
One more suggestion i would like to give is that if in both the datasource array's have same set of key value pairs then you should keep a 3rd array as the ultimate datasource and switch it with the respective arrays from your unitChanged: method. That way you won't need to have an if condition every now and then to get the current set of data.