美文网首页iOS DeveloperIOSiOS学习开发
Swift 自定义PickerView,支持选择时间、单选、多选

Swift 自定义PickerView,支持选择时间、单选、多选

作者: 星辰大海_王 | 来源:发表于2017-09-10 13:41 被阅读0次

    bg:

    系统提供了时间选择器UIDatePicker、文本选择器UIPickerView,这两种选择器本身具备了很多特性及优点。但在实际项目开发中有较多地方用到时间选择或数据选择场景,比如商城类App购物车的商品添加、有预约场景的App时间及事务选择等,考虑到UI要求、代码封装易用性等方面,就需要我们自己自定义一些选择控件了。

    这里自定义一个简单的选择器:时间选择单选多选(暂未添加多级联动选择模式,后续添加)其中时间选择支持设置时间范围和默认时间

    Demo地址:https://github.com/wanghhh/HHPickerViewDemo

    先看下效果图:

    pickerView_gif.gif
    调用示例:

    比如选择时间:

    let pickerView = HHPickerView.init(frame: CGRect.init(x: marginTop, y: self.view.bounds.size.height, width: self.view.bounds.size.width, height: CGFloat(toolBarH + pickerViewH)), dateFormat: nil,datePickerMode:.dateAndTime, minAndMaxAndCurrentDateArr: nil)
            pickerView.rowAndComponentCallBack = {(resultStr,selectedArr) in
                print("str--->\(String(describing: resultStr))")
                btn.setTitle(resultStr! as String, for: .normal)
            }
    pickerView.show()
    

    //多选

    let pickerView = HHPickerView.init(frame: CGRect.init(x: marginTop, y: self.view.bounds.size.height, width: self.view.bounds.size.width, height: CGFloat(toolBarH + pickerViewH)), dataSource: data as NSArray, defaultIntegerArr: [1,3,6], pickerType: .mutable)
            
            pickerView.rowAndComponentCallBack = {(resultStr,selectedArr) in
                print("str--->\(String(describing: resultStr))")
                btn.setTitle(resultStr! as String, for: .normal)
            }
     pickerView.show()
    

    代码结构:

    ic_picker01.png

    调用时将HHPickerView.swift文件拖进项目即可,顾名思义HHPickerView类就是自定义的选择器。
    由于要支持时间选择、文本选择,就抽取了对应的2个辅助类(暂且这样叫):HHDatePicker(时间选择类)、HHCollectionView(文本选择类)。其中HHDatePicker直接继承自系统的UIDatePicker,HHCollectionView这里就继承自UICollectionView实现了,也可以直接调用辅助类中的方法。

    HHPickerView实现:

    //选择器类型

    enum HHPickerViewType:NSInteger {
       case single = 0   //只能单选
       case mutable = 1  //可多选、单选
       case time = 2     //选择时间
    }
    
    定义一个结果回调闭包

    //返回选择结果内容、结果索引数组(针对单选、多选)

    typealias HHPickerViewCallBackClosure = (_ resultStr:NSString?,_ resultArr:NSArray?) -> ()
    
    var rowAndComponentCallBack:HHPickerViewCallBackClosure?//选择内容回调
    

    重写init方法来实现基本UI设置,代码如下:

    override init(frame: CGRect) {
        super.init(frame: frame)
        self.backgroundColor = UIColor.white;
        
        // 1 获取window
        if (keyWindow == nil) {
            self.keyWindow = UIApplication.shared.keyWindow
        }
        // 2.遮罩view
        overlayView = UIControl.init(frame: UIScreen.main.bounds)
        overlayView?.backgroundColor = UIColor.init(red: 0, green: 0, blue: 0, alpha: 0.5)
        overlayView?.addTarget(self, action: #selector(hide), for: .touchUpInside)
        overlayView?.alpha = 0
        // 3.创建工具条toolView
        let toolView:UIView = UIView.init(frame: CGRect.init(x: 0, y: 0, width: Int(self.bounds.size.width), height: Int(toolBarH)))
        toolView.backgroundColor = UIColor(red: 230/255, green: 230/255, blue: 230/255, alpha: 1)
        addSubview(toolView)
        
        cancelButton = UIButton.init(frame: CGRect.init(x: btnMargin, y: 0, width: 44, height: toolView.bounds.size.height))
        cancelButton?.setTitle("取消", for: .normal)
        cancelButton?.setTitleColor(cancelTextNormalColor, for: .normal)
        cancelButton?.setTitleColor(cancelTextSelectedColor, for: .selected)
        cancelButton?.titleLabel?.font = UIFont.systemFont(ofSize: 17.5)
        cancelButton?.contentHorizontalAlignment = .left
        cancelButton?.addTarget(self, action: #selector(cancelAction), for: .touchUpInside)
        toolView.addSubview(cancelButton!)
        
        confirmButton = UIButton.init(frame: CGRect.init(x: (toolView.bounds.size.width - 44.0 - btnMargin), y: 0, width: 44, height: toolView.bounds.size.height))
        confirmButton?.setTitle("确定", for: .normal)
        confirmButton?.setTitleColor(confirmTextNormalColor, for: .normal)
        confirmButton?.setTitleColor(confirmTextSelectedColor, for: .selected)
        confirmButton?.titleLabel?.font = UIFont.systemFont(ofSize: 17.5)
        confirmButton?.contentHorizontalAlignment = .left
        confirmButton?.addTarget(self, action: #selector(confirmAction), for: .touchUpInside)
        toolView.addSubview(confirmButton!)
    }
    
    时间选择模式便利方法:(可设置dateFormat格式化字符串;选择器datePickerMode;时间范围控制:可选最小、最大时间及默认时间)
    /// 时间选择便利构造器
    ///
    /// - Parameters:
    ///   - frame: frame
    ///   - dateFormat: 时间格式化字符串,可空
    ///   - datePickerMode: 选择器的时间模式,可空
    ///   - minAndMaxAndCurrentDateArr: 可选最小、最大时间及当前时间,可空
    convenience init(frame: CGRect,dateFormat:NSString?,datePickerMode:UIDatePickerMode?,minAndMaxAndCurrentDateArr:[NSDate]?) {
        self.init(frame: frame)
        pickerViewType = HHPickerViewType.time
        
        let picker = HHDatePicker.init(frame: CGRect.init(x: (confirmButton?.superview?.frame.minX)!, y: (confirmButton?.superview?.frame.maxY)!, width: UIScreen.main.bounds.size.width, height: CGFloat(pickerViewH)), dateFormat: dateFormat,datePickerMode:datePickerMode, minAndMaxAndCurrentDateArr: nil, resultCallBack: {[weak self] (resultStr) in
            self?.blockContent = resultStr
        })
        picker.getSelectedResult({[weak self] (resultStr) in
            self?.blockContent = resultStr
        })
        addSubview(picker)
    }
    
    单选、多选模式便利构造方法:(可设置单选、多选模式;可设置默认选中的选项)
    /// 单选/多选便利构造器
    ///
    /// - Parameters:
    ///   - frame: frame
    ///   - pickerType: 选择类型(单选或多选)
    ///   - dataSource: 数据源
    ///   - defaultIntegerArr:  默认选中项的索引数组
    convenience init(frame: CGRect,dataSource:NSArray,defaultIntegerArr:NSArray?,pickerType:HHPickerViewType) {
        self.init(frame: frame)
        pickerViewType = pickerType
        if (dataSource.count != 0) {
            let picker = HHCollectionView.init(frame: CGRect.init(x: (confirmButton?.superview?.frame.minX)!, y: (confirmButton?.superview?.frame.maxY)!, width: UIScreen.main.bounds.size.width, height: CGFloat(pickerViewH)), collectionViewLayout: HHWaterfallLayout(), dataSource: dataSource, defaultIntegerArr: defaultIntegerArr, contentCallBack: { [weak self] (resultStr, selectedArr) in
                self?.blockContent = resultStr
                self?.selectedArr = selectedArr
            })
            picker.rowAndComponentCallBack = {[weak self](resultStr,selectedArr) in
                self?.blockContent = resultStr
                self?.selectedArr = selectedArr
            }
            addSubview(picker)
        }else{
            assert(dataSource.count != 0, "dataSource is not allowed to be nil")
        }
    }
    
    主要事件处理

    //显示

    func show(){
        keyWindow?.addSubview(overlayView!)
        keyWindow?.addSubview(self)
        UIView.animate(withDuration: 0.25, animations: {
            self.overlayView?.alpha = 1.0
            var frame = self.frame
            frame.origin.y = UIScreen.main.bounds.size.height - self.bounds.size.height
            self.frame = frame
        }) { (isFinished) in
            //
        }
    }
    

    //隐藏

    func hide() {
        self.dismissCallBack()
        UIView.animate(withDuration: 0.25, animations: {
            self.overlayView?.alpha = 0
            var frame = self.frame
            frame.origin.y = UIScreen.main.bounds.size.height
            self.frame = frame
        }) { (isFinished) in
            self.overlayView?.removeFromSuperview()
            self.removeFromSuperview()
        }
    }
    

    //取消选择

    func cancelAction() {
        hide()
    }
    

    //确定选择

    func confirmAction() {
        if blockContent == "" {
            showAlert(withTitle: "提示", message: "未选择任何一项!")
        }else if pickerViewType != HHPickerViewType.time && (selectedArr?.count)! > 1 && pickerViewType == HHPickerViewType.single {
            showAlert(withTitle: "提示", message: "此项仅支持单选!")
        }else{
            self.rowAndComponentCallBack!(blockContent,selectedArr)
        }
        hide()
    }
    

    //异常提示

    @objc private func showAlert(withTitle title: String?, message: String?) {
        let alertVc = UIAlertController.init(title: title, message: message, preferredStyle: UIAlertControllerStyle.alert)
        alertVc.addAction(UIAlertAction.init(title: "我知道了", style: UIAlertActionStyle.cancel, handler: nil))
        UIApplication.shared.keyWindow?.rootViewController?.present(alertVc, animated: true, completion: nil)
    }
    

    以上就是HHPickerView的主要实现,代码简单,就不再详细说明了,可查看Demo:https://github.com/wanghhh/HHPickerViewDemo

    HHDatePicker实现:

    //结果回调闭包

    typealias HHDatePickerCallBackClosure = (_ resultStr:NSString?) -> ()
    
    var dateChangeCallBack:HHDatePickerCallBackClosure? //时间改变回调
    
    便利构造方法:
    /// 时间选择器便利构造方法
    ///
    /// - Parameters:
    ///   - frame: frame
    ///   - dateFormat: 时间格式化字符串
    ///   - datePickerMode: 选择器的时间模式
    ///   - minAndMaxAndCurrentDateArr: 可选最小、最大时间及当前时间
    ///   - resultCallBack: 选择结果
    convenience init(frame: CGRect,dateFormat:NSString?,datePickerMode:UIDatePickerMode?,minAndMaxAndCurrentDateArr:[NSDate]?,resultCallBack:((_ resultStr:NSString) -> Void)?) {
        self.init(frame: frame)
        self.backgroundColor = UIColor.white;
        if datePickerMode != nil {
            self.datePickerMode = datePickerMode!
        }else{
            self.datePickerMode = .dateAndTime //默认显示月、日、时间
        }
        if dateFormat?.range(of: "yy").location != NSNotFound {
            self.datePickerMode = .dateAndTime
        }else{
            self.datePickerMode = .date
        }
        //可以设置时间范围
        var minDateTem = NSDate.init()
        var maxDateTem = NSDate.init(timeIntervalSinceNow: 90*365*24*60*60)
        var currentDateTem = NSDate.init()
        if minAndMaxAndCurrentDateArr != nil && minAndMaxAndCurrentDateArr?.count == 2 {
            minDateTem = (minAndMaxAndCurrentDateArr?[0])!
            maxDateTem = (minAndMaxAndCurrentDateArr?[1])!
            currentDateTem = (minAndMaxAndCurrentDateArr?[2])!
        }
        self.minimumDate = minDateTem as Date
        self.maximumDate = maxDateTem as Date
        self.setDate(currentDateTem as Date, animated: false)
        self.locale = Locale.init(identifier: "zh_CN")
        
        self.addTarget(self, action: #selector(dateChange(datePicker:)), for: UIControlEvents.valueChanged)
        
        //默认回调当前时间
        let theDate = self.date
        let dateFormatter = DateFormatter.init()
        if (dateFormat != nil) {
            dateFormatter.dateFormat = dateFormat! as String
            self.dateFormat = dateFormat
        }else{
            dateFormatter.dateFormat = "YYYY-MM-dd HH:mm:ss"
            self.dateFormat = dateFormatter.dateFormat! as NSString
        }
        let nowDate = dateFormatter.string(from: theDate)
        resultCallBack!(nowDate as NSString)
    }
    

    //时间改变结果回调

    fileprivate func getSelectedResult(_ callBack: @escaping(HHDatePickerCallBackClosure)) {
        dateChangeCallBack = callBack
    }
    

    //时间改变监听

    func dateChange(datePicker:UIDatePicker) {
        let theDate = datePicker.date
        print("\(theDate.description(with: Locale.current))")
        
        let dateFormatter = DateFormatter.init()
        dateFormatter.dateFormat = self.dateFormat! as String
        let nowDate = dateFormatter.string(from: theDate)
    
        dateChangeCallBack!(nowDate as NSString)
    }
    
    HHCollectionView主要实现:

    //回调

    fileprivate var rowAndComponentCallBack:HHPickerViewCallBackClosure?//选择内容回调
    lazy var dataSourceArr = NSMutableArray() //数据源
    lazy var selectedArr = NSMutableArray() //被选中的数据
    
    便利方法:
    /// 便利构造器
    ///
    /// - Parameters:
    ///   - frame: frame
    ///   - collectionViewLayout: collectionViewLayout
    ///   - dataSource: 选择项数据源
    ///   - defaultIntegerArr: 默认选中的项索引数组
    ///   - contentCallBack: 选择结果回调
    convenience init(frame:CGRect,collectionViewLayout:UICollectionViewLayout,dataSource:NSArray,defaultIntegerArr:NSArray?,contentCallBack:HHPickerViewCallBackClosure?) {
        self.init(frame: frame, collectionViewLayout: collectionViewLayout)
        self.delegate = self
        self.dataSource = self
        self.backgroundColor = UIColor.white
        self.dataSourceArr = NSMutableArray.init(array: dataSource)
        if (defaultIntegerArr != nil) {
            self.selectedArr = NSMutableArray.init(array: defaultIntegerArr!)
        }
        
        self.register(HHCollectionCell.self, forCellWithReuseIdentifier: HHCollectionViewCellId)
        
        if (contentCallBack != nil) {
            //默认选中数据
            var resultStr = "" //选中的结果的拼接字符串,多选用“;”号隔开(按需要自定义)
            
            if self.selectedArr.count > 0 {
                for (idx,obj) in self.selectedArr.enumerated() {
                    if idx == 0 {
                        resultStr = self.dataSourceArr[(obj as? Int)!] as! String
                    }else{
                        resultStr = "\(resultStr);\(self.dataSourceArr[(obj as? Int)!])"
                    }
                }
            }
            contentCallBack!(resultStr as NSString,selectedArr)
        }
    }
    
    在UICollectionViewDelegate中回调结果:
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        let cell:HHCollectionCell = collectionView.cellForItem(at: indexPath) as! HHCollectionCell        
        if (self.selectedArr.count>0) {
            var isExited = false//是否已经被选中,即存在selectedArr中
            for (_,obj) in self.selectedArr.enumerated() {
                if obj as? NSInteger == indexPath.row{
                    cell.isSelected = false //取消选中
                    isExited = true
                    selectedArr.remove(indexPath.row)
                    break
                }
            }
            if isExited == false {
                selectedArr.add(indexPath.row)
            }
        }else{
            cell.isSelected = true
            selectedArr.add(indexPath.row)
        }
        reloadItems(at: [indexPath])
        
        //组装回调结果***
        //默认选中数据
        var resultStr = "" //选中的结果的拼接字符串,多选用“;”号隔开(按需要自定义)
        
        if self.selectedArr.count > 0 {
            for (idx,obj) in self.selectedArr.enumerated() {
                if idx == 0 {
                    resultStr = self.dataSourceArr[(obj as? Int)!] as! String
                }else{
                    resultStr = "\(resultStr);\(self.dataSourceArr[(obj as? Int)!])"
                }
            }
        }
        self.rowAndComponentCallBack!(resultStr as NSString,selectedArr)
    }
    
    HHCollectionView的布局及自定义cell的代码比较简单就不在bia了,详细代码可以下载demo:https://github.com/wanghhh/HHPickerViewDemo查看。
    本文所涉及到代码都比较简单,主要提供一个思路:通过自定义view+自定义datePicker+自定义文本选择view实现时间选择、单选及多选。大家可以根据项目需要比如UI需求等,予以扩展、完善。

    欢迎大家指出错误、相互交流,共同学习!

    相关文章

      网友评论

        本文标题:Swift 自定义PickerView,支持选择时间、单选、多选

        本文链接:https://www.haomeiwen.com/subject/wfhnjxtx.html