【问题标题】:How do I add section titles in a UIPickerView?如何在 UIPickerView 中添加部分标题?
【发布时间】:2016-12-29 22:12:14
【问题描述】:

这绝不是需要的东西。如果可能的话会很好。我不确定这是否可以做到。

我有一个UIPickerView,它将有 41-42 个选项。现在,我按字母顺序排列了所有选项。我希望他们被分成几组,在每组之前我希望它有一个标题。类似于 TableView 具有部分的方式,您可以为每个部分指定一个标题。我想要选择器中每个部分的标题,但我不希望它成为可选行。例如:

选择器选项:
核心(不可选择)
野蛮人(可选)
吟游诗人(可选)
更多选项
APG(不可选择)
炼金术士(可选)
骑士(可选)
更多选项
继续几个

这甚至可能吗?

【问题讨论】:

  • 你可以这样做
  • 您能详细说明一下吗?除了 Duncan C 建议的方法之外,还有其他方法吗?有教程链接吗?
  • 等一下..制作一些快速示例代码

标签: ios uipickerview


【解决方案1】:

您不需要子类化 UIPickerView 来实现这一点,而是需要仔细实现数据源和委托。

我建议你有一个实现这两者的类,作为一个测试,我这样做了:

import UIKit

class PickerViewSource: NSObject, UIPickerViewDelegate, UIPickerViewDataSource {

    init(pickerView: UIPickerView) {
        super.init()
        pickerView.dataSource = self
        pickerView.delegate = self
    }

    var selected: (([String: Any]) -> Void)?

    let data = [
        ["title": "Group a", "selectable": false],
        ["title": "title a1", "selectable": true],
        ["title": "title a2", "selectable": true],
        ["title": "title a3", "selectable": true],
        ["title": "Group b", "selectable": false],
        ["title": "title b1", "selectable": true],
        ["title": "title b2", "selectable": true],
        ["title": "Group c", "selectable": false],
        ["title": "title c1", "selectable": true],
        ["title": "title c2", "selectable": true],
        ]

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

    func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
        return data.count
    }

    func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView {

        let d = data[row]
        if let selectable = d["selectable"] as? Bool, selectable == true {

            if let view = view as? ItemView, let title = d["title"] as? String{
                view.label.text = title
                return view
            }
            let view = ItemView()
            if let title = d["title"] as? String{
                view.label.text = title                    
            }
            return view
        }

        if let selectable = d["selectable"] as? Bool, selectable == false {
            if let view = view as? GroupView, let title = d["title"] as? String{
                view.label.text = title
                return view
            }
            let view = GroupView()
            if let title = d["title"] as? String{
                view.label.text = title                    
            }
            return view

        }
        return UIView()
    }

    func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
        var index = row
        if let selectable = data[row]["selectable"] as? Bool, selectable == false {
            index += 1
            pickerView.selectRow(index, inComponent: 0, animated: true)
        }
        selected?(data[index])
    }

}

如你所见,这个类有一个回调var selected: (([String: Any]) -> Void)?。只有可选项目才会调用它。

ViewController 实例化源并设置回调:

class ViewController: UIViewController {

    var pickerViewSource: PickerViewSource?

    @IBOutlet weak var pickerView: UIPickerView! {
        didSet{
            pickerViewSource = PickerViewSource(pickerView: pickerView)

            pickerViewSource?.selected = {
                selected in

                print(selected)
            }
        }
    }
}

为了完整起见,观点:

class BaseView: UIView {
    var label: UILabel = UILabel()

    override func layoutSubviews() {
        super.layoutSubviews()

        self.addSubview(label)
        label.frame = self.bounds
    }
}

class GroupView: BaseView {
    override func layoutSubviews() {
        super.layoutSubviews()
        label.backgroundColor = .orange
    }

}

class ItemView: BaseView {

}


您可以允许选择组,但不要发送选择回调,而不是通过选择下一行来强制选择。但是为了确保触发没有设置任何可选择的内容,您应该添加一个取消选择回调。

var selectedElement: [String:Any]?

var deselected: (([String: Any]) -> Void)?

func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {

    if let element = selectedElement {
        deselected?(element)
        selectedElement = nil
    }

    if let selectable = data[row]["selectable"] as? Bool, selectable == true {
        let element = data[row]
        selected?(element)
        selectedElement = element
    }

}

现在您可以使用回调 selecteddeselected 来更改您的用户界面,即启用或禁用按钮。

完整示例请参见我刚刚发布的示例代码:https://github.com/vikingosegundo/PickerWithSectionTitlesExample

【讨论】:

    【解决方案2】:

    编辑:正如@Duncan C 在他的回答中提到的那样。在 iOS7+ 中没有内置的解决方案。我的回答提供了另一种实现目标的方法。

    在 iOS 7 之前,您可以使用 showsSelectionIndicator 在选取器视图中不显示不同对象的选定状态。但是 iOS7+ 不允许这样做:

    在 iOS 7 及更高版本上,您无法自定义选取器视图的选择指示器。选择指示器始终显示,因此将此属性设置为 false 无效。

    相反,您可以做一些讨厌的 hack:

    委托采用 UIPickerViewDelegate 协议,为每个组件的行提供内容,可以是属性字符串、纯字符串或视图,它通常会响应新的选择或取消选择。

    这个简短的例子只是展示了解决方案的工作原理。要使这些选择器对象不可选择,您可以使用文档中提到的属性字符串,该字符串看起来与其他字符串不同。

    如果用户尝试选择这些标题对象之一,您可以不关闭选择器视图。用户将不得不选择另一个选项,并且随着时间的推移,用户将了解到这些带有粗体字符串的对象是不可选择的。

     var datasource = ["Core", "content", "content", "content", "APG", "content", "content"]
    
    
    func pickerView(_ pickerView: UIPickerView, attributedTitleForRow row: Int, forComponent component: Int) -> NSAttributedString? {
     print(self.datasource[row])
     switch row {
         case 0:
             // create attributed string
             let myString = datasource[row]
             let myAttribute: [String:Any] = [ NSForegroundColorAttributeName: UIColor.blue, NSUnderlineStyleAttributeName: NSUnderlineStyle.styleDouble.rawValue ]
             let myAttrString = NSAttributedString(string: myString, attributes: myAttribute)
     
             return myAttrString
         case 15:
             let myString = datasource[row]
             let myAttribute: [String:Any] = [
        NSForegroundColorAttributeName: UIColor.blue,
        NSUnderlineStyleAttributeName: NSUnderlineStyle.styleDouble.rawValue ]
             let myAttrString = NSAttributedString(string: myString, attributes: myAttribute)
     
             return myAttrString
     
         case 4:
             let myString = datasource[row]
             let myAttribute: [String:Any] = [
        NSForegroundColorAttributeName: UIColor.blue,
        NSUnderlineStyleAttributeName: NSUnderlineStyle.styleDouble.rawValue ]
             let myAttrString = NSAttributedString(string: myString, attributes: myAttribute)
             return myAttrString
         default:
             //It all depends on your datasource, but if you have 0-42 strings(core, APG included) you can just output the rest under default here
             let myString = datasource[row]
             let myAttribute: [String:Any] = [ NSForegroundColorAttributeName: UIColor.black]
             let myAttrString = NSAttributedString(string: myString, attributes: myAttribute)
             return myAttrString
     
          }
     
     }
    

    它看起来不太好,但你可以自定义你想要的方式。您还可以在选取器视图对象中添加图像或 UiView。

    【讨论】:

    • 为了改善这一点,如果用户选择了带有章节标题的行,则自动将选择更新到下一行。
    【解决方案3】:

    不,我认为标准选择器视图不提供该选项。也就是说,您也许可以继承 UIPickerView 并教您的自定义子类具有您所描述的部分分隔符。

    当然可以,尽管需要做更多的工作,但可以创建自己的控件,该控件的作用类似于您描述的部分的选择器。

    【讨论】:

    • 谢谢你。现在我不想把事情复杂化。试图让我的应用程序简单。我正在学习 Xcode 和 swift。我现在可以把它放在次要位置,直到我更好地理解这门语言。与其说是必需品,不如说是奢侈品。
    • UIPickerViewDelegatepickerView(_:viewForRow:forComponent:reusing:) 可能会被强制为组指示符和可选项目返回不同的视图。不需要子类化。
    • 好的,我会进一步研究。我将阅读 Apple 的文档,并寻找教程。我不确定从哪里开始寻找。这是一个很好的起点。谢谢你。
    • @DuncanC,请看我的回答。
    猜你喜欢
    • 2016-12-06
    • 2015-07-12
    • 2019-04-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-04-20
    相关资源
    最近更新 更多