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需求等,予以扩展、完善。
欢迎大家指出错误、相互交流,共同学习!
网友评论