如何在一个视图控制器中使用两个 UIPickerViews?

Posted

技术标签:

【中文标题】如何在一个视图控制器中使用两个 UIPickerViews?【英文标题】:How to use two UIPickerViews in one view controller? 【发布时间】:2014-12-24 21:04:57 【问题描述】:

我在一个视图控制器中有两个UIPickerControllers。我可以让一个工作,但是当我添加第二个时,我的应用程序崩溃了。这是我用于一个选择器视图的代码:

import UIKit

class RegisterJobPosition: UIViewController, UIPickerViewDelegate 

    @IBOutlet weak var positionLabel: UILabel!

    var position = ["Lifeguard", "Instructor", "Supervisor"]

    override func viewDidLoad() 
        super.viewDidLoad()
    

    func numberOfComponentsInPickerView(PickerView: UIPickerView!) -> Int
    
        return 1
    

    func pickerView(pickerView: UIPickerView!, numberOfRowsInComponent component: Int) -> Int
    
        return position.count
    

    func pickerView(pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String!
    
        return position[row]
    

    func pickerView(pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int)         
        positionLabel.text = position[row]
    

现在,我怎样才能让第二个选择器工作?假设我的第二个选择器视图称为location(另一个称为position)。我尝试在 location 的选择器视图方法中复制代码,但它不起作用。

【问题讨论】:

如果您的视图控制器是处理用户交互的唯一代表,您将需要一种方法来区分这两者。你能发布你用来区分它们的代码吗? 我只是复制代码。比如在numberOfRowsInComponent方法中,我只是放了:return location.count 这部分是有道理的。但是,您如何以编程方式确定要返回计数的选择器实例?如果您有两个选择器实例向您的单个视图控制器(委托)发送消息,您需要一种方法来识别选择器,以便您可以执行适当的分支逻辑并为正确的选择器返回正确的计数。 啊....我无法设置“虚拟”变量来告诉应用程序我想要哪个选择器,因为我希望两者同时可用.....我还能怎么做? 我写过一篇文章,通过标签属性来区分多个UIActionSheets:bit.ly/DistinguishUIActionSheetsSwift -- 虽然不完全一样,但场景是相似的。 【参考方案1】:

这是我的解决方案:

在情节提要中,将两个 UIPickerView 实例添加到您的视图中 将第一个选择器的标签设置为1,并为“属性检查器”下的第二个选择器设置2 control + 从每个选择器拖动到顶部的黄色视图控制器图标并选择dataSource。重复同样的选择delegate

UIPickerViewDataSourceUIPickerViewDelegate 添加到您的视图控制器:

class ViewController: UIViewController, UIPickerViewDataSource, UIPickerViewDelegate 

在您的视图控制器类中,为选择器创建空数组:

var picker1Options = []
var picker2Options = []

viewDidLoad() 中,使用您的内容填充数组:

picker1Options = ["Option 1","Option 2","Option 3","Option 4","Option 5"]
picker2Options = ["Item 1","Item 2","Item 3","Item 4","Item 5"]

实现委托和数据源方法:

func numberOfComponentsInPickerView(pickerView: UIPickerView) -> Int 
    return 1


func pickerView(pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int 
    if pickerView.tag == 1 
        return picker1Options.count
     else 
        return picker2Options.count
    


func pickerView(pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String! 
    if pickerView.tag == 1 
        return "\(picker1Options[row])"
     else 
        return "\(picker2Options[row])"
    

【讨论】:

感谢标签建议,这对我使用多个选择器真的很有帮助。我现在解决了。 这让我大吃一惊,在同一个 ViewController 中填充多个 PickerView 是多么困难。在 android 上它超级简单。 Lazar K 这是一个非常可恶的评论。它既不简单也不复杂。这只是一个不同的策略。在 Android 上,您会(或应该)为不同的 Spinner 使用不同的适配器。实际上,您也可以在 ios 上使用不同的数据源和委托。我实际上会在答案中提出这个建议 不使用容易出错的标签,您可以在类中只使用两个选择器视图属性(例如@IBOutlet var locationPickerViewpositionPickerView)并检查委托/数据中是否有pickerView源方法等于任一属性。 类似于Diavel's的回答。【参考方案2】:

根据我在问题中的信息,我想说您需要设置数据源和委托方法来处理区分哪个选择器实例正在调用它们的能力。

选择器视图上的Using the tag property 是一种策略。

方法中应该有一些 if/else 或 switch 语句,这些语句具有不同的逻辑,具体取决于所引用的是位置还是位置选择器。

【讨论】:

关于 if/else 语句,我什至如何让一个变量成为一个告诉要使用的位置或位置选择器的值? 所以,如果你在storyboard的pickers上设置了tag,你可以在callback方法中测试。看到方法中的第一个参数是pickerView了吗?您可以检查 pickerView.tag 并根据标签与您的 UI 和数据数组的相关性执行正确的逻辑。【参考方案3】:

我发现这行得通。

class SecondViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource 

    @IBOutlet weak var textbox1: UILabel!
    @IBOutlet weak var textbox2: UILabel!

    @IBOutlet weak var dropdown1: UIPickerView!
    @IBOutlet weak var dropdown2: UIPickerView!

    var age = ["10-20", "20-30", "30-40"]
    var Gender = ["Male", "Female"]

    func numberOfComponents(in pickerView: UIPickerView) -> Int 
        return 1
    

    func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int 
        var countrows : Int = age.count
        if pickerView == dropdown2 
            countrows = self.Gender.count
        

        return countrows
    

    func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? 
        if pickerView == dropdown1 
            let titleRow = age[row]
             return titleRow
         else if pickerView == dropdown2 
            let titleRow = Gender[row]
            return titleRow
        

        return ""
    

    func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) 
        if pickerView == dropdown1 
            self.textbox1.text = self.age[row]
         else if pickerView == dropdown2             
            self.textbox2.text = self.Gender[row]
        
    

【讨论】:

这个方案我觉得比使用标签好很多【参考方案4】:

我的背景是 Android,但我的回答非常 OOP。我建议创建不同的类来实现 DataSource 和 Delegate,如下所示:

class PositionDataSourceDelegate : NSObject, UIPickerViewDelegate, UIPickerViewDataSource 
   var position = ["Lifeguard", "Instructor", "Supervisor"]
   var selectedPosition : String?

    func numberOfComponentsInPickerView(pickerView: UIPickerView) -> Int 
       return 1
    

    func pickerView(pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int 
      return position.count
    

    func pickerView(pickerView: UIPickerView, attributedTitleForRow row: Int, forComponent component: Int) -> NSAttributedString? 
       return position[row]
    

    func pickerView(pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) 
       selectedPosition = position[row]
    

然后是另一个位置:

class LocationDataSourceDelegate : NSObject, UIPickerViewDelegate, UIPickerViewDataSource 
   var location = ["Up", "Down", "Everywhere"]
   var selectedLocation : String?

    func numberOfComponentsInPickerView(pickerView: UIPickerView) -> Int 
        return 1
    

    func pickerView(pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int 
        return location.count
    

    func pickerView(pickerView: UIPickerView, attributedTitleForRow row: Int, forComponent component: Int) -> NSAttributedString? 
         return location[row]
    

    func pickerView(pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) 
        selectedLocation = location[row]
   

然后在您的 RegisterJobPosition 中,您需要为每个创建一个实例:

let positionDSD = PositionDataSourceDelegate()
let locationDSD = LocationDataSourceDelegate()

并像这样将它们分配给选择器:

positionPicker.dataSource = positionDSD
positionPicker.delegate = positionDSD
locationPicker.dataSource = locationDSD
locationPicker.delegate = locationDSD

您可以使用以下方式访问所选位置和位置:

positionDSD.selectedPosition 
locationDSD.selectedLocation

希望这对您和其他人有所帮助,我也希望有一些建设性的方法来解释为什么这不是“swifty”

【讨论】:

这是一个很好的 OO 解决方案,但它实际上并不能在实践中工作——我是 iOS 新手,但整天都在尝试解决这个问题,在我看来 UIPickerViewDelegate /DataSource 也必须是 UIViewController。普通的 NSObject 不起作用。 UIPickerViewDelegateUIPickerViewDataSource 可以应用于任何NSObject。刚刚测试了它,它似乎工作正常。看不出为什么这些特定协议不适用于非 UIViewController,因为这违背了 SDK 的设计方式。将数据源分解为自己的类是一个好主意,但目前没有足够多的 iOS 开发人员这样做。 如果您正在使用 Storyboards,您可以进一步采纳 Matei 的建议。将“对象”拖到您的 VC 上并输入 PositionDataSourceDelegate。在您的 VC 代码中创建一个连接到该对象的插座(打开助手故事板旁边的窗口和CTRL从故事板拖动到VC代码。您可以调用出口位置DSD)。将 Picker 的数据源和委托方法连接到对象。最后删除 let positionDSD =.. 和 positionPicker.dataSource =... positionPicker.delegate =【参考方案5】:

我认为与 Java 不同的最大问题是 Java 很容易允许通过构造函数传递属性。例如您可以将类 LocationDataSourceDelegate 声明为泛型并将其称为 genericDataSourceDelegate 并使构造函数接受和 Array public genericDataSourceDelegate (String data[]) 并能够创建一个可以简单地创建对象的类。您只需实例化它并将 location 传递给构造函数,如 genericDataSourceDelegate (location)

您的模型存在问题,您将不得不在一个程序中创建尽可能多的委托类,这对您的编译器来说是一种压力。

【讨论】:

以上是关于如何在一个视图控制器中使用两个 UIPickerViews?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用按钮使用两个表格视图

如何动态增加滚动视图中两个表格视图的高度

如何使用自动布局在两个子视图控制器之间移动?

如何将 xib 加载到一个视图控制器中的两个容器视图中

如何将列表和字符串从一个控制器的两个操作传递到一个视图?如何在视图文件中单独获取这些数据?

如何在另一个视图中使用已经初始化的类?