Swift TableView Cell折叠展开效果+隐藏Header、Footer

PS:依赖库 pod 'SnapKit', '~> 4.2.0'
let kJustExpandOne: Bool = true 控制是否显示多条或一条
1、效果图01-可任意展开多条

折叠展开效果01.gif

2、效果图02-只展示一条

折叠展开效果02.gif

源码

//
//  ProblemFeedbackViewController.swift
//  MicroCoupletTaxi
//
//  Created by 郭明健 on 2020/8/31.
//  Copyright © 2020 GuoMingJian. All rights reserved.
//

import UIKit
import SnapKit

class ProblemFeedbackViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
    
    var dataSourceArray: NSMutableArray = NSMutableArray()
    let kJustExpandOne: Bool = true // 是否仅展开一条数据
    
    lazy var tableView: UITableView = {
        let tableView = UITableView.init(frame: .zero, style: .grouped)
        tableView.dataSource = self
        tableView.delegate = self
        tableView.backgroundColor = UIColor.white
        tableView.showsVerticalScrollIndicator = false
        tableView.bounces = true
        tableView.separatorStyle = .none
        // 隐藏Header、Footer
        tableView.tableHeaderView = UIView.init(frame: CGRect.init(x: 0, y: 0, width: 0, height: CGFloat.leastNormalMagnitude))
        tableView.sectionHeaderHeight = 0
        tableView.sectionFooterHeight = 0
        // 注册Cell
        tableView.register(ProblemCell.classForCoder(), forCellReuseIdentifier: ProblemCell.description())
        return tableView
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        //
        configNavigationBar()
        setupUI()
        setData()
    }
    
    func configNavigationBar() {
        self.title = "问题反馈"
    }
    
    func setupUI() {
        self.view.addSubview(tableView)
        tableView.snp_makeConstraints { (make) in
            make.center.size.equalToSuperview()
        }
    }
    
    func setData() {
        //
        var dic = NSMutableDictionary.init()
        dic.addEntries(from: ["isOpen" : 0,
                              "problem" : "您需支付取消费的情况",
                              "answer" : "1. 若您在1分钟后取消订单,需支付取消(若司机有迟到,拒载行为,您可无需支付)。\n2.取消费金额为2元,用于补偿司机的空驶成本。"])
        dataSourceArray.add(dic)
        dic = NSMutableDictionary.init()
        dic.addEntries(from: ["isOpen" : 0,
                              "problem" : "您无需支付取消费的情况",
                              "answer" : "1.司机未到达时,您在1分钟内取消订单。\n2.司机未在规定时间内(详见软件提示)到达上车点,您取消订单。\n3.司机未朝上车点行驶,或以其他各种理由不来接您时,您取消订单。\n1.司机未到达时,您在1分钟内取消订单。\n2.司机未在规定时间内(详见软件提示)到达上车点,您取消订单。\n3.司机未朝上车点行驶,或以其他各种理由不来接您时,您取消订单。\n1.司机未到达时,您在1分钟内取消订单。\n2.司机未在规定时间内(详见软件提示)到达上车点,您取消订单。\n3.司机未朝上车点行驶,或以其他各种理由不来接您时,您取消订单。"])
        dataSourceArray.add(dic)
        dic = NSMutableDictionary.init()
        dic.addEntries(from: ["isOpen" : 0,
                              "problem" : "你的来自哪里?",
                              "answer" : "中国"])
        dataSourceArray.add(dic)
        dic = NSMutableDictionary.init()
        dic.addEntries(from: ["isOpen" : 0,
                              "problem" : "你的爱好?",
                              "answer" : "写代码"])
        dataSourceArray.add(dic)
    }
    
    //MARK:- UITableViewDataSource
    
    func numberOfSections(in tableView: UITableView) -> Int {
        return dataSourceArray.count
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        let dic: NSMutableDictionary = dataSourceArray[section] as! NSMutableDictionary
        let isOpen: Int = dic.object(forKey: "isOpen") as! Int
        if isOpen == 0 {
            return 1
        } else {
            return 2
        }
    }
    
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        if indexPath.row == 0 {
            return 44
        } else {
            //
            let dic: NSMutableDictionary = dataSourceArray[indexPath.section] as! NSMutableDictionary
            let answer: String = dic.object(forKey: "answer") as! String
            //
            let cell = ProblemCell.init(style: .default, reuseIdentifier: ProblemCell.description())
            let height: CGFloat = cell.getCellHeight(content: answer)
            return height
        }
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell: ProblemCell = tableView.dequeueReusableCell(withIdentifier: ProblemCell.description(), for: indexPath) as! ProblemCell
        cell.selectionStyle = .none
        //
        let dic: NSMutableDictionary = dataSourceArray[indexPath.section] as! NSMutableDictionary
        let problem = dic.object(forKey: "problem") as? String
        let answer = dic.object(forKey: "answer") as? String
        let isOpen: Int = dic.object(forKey: "isOpen") as! Int
        if indexPath.row == 0 {
            cell.setRightImageViewIsHidden(isHidden: false)
            cell.rightImageView.image = UIImage.init(named: (isOpen == 1) ? "下箭头" : "右箭头")
            cell.contentLabel.text = problem
            cell.contentView.backgroundColor = UIColor.white
        } else {
            cell.setRightImageViewIsHidden(isHidden: true)
            cell.contentLabel.text = answer
            cell.contentView.backgroundColor = UIColor.init(red: 242/255.0, green: 242/255.0, blue: 247/255.0, alpha: 1.0)
        }
        //
        return cell
    }
    
    //MARK:- UITableViewDelegate
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        if indexPath.row == 0 {
            updateData(indexPath: indexPath)
        } else {
        }
    }
    
    //MARK:-
    func updateData(indexPath: IndexPath) {
        //
        if kJustExpandOne {
            // 显示单个
            for i in 0...dataSourceArray.count-1 {
                let dic : NSMutableDictionary = dataSourceArray[i] as! NSMutableDictionary
                var isOpen = dic.object(forKey: "isOpen") as? Int
                let oldStatus = isOpen
                if i != indexPath.section {
                    isOpen = 0
                } else {
                    isOpen = (isOpen == 0) ? 1 : 0
                }
                dic.setValue(isOpen, forKey: "isOpen")
                dataSourceArray.replaceObject(at: i, with: dic)
                //
                let newIndexPath = IndexPath.init(row: 1, section: i)
                if oldStatus == 1 && isOpen == 0 {
                    tableView.deleteRows(at: [newIndexPath], with: .automatic)
                    // 改变原已展开Cell箭头icon
                    let cell: ProblemCell = tableView.cellForRow(at: IndexPath.init(row: 0, section: i)) as! ProblemCell
                    cell.rightImageView.image = UIImage.init(named: (isOpen == 1) ? "下箭头" : "右箭头")
                }
                if oldStatus == 0 && isOpen == 1 {
                    tableView.insertRows(at: [newIndexPath], with: .automatic)
                }
            }
        } else {
            // 可显示多个
            let dic: NSMutableDictionary = dataSourceArray[indexPath.section] as! NSMutableDictionary
            var isOpen = dic.object(forKey: "isOpen") as? Int
            isOpen = (isOpen == 0) ? 1 : 0
            dic.setValue(isOpen, forKey: "isOpen")
            dataSourceArray.replaceObject(at: indexPath.section, with: dic)
            //
            let newIndexPath = IndexPath.init(row: 1, section: indexPath.section)
            if isOpen == 1 {
                tableView.insertRows(at: [newIndexPath], with: .automatic)
            } else {
                tableView.deleteRows(at: [newIndexPath], with: .automatic)
            }
        }
        // 改变箭头icon
        let dic : NSMutableDictionary = dataSourceArray[indexPath.section] as! NSMutableDictionary
        let isOpen = dic.object(forKey: "isOpen") as? Int
        let cell: ProblemCell = tableView.cellForRow(at: indexPath) as! ProblemCell
        cell.rightImageView.image = UIImage.init(named: (isOpen == 1) ? "下箭头" : "右箭头")
    }
    
}

class ProblemCell: UITableViewCell {
    
    var contentLabel: UILabel!
    var rightImageView: UIImageView!
    //
    let rightIconSize: CGSize = CGSize.init(width: 30, height: 30)
    let contentFont: UIFont = UIFont.systemFont(ofSize: 17)
    let topSpac: CGFloat = 10
    let leftSpac: CGFloat = 15
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        //
        setupUI()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func setupUI() {
        rightImageView = UIImageView.init()
        rightImageView.image = UIImage.init(named: "右箭头")
        self.addSubview(rightImageView)
        rightImageView.snp_makeConstraints { (make) in
            make.size.equalTo(rightIconSize)
            make.right.equalToSuperview().offset(-leftSpac)
            make.centerY.equalToSuperview()
        }
        //
        contentLabel = UILabel.init()
        contentLabel.font = contentFont
        contentLabel.textColor = UIColor.black
        contentLabel.numberOfLines = 0
        self.addSubview(contentLabel)
        contentLabel.snp_makeConstraints { (make) in
            make.top.equalTo(topSpac)
            make.centerY.equalToSuperview()
            make.left.equalTo(leftSpac)
            make.right.equalTo(rightImageView.snp_left)
        }
    }
    
    func setRightImageViewIsHidden(isHidden: Bool) {
        rightImageView.isHidden = isHidden
        if isHidden {
            rightImageView.snp_remakeConstraints { (make) in
                make.size.equalTo(CGSize.zero)
                make.right.equalToSuperview().offset(-leftSpac)
                make.centerY.equalToSuperview()
            }
        } else {
            rightImageView.snp_remakeConstraints { (make) in
                make.size.equalTo(rightIconSize)
                make.right.equalToSuperview().offset(-leftSpac)
                make.centerY.equalToSuperview()
            }
        }
    }
    
    func getCellHeight(content: String) -> CGFloat {
        //
        let w = UIScreen.main.bounds.size.width - (leftSpac*2)
        let rect = (content as NSString).boundingRect(with: CGSize.init(width: w, height: CGFloat.greatestFiniteMagnitude), options: [.truncatesLastVisibleLine, .usesLineFragmentOrigin, .usesFontLeading], attributes: [.font : contentFont], context: nil)
        var height: CGFloat = ceil(rect.size.height) + topSpac*2
        if height < 44 {
            height = 44
        }
        return height
    }
}

Icon 资源

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