Swift-下拉菜单-模拟地址选择

建议直接观看原文(下方有链接),原文有图片演示,本文只是添加了一些注释。

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

本文为转载的总结性文章,如若涉及版权问题,请与博主联系。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,186评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,858评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,620评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,888评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,009评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,149评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,204评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,956评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,385评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,698评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,863评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,544评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,185评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,899评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,141评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,684评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,750评论 2 351