如何快速制作嵌套的可折叠 uitableview?

Posted

技术标签:

【中文标题】如何快速制作嵌套的可折叠 uitableview?【英文标题】:How to make nested collapsible tableview i swift? 【发布时间】:2021-08-20 09:04:28 【问题描述】:

我有一些设计,其中父母和他们的孩子都是可折叠的。我已经使用表格视图实现了父级,但是对于它的子级,我将如何实现它的扩展单元格。我应该加载另一个自定义视图单元格还是应该更改当前单元格,我不知道。如果有人对此有任何想法,请帮助我。

   // Model Class
class WorkoutTemplate 
    var folderName:String
    var workoutType: [WorkoutType]
    init(folderName: String, workoutType: [WorkoutType]) 
        self.folderName = folderName
        self.workoutType = workoutType
    

class WorkoutType 
    var workoutCategoryName: String // Basic Full Body
    var totalWorkouts : [Workout]
    init(workoutCategoryName: String, totalWorkouts: [Workout] ) 
        self.workoutCategoryName = workoutCategoryName
        self.totalWorkouts = totalWorkouts
    

class  Workout
    var workoutName: String
    var reps:Int = 0
    var sets:Int = 0
    init(workoutName: String, reps: Int, sets: Int) 
        self.workoutName = workoutName
        self.reps = reps
        self.sets = sets
    
    




// Controller class
import UIKit

class AddWorkoutViewController: UIViewController 
    @IBOutlet weak var tableView: UITableView?
    var data = [WorkoutTemplate]()
    var sectionIsExpanded = [false, false, false]
    private let headerIdentifier = "WorkoutTemplateHeaderCell"
    private let cellIdentifier = "AddWorkoutCategoryCell"
    override func viewDidLoad() 
        super.viewDidLoad()
        let workout = Workout(workoutName: "Squat", reps: 10, sets: 9)
        let workout2 = Workout(workoutName: "Bench press", reps: 6, sets: 7)
        let workout3 = Workout(workoutName: "Dunmbell lunge", reps: 12, sets: 78)
        
        let workoutType = WorkoutType(workoutCategoryName: "Basic full body", totalWorkouts: [workout, workout2, workout3])
        
        let workoutTemplate = WorkoutTemplate(folderName: "My Workout Template", workoutType: [workoutType, workoutType])
        
        data.append(workoutTemplate)
        data.append(workoutTemplate)
        data.append(workoutTemplate)
        
        tableView?.reloadData()
        
        
    


extension AddWorkoutViewController: UITableViewDataSource, UITableViewDelegate 
    
    
    func numberOfSections(in tableView: UITableView) -> Int 
        return data.count
    
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int 
        // First will always be header
        let sectiondata = data[section]
        return sectionIsExpanded[section] ? (sectiondata.workoutType.count + 1) : 1
    
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell 
        if indexPath.row == 0 
            let headerCell = tableView.dequeueReusableCell(withIdentifier: headerIdentifier, for: indexPath) as! AddWorkoutTemplateHeaderTableViewCell
            let sectionData = data[indexPath.section]
            headerCell.headingLabel?.text = sectionData.folderName
            
            if sectionIsExpanded[indexPath.section] 
                headerCell.setExpanded()
             else 
                headerCell.setCollapsed()
            
            return headerCell
         else 
            let catCell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! AddWorkoutCategoryTableViewCell
            let sectionData = data[indexPath.section]
            let rowData = sectionData.workoutType[(indexPath.row - 1)]
           
            catCell.headingLabel?.text = rowData.workoutCategoryName
            var subHeadings = ""
            for item in rowData.totalWorkouts 
                subHeadings = subHeadings + item.workoutName + ", "
            
            
            
            catCell.mutiWorkoutLabel?.text = subHeadings
            return catCell
        
    
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) 
        // Expand/hide the section if tapped its header
        if indexPath.row == 0 
            sectionIsExpanded[indexPath.section] = !sectionIsExpanded[indexPath.section]
            
            tableView.reloadSections([indexPath.section], with: .automatic)
        
    
    

【问题讨论】:

【参考方案1】:

因为你有“通用”元素——人字形图像、标题、... 和“开始锻炼”按钮,我建议使用单个单元格类而不是单独的类。

将“中心”元素放在垂直堆栈视图中:

当你想显示单元格“折叠”集时:

collapsedView.isHidden = false
expandedView.isHidden = true

并显示单元格“展开”:

collapsedView.isHidden = true
expandedView.isHidden = false

堆栈视图会自动移除隐藏视图占用的空间,但将其保留为视图层次结构的一部分,因此您不必添加/删除子视图和激活/停用约束。

这是一个示例布局:

以及它在运行时的样子:

【讨论】:

谢谢,@DonMag 我更可能得到你的回答【参考方案2】:
//
//  AddWorkoutViewController.swift

//
//  Created by developer on 6/1/21.
//

import UIKit

class AddWorkoutViewController: UIViewController, passIndexData 
    @IBOutlet weak var tableView: UITableView?
    var data = [WorkoutTemplate]()
    var sectionIsExpanded = [false, false, false]
    private let headerIdentifier = "WorkoutTemplateHeaderCell"
    private let cellIdentifier = "AddWorkoutCategoryCell"
    override func viewDidLoad() 
        super.viewDidLoad()
        let workout = Workout(workoutName: "Squat", reps: 10, sets: 9)
        let workout2 = Workout(workoutName: "Bench press", reps: 6, sets: 7)
        let workout3 = Workout(workoutName: "Dunmbell lunge", reps: 12, sets: 78)
        
        let workoutType = WorkoutType(workoutCategoryName: "Basic full body", totalWorkouts: [workout, workout2, workout3])
        let workoutType2 = WorkoutType(workoutCategoryName: "Basic full body", totalWorkouts: [workout, workout2, workout3])
        
        let workoutTemplate = WorkoutTemplate(folderName: "My Workout Template", workoutType: [workoutType, workoutType2])
        
        let workoutType3 = WorkoutType(workoutCategoryName: "Basic full body", totalWorkouts: [workout, workout2, workout3])
        let workoutType4 = WorkoutType(workoutCategoryName: "Basic full body", totalWorkouts: [workout, workout2, workout3])
        
        
        let workoutTemplate2 = WorkoutTemplate(folderName: "My Workout Template", workoutType: [workoutType3, workoutType4])
        
        data.append(workoutTemplate)
        data.append(workoutTemplate2)
        //data.append(workoutTemplate)
        
        tableView?.reloadData()
        
        
    
    
    func stringToAtributedString(string:String, fontName: String, alpha: CGFloat = 1)-> NSMutableAttributedString 
        if let safeFont = UIFont(name: fontName, size: 14) 
            let color = UIColor(named: "textBlack")
            color?.withAlphaComponent(alpha)
            let attribute = [NSAttributedString.Key.font: safeFont, NSAttributedString.Key.foregroundColor: color]
            let attributedString = NSMutableAttributedString(string: string, attributes: attribute)
            return attributedString
        
        return NSMutableAttributedString()
        
    



extension AddWorkoutViewController: UITableViewDataSource, UITableViewDelegate 
    
    
    func numberOfSections(in tableView: UITableView) -> Int 
        return data.count
    
    
    func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat 
        return 1.2 //.leastNormalMagnitude // 0 may not work
    
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int 
        // First will always be header
        let sectiondata = data[section]
        return sectionIsExpanded[section] ? (sectiondata.workoutType.count + 1) : 1
    
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell 
        if indexPath.row == 0 
            // header cell
            let headerCell = tableView.dequeueReusableCell(withIdentifier: headerIdentifier, for: indexPath) as! AddWorkoutTemplateHeaderTableViewCell
            let sectionData = data[indexPath.section]
            headerCell.headingLabel?.text = sectionData.folderName
            
            if sectionIsExpanded[indexPath.section] 
                headerCell.setExpanded()
             else 
                headerCell.setCollapsed()
            
            headerCell.selectionStyle = .none
            return headerCell
         else 
            // workout category cell
            let catCell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! AddWorkoutCategoryTableViewCell
            let sectionData = data[indexPath.section]
            let rowData = sectionData.workoutType[(indexPath.row - 1)]
           
            catCell.headingLabel?.text = rowData.workoutCategoryName
            var subHeadings = ""
            
            let workoutNames = UILabel()
            workoutNames.numberOfLines = 0
            workoutNames.alpha = 0.7
            
            for item in rowData.totalWorkouts 
                subHeadings = subHeadings + item.workoutName + ", "
            
            
            
            
            workoutNames.attributedText = stringToAtributedString(string: subHeadings, fontName: "Poppins-Regular")
            
            if catCell.workoutSetContainer != nil 
                for view in catCell.workoutSetContainer!.subviews.enumerated() 
                    view.element.removeFromSuperview()
                
            
                        
            print("section: \(indexPath.section) row: \(indexPath.row) rowCollapse: \(rowData.isExpanded)")
            
            
            if rowData.isExpanded 
                catCell.setCollapsed()
                for item in rowData.totalWorkouts.enumerated() 
                    let workoutSetcell = tableView.dequeueReusableCell(withIdentifier: "WorkoutSetCell") as! AddworkoutWOrkoutSetTableViewCell
                    workoutSetcell.workoutNameLabel?.text = item.element.workoutName
                    //workoutSetcell.workoutSetLabel?.text = "\(item.element.reps)" + " " + "\(item.element.sets)"
                    
                    let repsCount = stringToAtributedString(string: "\(item.element.reps)", fontName: "Poppins-Bold")
                    let repsString = stringToAtributedString(string: " reps", fontName: "Poppins-Regular", alpha: 0.7)
                    
                    
                    repsCount.append(repsString)
                    workoutSetcell.workoutSetLabel?.attributedText = repsCount
                    
                    

                    catCell.workoutSetContainer?.addArrangedSubview(workoutSetcell)
                
                
             else 
                catCell.setExpanded()
                catCell.workoutSetContainer?.addArrangedSubview(workoutNames)

            
            
            catCell.cellDelegate = self
            catCell.cellIndexPath = indexPath
            catCell.selectionStyle = .none
            return catCell
        
    
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) 
        // Expand/hide the section if tapped its header
        if indexPath.row == 0 
            sectionIsExpanded[indexPath.section] = !sectionIsExpanded[indexPath.section]
            
            tableView.reloadSections([indexPath.section], with: .automatic)
        
        
        
        
    
    


extension AddWorkoutViewController:expandCell 
    func passIndexValues(indexPath: IndexPath?) 
        if indexPath != nil 
            let sectionData = data[indexPath!.section]
            let rowData = sectionData.workoutType[indexPath!.row - 1]
            rowData.isExpanded = !rowData.isExpanded
            sectionData.workoutType[indexPath!.row - 1] = rowData
            data[indexPath!.section] = sectionData

            tableView?.reloadRows(at: [indexPath!], with: .automatic)
            //tableView?.reloadData()
        
        
        //print("index path \(indexPath?.section)")
    
    
    
    

【讨论】:

以上是关于如何快速制作嵌套的可折叠 uitableview?的主要内容,如果未能解决你的问题,请参考以下文章

带部分的可折叠 UITableView

如何创建 QCheckBoxes 的可折叠列表?

展开或折叠时可展开的 UItableview 内容高度不同

iphone中的可扩展tableView

iphone中的可扩展tableView

iOS开发高级分享 - iOS的可折叠表视图