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

本文是一篇关于Swift如何实现下拉菜单以模拟地址选择的总结性文章,内容来源于简书并添加了注释。建议通过提供的原文链接查看详细教程,包括图片演示。
摘要由CSDN通过智能技术生成

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

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

原文:Swift下拉菜单实现 - 简书

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

敛柒

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值