iOS开发经验

Swift下拉菜单实现

2018-07-11  本文已影响429人  向日葵的夏天_summer

title: Swift下拉菜单实现

1.效果图

dropDown.gif

2.实现过程

整体思路就是创建一个View,两个tableView,一个背景bgView。实现一个代理协议和一个数据源协议,通过设置代理和数据源传值。

2.1 代理和数据源
private let SCREEN_WIDTH = UIScreen.main.bounds.size.width
private let SCREEN_HEIGHT = UIScreen.main.bounds.size.height

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
}

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 ""
}
}
2.2 根据菜单列数创建button
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: UIControlState.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)
    }
}
2.3 button点击时候控制tableView的展示或者隐藏
@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
    }
}
2.4 展示或者隐藏TableView
//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()
            }
        }
    }
}
2.5 实现tableView的代理和数据源方法
extension DropMenuView: UITableViewDelegate, UITableViewDataSource {
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
}
}

3.所有代码

private let SCREEN_WIDTH = UIScreen.main.bounds.size.width
private let SCREEN_HEIGHT = UIScreen.main.bounds.size.height

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
}

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 ""
}
}


import UIKit

class DropMenuView: UIView {

public struct Index {
    // 列
    var column: Int
    //行
    var row: Int
    //item
    var item: Int
    
    init(column: Int, row: Int, item: Int = -1) {
        self.column = column
        self.row = row
        self.item = item
    }
}

//MARK:- 数据源
weak var dataSource: DropMenuViewDataSource? {
    didSet {
        if oldValue === dataSource {
            return
        }
        dataSourceDidSet(dataSource: dataSource!)
    }
}

weak var delegate: DropMenuViewDelegate?

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
}()

// 菜单的原点坐标
private var menuOrigin: CGPoint = CGPoint.zero
// 菜单高度
private var menuHeight: CGFloat = 0
// tableView最大高度
private var maxTableViewHeight: CGFloat = SCREEN_HEIGHT - 200
// 当前选中的是哪一列
private var selectedColumn: Int = -1
// 每一列选中的row
private var selectedRows = Array<Int>()
// 列表是否正在展示
private var isShow: Bool = false
// 动画时长
private var animationDuration: TimeInterval = 0.25
// cell的高度
private let cellHeight: CGFloat = 50
// cell的标识
private let DropViewTableCellID = "DropViewTableCellID"
// titleButtons
private var titleButtons = [UIButton]()
// 背景颜色
private var bgColor: UIColor = UIColor.orange
// title字体颜色
private var titleColor: UIColor =  UIColor.white
// title 字体大小
private var titleFont: UIFont = UIFont.systemFont(ofSize: 15)
// 分割线颜色
private var seperatorLineColor: UIColor = UIColor.lightGray

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
    
}

@objc func tapToDismiss() {
    animationTableView(show: false)
    isShow = false
}

required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
}


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: UIControlState.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 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()
            }
        }
    }
}

}

extension DropMenuView: UITableViewDelegate, UITableViewDataSource {
    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
}
}

4.在控制器中使用

import UIKit

class ViewController: UIViewController {

struct DropMenuData {
    static var TitleDatas = ["出售", "区域", "来源"]
    
    // 房屋类型
    static var HouseType = ["出租", "出售"]
    // 区域
    static var HouseArea = ["东城区": ["安定门", "交道口", "王府井", "和平里", "北新桥", "东直门外", "东直门", "雍和宫"], "西城区": ["新街口", "阜成门", "金融街", "长椿街", "西单"], "朝阳区": ["双井", "国贸", "北苑", "大望路", "四惠", "十里堡", "花家池"], "丰台区": ["方庄", "角门", "草桥", "木樨园", "宋家庄", "东大街", "南苑", "大红门"]]
    //来源
    static var HouseSource = ["全部来源", "房天下", "便民网", "列表网", "城际分类", "58同城", "赶集", "安居客"]
}

private var menuView: DropMenuView!

override func viewDidLoad() {
    super.viewDidLoad()
    view.backgroundColor = UIColor.white
    
    menuView = DropMenuView(menuOrigin: CGPoint(x: 0, y: 30), menuHeight: 50)
    menuView.dataSource = self
    menuView.delegate = self
    view.addSubview(menuView)
}
}

extension ViewController: DropMenuViewDelegate {
    func menu(_ menu: DropMenuView, didSelectRowAtIndexPath index: DropMenuView.Index) {
        print(index.column, index.row, index.item)
    }
}

extension ViewController: DropMenuViewDataSource {
    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 ""
}
}
上一篇 下一篇

猜你喜欢

热点阅读