How do I use multiple picker views with different data sources in the same view?

后端 未结 3 1927
孤城傲影
孤城傲影 2020-12-18 09:06

I have a view with three picker views in it. Two of the picker views have the same data, an array with the numbers 1 to 100. The third picker view has an array with a list

相关标签:
3条回答
  • 2020-12-18 09:22

    When dealing with multiple controls with delegates and data sources, you should consider avoiding view controller bloat (i.e., in the spirit of the single responsibility principle) by creating separate objects for the delegates of the multiple pickers. This keeps this logic out of the view controller, itself, and avoids single cumbersome UIPickerViewDataSource and UIPickerViewDelegate methods trying to service multiple pickers with hairy if-else or switch statements.

    For example, here is a view controller that has outlets for two pickers, but rather than encumbering the view controller with code to manage the dataSource and delegate for these pickers, you can have separate objects for each picker, and all the view controller has to do is to say which delegate object will handle which picker:

    class ViewController: UIViewController {
    
        @IBOutlet weak var namePicker: UIPickerView!
        @IBOutlet weak var numberPicker: UIPickerView!
    
        let namePickerDelegate = NamePickerDelegate()
        let numberPickerDelegate = NumberPickerDelegate()
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            namePicker.delegate = namePickerDelegate
            namePicker.dataSource = namePickerDelegate
    
            numberPicker.delegate = numberPickerDelegate
            numberPicker.dataSource = numberPickerDelegate
        }
    
        @IBAction func didTapButton(_ sender: Any) {
            let nameValue = namePicker.selectedRow(inComponent: 0)
            let numberValue = numberPicker.selectedRow(inComponent: 0)
    
            print("\(nameValue); \(numberValue)")
        }
    
    }
    

    The only trick is to make sure to keep a strong reference to those delegate objects, as shown above, because the picker, itself, only has weak references to its delegate, as is best practice.

    And the implementation of the picker delegate methods is much cleaner:

    class NamePickerDelegate: NSObject, UIPickerViewDataSource, UIPickerViewDelegate {
        let names = ["Mo", "Larry", "Curley"]
    
        func numberOfComponents(in pickerView: UIPickerView) -> Int {
            return 1
        }
    
        func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
            return names.count
        }
    
        func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
            return names[row]
        }
    }
    
    class NumberPickerDelegate: NSObject, UIPickerViewDataSource, UIPickerViewDelegate {
        let numbers: [String] = {
            let formatter = NumberFormatter()
            formatter.numberStyle = .spellOut
            return (0 ..< 100).compactMap { formatter.string(for: $0) }  // use `flatMap` in Xcode versions prior to 9.3
        }()
    
        func numberOfComponents(in pickerView: UIPickerView) -> Int {
            return 1
        }
    
        func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
            return numbers.count
        }
    
        func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
            return numbers[row]
        }
    }
    

    Now, this is still, obviously, a simplified example, but the beauty is that as the code gets more complicated, the details are encapsulated within separate objects, rather than encumbering a single view controller with all the code.


    If you want, you can have the view controller provide the list of strings to the delegate/data source object. In fact, that simplifies it because you need only one class for the picker delegate, and you just instantiate a different one for each picker:

    class ViewController: UIViewController {
    
        let names = ["Mo", "Larry", "Curley"]
    
        let numbers: [String] = {
            let formatter = NumberFormatter()
            formatter.numberStyle = .spellOut
            return (0 ..< 100).compactMap { formatter.string(for: $0) }  // use `flatMap` in Xcode versions prior to 9.3
        }()
    
        @IBOutlet weak var numberPickerOne: UIPickerView!
        @IBOutlet weak var numberPickerTwo: UIPickerView!
        @IBOutlet weak var namePicker: UIPickerView!
    
        lazy var numberPickerOneDelegate: PickerDelegate = PickerDelegate(strings: self.numbers)
        lazy var numberPickerTwoDelegate: PickerDelegate = PickerDelegate(strings: self.numbers)
        lazy var namePickerDelegate:PickerDelegate = PickerDelegate(strings: self.names)
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            numberPickerOne.delegate   = numberPickerOneDelegate
            numberPickerOne.dataSource = numberPickerOneDelegate
    
            numberPickerTwo.delegate   = numberPickerTwoDelegate
            numberPickerTwo.dataSource = numberPickerTwoDelegate
    
            namePicker.delegate        = namePickerDelegate
            namePicker.dataSource      = namePickerDelegate
        }
    
    }
    
    class PickerDelegate: NSObject, UIPickerViewDataSource, UIPickerViewDelegate {
        let strings: [String]
    
        init(strings: [String]) {
            self.strings = strings
            super.init()
        }
    
        func numberOfComponents(in pickerView: UIPickerView) -> Int {
            return 1
        }
    
        func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
            return strings.count
        }
    
        func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
            return strings[row]
        }
    }
    
    0 讨论(0)
  • 2020-12-18 09:25

    If you IBOutlet the connections, you don't need the Tags. All IBOutlet should be weak, We generally use weak for IBOutlets (UIViewController's Childs).This works because the child object only needs to exist as long as the parent object does.

    If you're using storyboard or Nib for UIPickerView, you don't need to do the allocation for UIPickerView.

    Try this:

    @IBOutlet weak var trackPickerView: UIPickerView!
    @IBOutlet weak var layoutWidthPickerView: UIPickerView!
    @IBOutlet weak var layoutLengthPickerView: UIPickerView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        trackPickerView.delegate = self
        layoutWidthPickerView.delegate = self
        layoutLengthPickerView.delegate = self
    }
    
    let numbers = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32", "33", "34", "35", "36", "37", "38", "39", "40", "41", "42", "43", "44", "45", "46", "47", "48", "49", "50", "51", "52", "53", "54", "55", "56", "57", "58", "59", "60", "61", "62", "63", "64", "65", "66", "67", "68", "69", "70", "71", "72", "73", "74", "75", "76", "77", "78", "79", "80", "81", "82", "83", "84", "85", "86", "87", "88", "89", "90", "91", "92", "93", "94", "95", "96", "97", "98", "99", "100"]
    
    let manufacturers = ["Atlas True Track", "Atlas Code 100", "Atlas Code 83", "Bachmann Nickel Silver", "Bachmann Steel Alloy", "Kato", "Life-Like Trains Code 100", "LIfe-Like Trains Power-Loc", "Peco Code 100", "Peco Code 83", "Peco Code 75", "Shinohara Code 100", "Shinohara Code 70", "Walthers"]
    
    func numberOfComponents(in pickerView: UIPickerView) -> Int {
        return 1
    }
    
    func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
    
        if pickerView == layoutWidthPickerView || pickerView == layoutLengthPickerView {
            return numbers[row]
        }
    
        return manufacturers[row]
    }
    
    func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
    
        if pickerView == layoutWidthPickerView || pickerView == layoutLengthPickerView {
            return numbers.count
        }
    
        return manufacturers.count
    
    }
    

    Output:

    0 讨论(0)
  • 2020-12-18 09:33

    As an alternate way of doing that you can use only one picker view and to do the same thing.

    @IBOutlet weak var inputBank: UITextField!    
    @IBOutlet weak var inputBranch: UITextField! // Those 2 are the fields I need to set the picker view
    
    
    @IBOutlet weak var inputAccountNumber: UITextField! // this is an extra textField
    
    private var availableBankDetails: AvailableBankDetails?
    private var pickerBanks: [String]?
    private var pickerBranches: [String]?
    private var pickerView: UIPickerView
    
    private var profileDomain: ProfileDomain
    
    required init?(coder aDecoder: NSCoder) {
        self.profileDomain = ProfileDomain()
        self.pickerView = UIPickerView()
        super.init(coder: aDecoder)
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.pickerView.delegate = self
        createBankPickerView()
        createBranchPickerView()
    }
    
    private func loadAvailableBankDetails(completion: @escaping (Bool) -> ()) {
        profileDomain.getAvailableBanks { (boolResponse, jsonResponseBody) in
            if boolResponse {
                self.availableBankDetails = AvailableBankDetails(availableBankDetails: jsonResponseBody)
                self.pickerBanks = self.availableBankDetails!.getAvailableBankDetails()
                completion(true)
            } else {
                self.view.makeToast(jsonResponseBody["message"].stringValue, duration: 3, position: .bottom, title: "Error")
                completion(false)
            }
    
        }
    }
    
    private func loadAvailableBranchDetails(bankName: String) -> [String] {
        pickerBranches = availableBankDetails?.getAvailableBranches(bank: bankName)
        return (availableBankDetails?.getAvailableBranches(bank: bankName))!
    }
    
    private func createBankPickerView() {
        loadAvailableBankDetails { (boolResponse) in
            if boolResponse {
                self.pickerView.selectRow(1, inComponent: 0, animated: true)
                self.inputBank.inputView = self.pickerView
                self.pickerView.backgroundColor = UIColor.lightGray
                self.createToolbar(inputBankOrBranch: self.inputBank)
            } else {
                print("error")
            }
        }
    
    }
    
    private func createBranchPickerView() {
        self.pickerView.selectRow(1, inComponent: 0, animated: true)
        self.inputBranch.inputView = self.pickerView
        self.pickerView.backgroundColor = UIColor.lightGray
        self.createToolbar(inputBankOrBranch: self.inputBranch)
    }
    
    func createToolbar(inputBankOrBranch: UITextField) {
      let toolbar = UIToolbar()
      toolbar.sizeToFit()
      toolbar.tintColor = UIColor.darkGray
      toolbar.backgroundColor = UIColor.blue
      let doneButton = UIBarButtonItem(title: "Done", style: .plain, target: self, action: #selector(self.closePickerView))
      toolbar.setItems([doneButton], animated: false)
      toolbar.isUserInteractionEnabled = true
      inputBankOrBranch.inputAccessoryView = toolbar
    }
    
    @objc func closePickerView() {
        view.endEditing(true)
    }
    
    func numberOfComponents(in pickerView: UIPickerView) -> Int {
        return 1
    }
    
    func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
        if (inputBank.isFirstResponder) {
            return pickerBanks!.count
        } else{
            if pickerBranches == nil {
                return 0
            }
            return pickerBranches!.count
        }
    }
    
    func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
        if (inputBank.isFirstResponder){
            return pickerBanks![row]
        } else {
            return pickerBranches![row]
        }
    }
    
    func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
        if inputBank.isFirstResponder {
            inputBank.text =  pickerBanks![row]
            inputBranch.text = ""
            self.pickerBranches = loadAvailableBranchDetails(bankName: inputBank.text!)
        } else {
            if pickerBranches == nil {
                inputBranch.text = ""
            } else {
                inputBranch.text = pickerBranches![row]
            }
        }        
    }
    
    func pickerView(_ pickerView: UIPickerView, widthForComponent component: Int) -> CGFloat {
        return 100.0
    }
    
    func pickerView(_ pickerView: UIPickerView, rowHeightForComponent component: Int) -> CGFloat {
        return 60.0
    }
    
    func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView {
    
    
        var label:UILabel
    
        if let v = view as? UILabel{
            label = v
        }
        else{
            label = UILabel()
        }
    
        label.textColor = UIColor.black
        label.textAlignment = .left
        label.font = UIFont(name: "Helvetica", size: 16)
    
    
    
        if inputBank.isFirstResponder {
            label.text = pickerBanks![row]
        } else {
            label.text = pickerBranches![row]
        }
    
        return label
    }
    
    0 讨论(0)
提交回复
热议问题