建议直接观看原文(下方有链接),原文有图片演示,本文只是添加了一些注释。
class XXXXXXXXXXXXXXXXX: UIViewController , DropMenuViewDataSource, DropMenuViewDelegate
struct DropMenuData {
static var TitleDatas = ["出售", "区域", "来源"]
// 房屋类型
static var HouseType = ["出租", "出售"]
// 区域
static var HouseArea = ["东城区": ["安定门", "交道口", "王府井", "和平里", "北新桥", "东直门外", "东直门", "雍和宫"], "西城区": ["新街口", "阜成门", "金融街", "长椿街", "西单"], "朝阳区": ["双井", "国贸", "北苑", "大望路", "四惠", "十里堡", "花家池"], "丰台区": ["方庄", "角门", "草桥", "木樨园", "宋家庄", "东大街", "南苑", "大红门"]]
//来源
static var HouseSource = ["全部来源", "房天下", "便民网", "列表网", "城际分类", "58同城", "赶集", "安居客"]
}
//
func menu(_ menu: DropMenuView, didSelectRowAtIndexPath index: DropMenuView.Index) {
print(index.column, index.row, index.item)
}
//定义有几组下拉菜单
func numberOfColumns(in menu: DropMenuView) -> Int {
return DropMenuData.TitleDatas.count
}
//显示下拉之后的内容有几个
func menu(_ menu: DropMenuView, numberOfRowsInColumn column: Int) -> Int {
if column == 0 {
return DropMenuData.HouseType.count
} else if column == 1 {
return DropMenuData.HouseArea.count
} else if column == 2 {
return DropMenuData.HouseSource.count
}
return 0
}
//显示下拉框内容以及下拉之后的内容
func menu(_ menu: DropMenuView, titleForRowsInIndePath index: DropMenuView.Index) -> String {
switch index.column {
case 0:
return DropMenuData.TitleDatas[index.row]
case 1:
return Array(DropMenuData.HouseArea.keys)[index.row]
case 2:
return DropMenuData.HouseSource[index.row]
default:
return ""
}
}
//
func menu(_ menu: DropMenuView, numberOfItemsInRow row: Int, inColumn: Int) -> Int {
if inColumn == 1 {
return Array(DropMenuData.HouseArea.values)[row].count
}
return 0
}
//
func menu(_ menu: DropMenuView, titleForItemInIndexPath indexPath: DropMenuView.Index) -> String {
if indexPath.column == 1 {
return Array(DropMenuData.HouseArea.values)[indexPath.row][indexPath.item]
}
return ""
}
//
// DropMenuView.swift
//
import Foundation
import UIKit
private let SCREEN_WIDTH = UIScreen.main.bounds.size.width
private let SCREEN_HEIGHT = UIScreen.main.bounds.size.height
//MARK:- 代理
//使用的时候需要实现代理
protocol DropMenuViewDelegate: NSObjectProtocol {
func menu(_ menu: DropMenuView, didSelectRowAtIndexPath index: DropMenuView.Index)
}
protocol DropMenuViewDataSource: NSObjectProtocol {
func numberOfColumns(in menu: DropMenuView) -> Int
func menu(_ menu: DropMenuView, numberOfRowsInColumn column: Int) -> Int
func menu(_ menu: DropMenuView, titleForRowsInIndePath index: DropMenuView.Index) -> String
func menu(_ menu: DropMenuView, numberOfItemsInRow row: Int, inColumn: Int) -> Int
func menu(_ menu: DropMenuView, titleForItemInIndexPath indexPath: DropMenuView.Index) -> String
}
//MARK:- 方法扩展
extension DropMenuViewDataSource {
func numberOfColumns(in menu: DropMenuView) -> Int {
return 1
}
func menu(_ menu: DropMenuView, numberOfItemsInRow row: Int, inColumn: Int) -> Int {
return 0
}
func menu(_ menu: DropMenuView, titleForItemInIndexPath indexPath: DropMenuView.Index) -> String {
return ""
}
}
class DropMenuView: UIView, UITableViewDelegate, UITableViewDataSource {
private var menuOrigin: CGPoint = CGPoint.zero // 菜单的原点坐标
private var menuHeight: CGFloat = 0 // 菜单初始高度
private let DropViewTableCellID = "DropViewTableCellID" // cell的标识
private var bgColor: UIColor = UIColor.orange // 背景颜色
private var isShow: Bool = false // 列表是否正在展示
private var selectedRows = Array<Int>() // 每一列选中的row
private var titleColor: UIColor = UIColor.white // title字体颜色
private var titleFont: UIFont = UIFont.systemFont(ofSize: 15) // title 字体大小
private var titleButtons = [UIButton]() // titleButtons
private var seperatorLineColor: UIColor = UIColor.lightGray // 分割线颜色
private var selectedColumn: Int = -1 // 当前选中的是哪一列
private let cellHeight: CGFloat = 50 // cell的高度
private var maxTableViewHeight: CGFloat = SCREEN_HEIGHT - 200 // tableView最大高度
private var animationDuration: TimeInterval = 0.25 // 动画时长
//MARK:- 行·列·标题
public struct Index {
var column: Int // 列
var row: Int //行
var item: Int //item
init(column: Int, row: Int, item: Int = -1) {
self.column = column
self.row = row
self.item = item
}
}
//MARK:- 数据源
weak var dataSource: DropMenuViewDataSource? {
didSet {
//“===”:三个等号我们称为等同符,当等号两边的值为相同类型的时候,直接比较等号两边的值,值相同则返回true,若等号两边的值类型不同时,直接返回false
//“==” :两个等号我们称为等值符,当等号两边的值为相同类型的时候,直接比较等号两边的值,值相同则返回true,若等号两边的值类型不同时,会发生类型的自动转换,转换为相同的类型后再作比较
if oldValue === dataSource {
return
}
dataSourceDidSet(dataSource: dataSource!)
}
}
//MARK:- 实现代理
weak var delegate: DropMenuViewDelegate?
//lazy :延迟加载(初始值直到第一次使用的时候才执行计算)
//注意 :lazy属性必须是变量(var修饰符),因为常量属性(let修饰符)必须在初始化之前就有值,所以常量属性不能定义为lazy。
/* 简单理解:类比OC中的方法
- (NSArray *)names {
if (!_names) {
_names = [[NSArray alloc] init];
NSLog(@"只在首次访问输出");
}
return _names;
}
在初始化对象后,_names 是 nil。只有当首次访问 names 属性时 getter 方法会被调用,并检查如果还没有初始化的话,就进行赋值。可以想见,控制台打印的“只在首次访问输出”的确只会输出一次。我们之后再多次访问这个属性的话,因为 _names已经有值,因此将直接返回
*/
private lazy var leftTableView: UITableView = {
let tableView = UITableView(frame: CGRect(x: menuOrigin.x, y: menuOrigin.y + menuHeight, width: SCREEN_WIDTH, height: 0))
tableView.register(UITableViewCell.self, forCellReuseIdentifier: DropViewTableCellID)
tableView.delegate = self
tableView.dataSource = self
return tableView
}()
private lazy var rightTableView: UITableView = {
let tableView = UITableView(frame: CGRect(x: menuOrigin.x, y: menuOrigin.y + menuHeight, width: SCREEN_WIDTH, height: 0))
tableView.register(UITableViewCell.self, forCellReuseIdentifier: DropViewTableCellID)
tableView.delegate = self
tableView.dataSource = self
return tableView
}()
private lazy var backgroundView: UIView = {
let bgView = UIView(frame: CGRect(x: menuOrigin.x, y: menuOrigin.y, width: SCREEN_WIDTH, height: SCREEN_HEIGHT))
bgView.backgroundColor = UIColor.black.withAlphaComponent(0.5)
bgView.alpha = 0
return bgView
}()
init(menuOrigin: CGPoint, menuHeight: CGFloat) {
self.menuOrigin = menuOrigin
self.menuHeight = menuHeight
super.init(frame: CGRect(x: menuOrigin.x, y: menuOrigin.y, width: SCREEN_WIDTH, height: menuHeight))
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(tapToDismiss))
backgroundView.addGestureRecognizer(tapGesture)
backgroundColor = bgColor
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc func tapToDismiss() {
animationTableView(show: false)
isShow = false
}
func dataSourceDidSet(dataSource: DropMenuViewDataSource) {
let columns = dataSource.numberOfColumns(in: self)
createTitleButtons(columns: columns)
selectedRows = Array<Int>(repeating: 0, count: columns)
}
private func createTitleButtons(columns: Int) {
let btnW: CGFloat = SCREEN_WIDTH / CGFloat(columns)
let btnH: CGFloat = self.menuHeight
let btnY: CGFloat = 0
var btnX: CGFloat = 0
for i in 0..<columns {
let btn = UIButton(type: .custom)
btnX = CGFloat(i) * btnW
btn.frame = CGRect(x: btnX, y: btnY, width: btnW, height: btnH)
btn.setTitle(dataSource?.menu(self, titleForRowsInIndePath: Index(column: i, row: 0)), for: UIControl.State.normal)
btn.setTitleColor(titleColor, for: .normal)
btn.titleLabel?.font = titleFont
btn.addTarget(self, action: #selector(titleBtnDidClick(btn:)), for: .touchUpInside)
btn.tag = i + 1000
addSubview(btn)
titleButtons.append(btn)
let seperatorLine = UIView(frame: CGRect(x: btn.frame.maxX, y: 0, width: 1, height: btnH))
seperatorLine.backgroundColor = seperatorLineColor
addSubview(seperatorLine)
}
}
@objc func titleBtnDidClick(btn: UIButton) {
let column = btn.tag - 1000
guard let dataSource = dataSource else {
return
}
if selectedColumn == column && isShow {
// 收回列表
animationTableView(show: false)
isShow = false
} else {
selectedColumn = column
leftTableView.reloadData()
// 刷新右边tableView
if dataSource.menu(self, numberOfItemsInRow: selectedRows[selectedColumn], inColumn: selectedColumn) > 0 {
rightTableView.reloadData()
}
// 展开列表
animationTableView(show: true)
isShow = true
}
}
//MARK:- TableView代理
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let dataSource = dataSource {
if tableView == leftTableView {
return dataSource.menu(self, numberOfRowsInColumn: selectedColumn)
} else {
return dataSource.menu(self, numberOfItemsInRow: selectedRows[selectedColumn], inColumn: selectedColumn)
}
}
return 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: DropViewTableCellID)
if let dataSource = dataSource {
if tableView == leftTableView {
cell?.textLabel?.text = dataSource.menu(self, titleForRowsInIndePath: Index(column: selectedColumn, row: indexPath.row))
// 选中上次选中的那行
if selectedRows[selectedColumn] == indexPath.row {
tableView.selectRow(at: indexPath, animated: true, scrollPosition: .none)
}
} else {
cell?.textLabel?.text = dataSource.menu(self, titleForItemInIndexPath: Index(column: selectedColumn, row: selectedRows[selectedColumn], item: indexPath.row))
//选中上次选中的行
if cell?.textLabel?.text == titleButtons[selectedColumn].titleLabel?.text {
leftTableView.selectRow(at: IndexPath(row: selectedRows[selectedColumn], section: 0), animated: true, scrollPosition: .none)
tableView.selectRow(at: indexPath, animated: true, scrollPosition: .none)
}
}
}
return cell!
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
guard let dataSource = dataSource else {
return
}
if tableView == leftTableView {
selectedRows[selectedColumn] = indexPath.row
let haveItems = dataSource.menu(self, numberOfItemsInRow: indexPath.row, inColumn: selectedColumn) > 0
if haveItems {
rightTableView.reloadData()
} else {
//收回列表
animationTableView(show: false)
isShow = false
//更新title
titleButtons[selectedColumn].setTitle(dataSource.menu(self, titleForRowsInIndePath: Index(column: selectedColumn, row: indexPath.row)), for: .normal)
}
delegate?.menu(self, didSelectRowAtIndexPath: Index(column: selectedColumn, row: indexPath.row))
} else {
//收回列表
animationTableView(show: false)
isShow = false
let index = Index(column: selectedColumn, row: selectedRows[selectedColumn], item: indexPath.row)
//更新title
titleButtons[selectedColumn].setTitle(dataSource.menu(self, titleForItemInIndexPath: index), for: .normal)
delegate?.menu(self, didSelectRowAtIndexPath: index)
}
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return cellHeight
}
//MARK:- 展示或者隐藏TableView
func animationTableView(show: Bool) {
var haveItems = false
let rows = leftTableView.numberOfRows(inSection: 0)
if let dataSource = dataSource {
for i in 0..<rows {
if dataSource.menu(self, numberOfItemsInRow: i, inColumn: selectedColumn) > 0 {
haveItems = true
}
}
}
let tableViewHeight = CGFloat(rows) * cellHeight > maxTableViewHeight ? maxTableViewHeight : CGFloat(rows) * cellHeight
if show {
superview?.addSubview(backgroundView)
superview?.addSubview(self)
if haveItems {
leftTableView.frame = CGRect(x: menuOrigin.x, y: menuOrigin.y + menuHeight, width: SCREEN_WIDTH * 0.5, height: 0)
rightTableView.frame = CGRect(x: SCREEN_WIDTH * 0.5, y: menuOrigin.y + menuHeight, width: SCREEN_WIDTH * 0.5, height: 0)
superview?.addSubview(leftTableView)
superview?.addSubview(rightTableView)
} else {
rightTableView.removeFromSuperview()
leftTableView.frame = CGRect(x: menuOrigin.x, y: menuOrigin.y + menuHeight, width: SCREEN_WIDTH, height: 0)
superview?.addSubview(leftTableView)
}
UIView.animate(withDuration: animationDuration) {
self.backgroundView.alpha = 1.0
self.leftTableView.frame.size.height = tableViewHeight
if haveItems {
self.rightTableView.frame.size.height = tableViewHeight
}
}
} else {
UIView.animate(withDuration: animationDuration, animations: {
self.backgroundView.alpha = 0
self.leftTableView.frame.size.height = 0
if haveItems {
self.rightTableView.frame.size.height = 0
}
}) { (_) in
self.backgroundView.removeFromSuperview()
self.leftTableView.removeFromSuperview()
if haveItems {
self.rightTableView.removeFromSuperview()
}
}
}
}
}
原文:https://www.jianshu.com/p/24c395d60776?utm_campaign=hugo
本文为转载的总结性文章,如若涉及版权问题,请与博主联系。