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

问题描述

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

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

enter image description here

解决方法

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

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

enter image description here

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

...

并显示单元格“扩展”:

collapsedView.isHidden = false
expandedView.isHidden = true

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

这是一个示例布局:

enter image description here

以及它在运行时的表现:

enter image description here

,
//
//  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",workout3])
        
        let workoutTemplate = WorkoutTemplate(folderName: "My Workout Template",workoutType: [workoutType,workoutType2])
        
        let workoutType3 = WorkoutType(workoutCategoryName: "Basic full body",workout3])
        let workoutType4 = WorkoutType(workoutCategoryName: "Basic full body",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)")
    }
    
    
    
}