I'm trying to create a second UIPickerView delegate so I can control them individually instead of using if blocks. Below is an example I put together, I'm unsure of how to connect the second UIPickerView outlet to the second UIPickerView delegate (assuming that is all I am missing).
import UIKit
protocol secondDelegate {
func secondPickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int
func secondNumberOfComponents(in pickerView: UIPickerView) -> Int
}
protocol secondDelegateDataSource {
func secondPickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String?
}
class ViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource, secondDelegate, secondDelegateDataSource {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
p1.delegate = self
p1.dataSource = self
p2.delegate = self
p2.dataSource = self
}
#IBOutlet weak var p1: UIPickerView!
#IBOutlet weak var p2: UIPickerView!
var delegate2: secondDelegate?
var dataSource2: secondDelegateDataSource?
let picker1 = ["1","3","5"]
let picker2 = ["2","4","6"]
// first picker
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return picker1.count
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return picker1[row]
}
// second picker
func secondNumberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func secondPickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return picker2.count
}
func secondPickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return picker2[row]
}
}
So here's the problem: you are saying the words "second delegate", but in fact you still have just one delegate — the view controller.
That's your mistake. Make your deeds match your words. Get all the delegate code out of the view controller. Instead, give your view controller two helper objects — the two picker delegates. Route the dataSource and delegate properties of the two pickers correctly, one to each delegate. Now the messages are coming and going to completely separate places, and you will not have any single bottleneck with if clauses everywhere as you do now.
Here's a simplified example where the two helpers are instance of one class — naturally, nothing requires that you do it that way:
class Helper: NSObject, UIPickerViewDataSource, UIPickerViewDelegate {
let data: [String]
init(data: [String]) {
self.data = data
}
func numberOfComponents(in pickerView: UIPickerView) -> Int {
1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
data.count
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
data[row]
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
print(data[row])
}
}
class ViewController: UIViewController {
#IBOutlet weak var p1: UIPickerView!
#IBOutlet weak var p2: UIPickerView!
let helper1 = Helper(data:["Manny", "Moe", "Jack"])
let helper2 = Helper(data:["Harpo", "Chico", "Groucho"])
override func viewDidLoad() {
super.viewDidLoad()
p1.delegate = helper1
p1.dataSource = helper1
p2.delegate = helper2
p2.dataSource = helper2
}
}
If the two picker views do very different kinds of thing, then you would want two separate Helper classes, each of which acts as a picker view delegate in its own way. Again, the goal here is to avoid having to examine the pickerView in the datasource and delegate methods and decide with an if clause what to do; each Helper type would do just one thing.
Related
I'm new in Swift! and I'm learning from several sources.
I'm building a small app with only 2 elements ( PickerView and label )
I want to know how to let the PickerView row contents to be displayed into a label?
also is there a way to show more information for the row content?
import UIKit
class ViewController: UIViewController {
#IBOutlet var picker: UIPickerView!
let names = ["Sara", "Ali", "Joy"]
override func viewDidLoad() {
super.viewDidLoad()
picker.dataSource = self
picker.delegate = self
}
}
extension ViewController : UIPickerViewDataSource {
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return names.count
}
}
extension ViewController : UIPickerViewDelegate {
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return names[row]
}
}
For example : when Ali is chosen, I want to display "He scores 90" in the label
but I don't want the "He scores 90" sentence to be shown next to Ali in PickerView itself
Thank you
Thank you El Tomato for your hint
it's one of the delegate methods - didSelectRow
and it worked great
import UIKit
class ViewController: UIViewController {
#IBOutlet var picker: UIPickerView!
#IBOutlet var label: UILabel!
let names = ["Sara", "Ali", "Joy"]
override func viewDidLoad() {
super.viewDidLoad()
picker.dataSource = self
picker.delegate = self
}
}
extension ViewController : UIPickerViewDataSource {
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return names.count
}
}
extension ViewController : UIPickerViewDelegate {
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return names[row]
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
label.text = names[row]
}
}
I am trying to set accessibility label for UIPickerView component, but the delegate method is never called. What's more, UIInspector shows the pickerView is not accessible, but it has the label actually set.
The dataSource/delegate methods are fired. Only the UIAccessibilityDelegate is not.
Tested on xcode 10.1 iOS 12.1 simulator
class ViewController: UIViewController, UIPickerViewDataSource, UIPickerViewDelegate, UIPickerViewAccessibilityDelegate {
#IBOutlet weak var pickerView: UIPickerView!
override func viewDidLoad() {
super.viewDidLoad()
pickerView.isAccessibilityElement = true
pickerView.accessibilityLabel = "TESTAccLabel"
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return 5
}
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 2
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return "\(component) - \(row)"
}
func pickerView(_ pickerView: UIPickerView, accessibilityLabelForComponent component: Int) -> String? {
return "TODO:JS"
}
}
this code makes me really mad. I already read thousands of tutorials and stack overflow comments....
Please help me. The Picker just contains a blank grey screen.
Code here:
GenderPickerModel.swift
import UIKit
class GenderPickerModel: NSObject, UIPickerViewDataSource, UIPickerViewDelegate {
let genderPickerData: [String] = ["male", "female"]
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1;
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return genderPickerData.count
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return genderPickerData[row]
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
}
}
and used in a Controller:
#IBOutlet weak var gender: UITextField!
private var genderPickerView: UIPickerView?
override func viewDidLoad() {
super.viewDidLoad()
loadProfile()
prepareView()
}
func prepareView() {
genderPickerView = UIPickerView.init()
let gpModel = GenderPickerModel()
genderPickerView?.delegate = gpModel
genderPickerView?.dataSource = gpModel
gender.inputView = genderPickerView;
genderPickerView?.selectRow(0, inComponent: 0, animated: true)
genderPickerView?.reloadAllComponents()
}
First, you do not need to do the separate array of gender in pickerview class and you don't need to create the instance of pickerview.
From this, you can see your datasource in your custompickerview
/**
Controller code where you can create instantiate picker assign data
*/
class ViewController: UIViewController {
#IBOutlet weak var textField: UITextField!
let genderPickerData : [String] = ["male", "female"]
override func viewDidLoad() {
super.viewDidLoad()
prepareView()
}
func prepareView() {
genderPickerView = UIPickerView()
let gpModel = GenderPickerModel()
gpModel.data = genderPickerData
gpModel.dataSource = gpModel
gpModel.delegate = gpModel
textField.inputView = gpModel
}
}
/**
Custom pickerview class where you can handle data
*/
import UIKit
class GenderPickerModel: UIPickerView, UIPickerViewDataSource, UIPickerViewDelegate {
var data = [String]()
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return data.count
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return data[row]
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
}
}
This is my source code and what I have done is make picker view on storyBoard. Make IBOutlet in this controller by contorl+drag.
It can be compiled however, only '?' appears in the picker view.
Where is the problem?
import UIKit
class SelectViewController: UIViewController, UIPickerViewDataSource, UIPickerViewDelegate{
var songNames = ["test1","test2","test3"]
#IBOutlet weak var songPicker: UIPickerView!
override func viewDidLoad(){
songPicker.delegate = self
songPicker.dataSource = self
}
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return songNames.count
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int) -> String? {
return songNames[row]
}
}
You have missed the forComponent parameter from the dataSource method. Add it in your titleForRow function like this:
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return songNames[row]
}
This should fix the problem you are having.
One short question: on a registration process I would like to ask the user to choose a value from a list of values.
Is it the right way to use a view Controller adding there all text fields and for the values a picker view? As the picker view needs so much space in between the text fields area I wonder what the best practice in this case would be?
this is my code so far:
class RegisterViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource{
#IBOutlet weak var gradeTextField: UITextField!
#IBOutlet weak var gradePicker: UIPickerView!
let gradePickerValues = ["5. Klasse", "6. Klasse", "7. Klasse"]
func numberOfComponentsInPickerView(pickerView: UIPickerView!) -> Int{
return 1
}
func pickerView(pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int{
return gradePickerValues.count
}
func pickerView(pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String! {
return gradePickerValues[row]
}
func pickerView(pickerView: UIPickerView!, didSelectRow row: Int, inComponent component: Int){
gradeTextField.text = gradePickerValues[row]
self.view.endEditing(true)
}
override func viewDidLoad() {
super.viewDidLoad()
statusMessageLabel.hidden = true
gradePicker.dataSource = self
gradePicker.delegate = self
gradePicker.hidden = true
gradeTextField.inputView = UIPickerView()
gradeTextField.text = gradePickerValues[0]
}
The pickerview is hidden at the beginning and appears only when I select the text field, this is fine so far... But the the picker view is empty...
It depends on controller appearance. If there only one choose action per screen it will be better to put Table View on it and selected row will be current selection.
If screen has multiply fields, that user should act with, then, in my opinion, it's better to put label + button above it and when user press this button you just shows Picker View from screen bottom. When user select any row in Picker View you change label text, but don't hide picker itself, it should be done by pressing "Done" button you place above.
Hope this helps.
Update:
Your problem because you just forget to set dataSource property of UIPickerView
Just do: gradePicker.dataSource = self in viewDidLoad()
And don't forget to implements protocol here: class RegisterViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource
Update 2:
Finally made it. If you add UIPickerView in inputView of your textFiled, then It should NOT be in IB. So you could remove it from storyboard (or .xib, if you use it).
Then change code to be something like this:
class RegisterViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource {
#IBOutlet weak var gradeTextField: UITextField!
var gradePicker: UIPickerView!
let gradePickerValues = ["5. Klasse", "6. Klasse", "7. Klasse"]
func numberOfComponentsInPickerView(pickerView: UIPickerView) -> Int{
return 1
}
func pickerView(pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int{
return gradePickerValues.count
}
func pickerView(pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String! {
return gradePickerValues[row]
}
func pickerView(pickerView: UIPickerView!, didSelectRow row: Int, inComponent component: Int){
gradeTextField.text = gradePickerValues[row]
self.view.endEditing(true)
}
override func viewDidLoad() {
super.viewDidLoad()
gradePicker = UIPickerView()
gradePicker.dataSource = self
gradePicker.delegate = self
gradeTextField.inputView = gradePicker
gradeTextField.text = gradePickerValues[0]
}
}
First set delegate
UIPickerViewDataSource,UIPickerViewDelegate
set IBOutlet for UIPickerView
#IBOutlet weak var pickerView: UIPickerView!
Take an array for data
var arrayFruits = [String]()
Write this code on viewDidLoad()
arrayFruits = ["Apple","Banana","Orange","Grapes","Watermelon"]
self.pickerView.dataSource = self
self.pickerView.delegate = self
Write picker view delegate methods:
//MARK: - Pickerview method
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return arrayFruits.count
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return arrayFruits[row]
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
self.labelFruit.text = arrayFruits[row]
}
100% working and tested