如何在同一视图中使用具有不同数据源的多个选取器视图?
Posted
技术标签:
【中文标题】如何在同一视图中使用具有不同数据源的多个选取器视图?【英文标题】:How do I use multiple picker views with different data sources in the same view? 【发布时间】:2017-05-25 03:16:11 【问题描述】:我有一个包含三个选择器视图的视图。其中两个选取器视图具有相同的数据,一个编号为 1 到 100 的数组。第三个选取器视图具有一个包含模型铁路轨道制造商列表的数组。我已经使用我在这个网站上找到的方法标记了选择器视图,但是当我运行应用程序时,所有三个选择器视图都有 1 到 100 作为它们的数据。我还从所有选择器视图中控制拖动到视图顶部的黄色圆圈,然后单击 dataSource 和 delegate。如何在一个视图中使用具有不同数据源的多个选取器视图?此外,为了使代码运行,我必须从所有与选择器视图相关的 @IBOutlet 语句中删除 weak。这是一件坏事吗?我对代码比较陌生。谢谢。
Picker View Scene Screen Shot
import UIKit
class ViewController: UIViewController, UIPickerViewDataSource, UIPickerViewDelegate
//MARK: Properties
@IBOutlet var layoutLengthPickerView: UIPickerView!
@IBOutlet var layoutWidthPickerView: UIPickerView!
@IBOutlet var trackPickerView: UIPickerView!
override func viewDidLoad()
super.viewDidLoad()
layoutLengthPickerView = UIPickerView()
layoutWidthPickerView = UIPickerView()
trackPickerView = UIPickerView()
layoutLengthPickerView.tag = 0
layoutWidthPickerView.tag = 1
trackPickerView.tag = 2
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.tag == 0
return numbers[row]
else if pickerView.tag == 1
return numbers[row]
else if pickerView.tag == 2
return manufacturers[row]
return ""
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int
if pickerView.tag == 0
return numbers.count
else if pickerView.tag == 1
return numbers.count
else if pickerView.tag == 2
return manufacturers.count
return 1
【问题讨论】:
去掉viewDidLoad
中创建新选择器视图的三行。这些台词弄乱了你在故事板中创建的那些。
也不要使用tag
,使用pickerView == layoutLengthPickerView
之类的东西
您还可以拥有一个包含多个组件的选择器。有时拥有多个选择器会导致用户体验混乱。
【参考方案1】:
在处理具有委托和数据源的多个控件时,您应该考虑通过为多个选择器的委托创建单独的对象来避免视图控制器膨胀(即,本着single responsibility principle 的精神)。这使这个逻辑脱离了视图控制器本身,并避免了单个繁琐的 UIPickerViewDataSource
和 UIPickerViewDelegate
方法尝试使用毛茸茸的 if
-else
或 switch
语句为多个选择器提供服务。
例如,这里有一个视图控制器,它有两个选择器的出口,但不是用代码来阻碍视图控制器来管理这些选择器的 dataSource
和 delegate
,您可以为每个选择器设置单独的对象,视图控制器所要做的就是说出哪个委托对象将处理哪个选择器:
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)")
唯一的技巧是确保保持对这些委托对象的强引用,如上所示,因为选择器本身只有对其委托的弱引用,这是最佳实践。
并且选择器委托方法的实现更加简洁:
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]
现在,这显然仍然是一个简化的示例,但好处在于随着代码变得越来越复杂,细节被封装在单独的对象中,而不是用所有代码来阻碍单个视图控制器。
如果需要,您可以让视图控制器向委托/数据源对象提供字符串列表。事实上,这简化了它,因为您只需要一个类作为选择器委托,并且您只需为每个选择器实例化一个不同的类:
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]
【讨论】:
谢谢。这非常有效。几个问题。对于我的layoutLengthPickerView
和layoutWidthPickerView
类,我是否需要在这两个类中声明numbers
?或者我是否在两个班级之外声明numbers
?另外,如果选择器视图没有按钮或任何可点击的对象,为什么要调用 IBAction func didTapButton
方法?最后,为什么我们在所有选择器视图委托类中都调用NSObject
?谢谢。
1.您可以更改这些类,以便将要显示的值作为您在init
方法中捕获的参数传递。 2. didTapButton
只是视图控制器如何捕获选择器视图中选择的值的示例。如果你不想要,你就不需要那个。做你想做的。 3.UIPickerViewDelegate
和UIPickerViewDataSource
协议是NSObjectProtocol
,所以这些对象必须是NSObject
子类。这就是 UIKit 协议的实现方式(为了与 Objective-C 兼容)。
这更有意义。您能解释一下 1(如何将数组放入 init
方法中)吗?我是初学者。谢谢。
@W.Cook - 查看关于如何将数据传递给选取器委托对象的扩展答案。
谢谢。运行得很漂亮。【参考方案2】:
如果您IBOutlet
连接,则不需要标签。所有的 IBOutlet 都应该是弱的,我们一般对 IBOutlets(UIViewController 的 Childs)使用弱。这是因为子对象只需要与父对象一样存在。
如果您将storyboard
或Nib
用于UIPickerView
,则无需为UIPickerView
进行分配。
试试这个:
@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
输出:
【讨论】:
感谢您的解释。这现在更有意义了。【参考方案3】:作为一种替代方法,您可以只使用一个选择器视图并执行相同的操作。
@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
【讨论】:
以上是关于如何在同一视图中使用具有不同数据源的多个选取器视图?的主要内容,如果未能解决你的问题,请参考以下文章