Expanding Table View Cells index out of range

im trying to make an expandable table view inside of viewCell



import UIKit
   
    class TableSupportVC: UITableViewController {
   
       
        let interface: TableSupportView = {
               let interface = TableSupportView(frame: CGRect.zero)
               interface.translatesAutoresizingMaskIntoConstraints = false
               return interface
           }()
   
       
        struct cellData {
            var opened = Bool()
            var title = String()
            var sectionData = [String]()
        }
       
        struct dataInside {
            var opened = Bool()
            var title = String()
            var info = [String]()
        }
   
        var tableViewData = [cellData]()
        var tableViewDataInside = [dataInside]()
       
        override func viewDidLoad() {
            super.viewDidLoad()
   
            //register tables
            tableView.register(TableSupportCell.self, forCellReuseIdentifier: menuCellIdita)
            tableView.register(TableSupportSubtitleCell.self, forCellReuseIdentifier: menuCellda)
               
            // Heres my data for cells
            tableViewData = [cellData(opened: false, title: "1", sectionData: ["1.1", "1.2","1.3","1.4"]),
                cellData(opened: false, title: "2", sectionData: ["2.1", "2.2", "2.3"])
            ]
           


           
            tableViewDataInside = [dataInside(opened: false, title: "1.1.1", info: ["1.1.1"])]
           
           
            setThemeNavigationBar()
                    // Size Scrollview
            interface.scrollView.contentSize = CGSize(width: wScreen, height: hScreen * 1.40)
                   
                    initComponents()
          
   
           
        }
   
       
       
        func setThemeNavigationBar() {
           
            let navigationBarAppearace = UINavigationBar.appearance()
            navigationBarAppearace.tintColor = blueBlumon
            navigationBarAppearace.barTintColor = blueBlumon
            navigationController?.navigationBar.barTintColor = blueBlumon
            navigationController?.navigationBar.barStyle = .blackOpaque
            navigationController?.navigationBar.isTranslucent = false
           
        }
       
       
        private func initComponents() {
           
            setNavItems()
            setSubviews()
            setAutolayout()
   
           
        }
       
   
        private func setSubviews() {
            view.backgroundColor = UIColor.white
            view.addSubview(interface)
        }
       
       
   
        private func setNavItems() {
   
            let backIcon = UIImage(named: "whiteArrow")
            let backItem = UIBarButtonItem(image: backIcon?.withRenderingMode(.alwaysTemplate), style: UIBarButtonItem.Style.plain, target: self, action: #selector(backPressed))
            backItem.tintColor = UIColor.white
            navigationItem.leftBarButtonItem = backItem
           
            let logoIcon = UIImage(named: "itemLogoBlue")
            let logoItem = UIBarButtonItem(image: logoIcon?.withRenderingMode(.alwaysOriginal), style: UIBarButtonItem.Style.plain, target: nil, action: nil)
            navigationItem.rightBarButtonItem = logoItem
           
        }
       
        // Método para definir el autolayout de los componentes de la vista principal del controlador
        private func setAutolayout() {
           
            NSLayoutConstraint.activate([
                interface.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
                interface.trailingAnchor.constraint(equalTo: view.trailingAnchor),
                interface.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
                interface.leadingAnchor.constraint(equalTo: view.leadingAnchor)
                ])
       
        }
       
        // Método de selector backPressed
        @objc private func backPressed() {
            self.navigationController?.popViewController(animated: true)
            dismiss(animated: true, completion: nil)
        }
        // MARK: - Table view data source
   
        override func numberOfSections(in tableView: UITableView) -> Int {
            return tableViewData.count
        }
   
        override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
   
           
            if tableViewData[section].opened == true{
                return tableViewData[section].sectionData.count + 1
            }
               
                 **//INDEX OUT OF RANGE HERE**
                if tableViewDataInside[section].opened == true{
                    return tableViewDataInside[section].info.count + 1
                }
            else {
                return 1
            }
            }
       
   
       
        override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
   
            if indexPath.row == 0{
                let cell = tableView.dequeueReusableCell(withIdentifier: menuCellIdita, for: indexPath) as! TableSupportCell
                cell.optionTitle.text = tableViewData[indexPath.section].title
               
                return cell
   
            }
            if indexPath.row == 2 && tableViewData[indexPath.section].title == "1.2"{
                    let cell = tableView.dequeueReusableCell(withIdentifier: menuCellda, for: indexPath) as! TableSupportSubtitleCell
                    cell.title.text = tableViewDataInside[indexPath.section].title
                    //cell.subtitle.text = tableViewDataInside[indexPath.section].info
                           return cell
                }
               
            else {
   
                let cell = tableView.dequeueReusableCell(withIdentifier: menuCellIdita, for: indexPath) as! TableSupportCell
                cell.optionTitle.text = tableViewData[indexPath.section].sectionData[indexPath.row - 1]
   
                return cell
            }
        }
   
           
   
       
       
       
       
        override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
            print("el indexpath es: \(indexPath.row)")
            if indexPath.row == 0 {
                if tableViewData[indexPath.section].opened == true{
                    tableViewData[indexPath.section].opened = false
                    let sections = IndexSet.init(integer: indexPath.section)
                    tableView.reloadSections(sections, with: .none)
                   
                    
                }
                   
                else{
                    tableViewData[indexPath.section].opened = true
                    let sections = IndexSet.init(integer: indexPath.section)
                    tableView.reloadSections(sections, with: .none)
                   
                }
               
               
            }
           
            if indexPath.row == 2 && tableViewData[indexPath.section].title == "1.2"{
                if tableViewDataInside[indexPath.section].opened == true{
                    tableViewDataInside[indexPath.section].opened = false
                    let sections = IndexSet.init(integer: indexPath.section)
                    tableView.reloadSections(sections, with: .none)
                   
                    
                }
                   
                else{
                    tableViewDataInside[indexPath.section].opened = true
                    let sections = IndexSet.init(integer: indexPath.section)
                    tableView.reloadSections(sections, with: .none)
                   
                }
               
               
            }
        }
       
        override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
            return 80
        }
       
   
   
    }



as you can see every time the user selects certain cells it activate




override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
            if indexPath.row == 0 {


if user press row 0 it will activate the code



same case for



override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {



but when i try to set



override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {



and run it crash index out of range


the main problem: i cant show my other cell *tableViewDataInside*

when the user clicks for example 1.2

Replies

WHere does it crash exactly ? Line 127 or 132 ?


You should instrument to understand what's going on:


override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
   
    print("Section", section)
    print("tableViewData", tableViewData.count)
    print("tableViewDataInside", tableViewDataInside.count)

    if tableViewData[section].opened == true {
        return tableViewData[section].sectionData.count + 1
    }
   
    **//INDEX OUT OF RANGE HERE**
    if tableViewDataInside[section].opened == true{
        return tableViewDataInside[section].info.count + 1
    }
    else {
        return 1
    }
}


Have you 2 or more sections ? It seems tableViewDataInside has just one item.


==> Please report on the print outputs.


Note: Types names should start with Uppercase: CellData vs cellData, DataInside vs dataInside, etc…

thanks i follow your advice and change the names
this is the print result:

Section 0

tableViewData 2

tableViewDataInside 1

Section 1

tableViewData 2

tableViewDataInside 1

Fatal error: Index out of range


it crash in line 131 check the code bellow ⬇
this is the code, i remove almost all coments
i just notice something my IF stament is not working in didSelectRowAt


i want to expande the table only if the user select certain row not every indexpath.row == 2


if indexPath.row == 2 && tableViewData[indexPath.section].sectionData == ["1.2"] {

what im doing wrong?


import UIKit

class TableSupportVC: UITableViewController {

    
    let interface: TableSupportView = {
           let interface = TableSupportView(frame: CGRect.zero)
           interface.translatesAutoresizingMaskIntoConstraints = false
           return interface
       }()

    
    struct CellData {
        var opened = Bool()
        var title = String()
        var sectionData = [String]()
    }
    
    struct DataInside {
        var opened = Bool()
        var title = String()
        var sectionData = [String]()
    }

    var tableViewData = [CellData]()
    var tableViewDataInside = [DataInside]()
    
    override func viewDidLoad() {
        super.viewDidLoad()

        tableView.register(TableSupportCell.self, forCellReuseIdentifier: menuCellIdita)
        tableView.register(TableSupportSubtitleCell.self, forCellReuseIdentifier: menuCellda)
            
        //CellData
        tableViewData = [CellData(opened: false, title: "1", sectionData: ["1.1", "1.2","1.3","1.4"]),
            CellData(opened: false, title: "2", sectionData: ["2.1", "2.2", "2.3"])
        ]
        
        //DataInside
        tableViewDataInside = [DataInside(opened: false, title: "1.1.1", sectionData: ["1.1.1"])]
        
        
        setThemeNavigationBar()
                // Size Scrollview
        interface.scrollView.contentSize = CGSize(width: wScreen, height: hScreen * 1.40)
                
                initComponents()
       

        
    }

    
    
    func setThemeNavigationBar() {
        
        let navigationBarAppearace = UINavigationBar.appearance()
        navigationBarAppearace.tintColor = blueBlumon
        navigationBarAppearace.barTintColor = blueBlumon
        navigationController?.navigationBar.barTintColor = blueBlumon
        navigationController?.navigationBar.barStyle = .blackOpaque
        navigationController?.navigationBar.isTranslucent = false
        
    }
    
    
    private func initComponents() {
        
        setNavItems()
        setSubviews()
        setAutolayout()

        
    }
    

    private func setSubviews() {
        view.backgroundColor = UIColor.white
        view.addSubview(interface)
    }
    
    

    private func setNavItems() {

        let backIcon = UIImage(named: "whiteArrow")
        let backItem = UIBarButtonItem(image: backIcon?.withRenderingMode(.alwaysTemplate), style: UIBarButtonItem.Style.plain, target: self, action: #selector(backPressed))
        backItem.tintColor = UIColor.white
        navigationItem.leftBarButtonItem = backItem
        
        let logoIcon = UIImage(named: "itemLogoBlue")
        let logoItem = UIBarButtonItem(image: logoIcon?.withRenderingMode(.alwaysOriginal), style: UIBarButtonItem.Style.plain, target: nil, action: nil)
        navigationItem.rightBarButtonItem = logoItem
        
    }
    
    // Método para definir el autolayout de los componentes de la vista principal del controlador
    private func setAutolayout() {
        
        NSLayoutConstraint.activate([
            interface.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
            interface.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            interface.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
            interface.leadingAnchor.constraint(equalTo: view.leadingAnchor)
            ])
    
    }
    
    // Método de selector backPressed
    @objc private func backPressed() {
        self.navigationController?.popViewController(animated: true)
        dismiss(animated: true, completion: nil)
    }
    // MARK: - Table view data source

    override func numberOfSections(in tableView: UITableView) -> Int {
        return tableViewData.count
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {

          print("Section", section)
          print("tableViewData", tableViewData.count)
          print("tableViewDataInside", tableViewDataInside.count)
        
          if tableViewData[section].opened == true {
              return tableViewData[section].sectionData.count + 1
          }
           
          //**//INDEX OUT OF RANGE HERE**
          if tableViewDataInside[section].opened == true{
            return tableViewDataInside[section].sectionData.count + 1
          }
          else {
              return 1
          }
        }
    

    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
       if indexPath.row == 0{
                   let cell = tableView.dequeueReusableCell(withIdentifier: menuCellIdita, for: indexPath) as! TableSupportCell
                   cell.optionTitle.text = tableViewData[indexPath.section].title
                    
                   return cell
        
               }
        if indexPath.row == 2 && tableViewData[indexPath.section].sectionData == ["1.2"]{
            let cell = tableView.dequeueReusableCell(withIdentifier: menuCellda, for: indexPath) as! TableSupportSubtitleCell
            cell.title.text = tableViewDataInside[indexPath.section].title
                   return cell
        }
                    
               else {
        
                   let cell = tableView.dequeueReusableCell(withIdentifier: menuCellIdita, for: indexPath) as! TableSupportCell
                   cell.optionTitle.text = tableViewData[indexPath.section].sectionData[indexPath.row - 1]
        
                   return cell
               }
           }

        

    
    
    
    
    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        print("el indexpath es: \(indexPath.row)")
            if indexPath.row == 0 {
                print("seleccionó 0")
                if tableViewData[indexPath.section].opened == true{
                    tableViewData[indexPath.section].opened = false
                    let sections = IndexSet.init(integer: indexPath.section)
                    tableView.reloadSections(sections, with: .none)
                     
                      
                }
                     
                else{
                    tableViewData[indexPath.section].opened = true
                    let sections = IndexSet.init(integer: indexPath.section)
                    tableView.reloadSections(sections, with: .none)
                     
                }
                 
                 
            }
             
         if indexPath.row == 2 && tableViewData[indexPath.section].sectionData == ["1.2"] {
                print("seleccionó 1.2")
                if tableViewDataInside[indexPath.section].opened == true{
                    tableViewDataInside[indexPath.section].opened = false
                    let sections = IndexSet.init(integer: indexPath.section)
                    tableView.reloadSections(sections, with: .none)
                     
                      
                }
                     
                else{
                    tableViewDataInside[indexPath.section].opened = true
                    let sections = IndexSet.init(integer: indexPath.section)
                    tableView.reloadSections(sections, with: .none)
                     
                }
                 
                 
            }
        }
    
    override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 80
    }
    
    func checkItemCount(arr: [String]) {
        var dict = [String: Any]()

        for x in arr {
            var count = 0
            for y in arr {
                if y == x {
                    count += 1
                }
            }

            dict[x] = count
        }

        print(dict)
    }

}

The crash is logical:


Section 0

tableViewData 2

tableViewDataInside 1

Section 1

tableViewData 2

tableViewDataInside 1

Fatal error: Index out of range


        tableViewDataInside = [DataInside(opened: false, title: "1.1.1", sectionData: ["1.1.1"])]

When you reach section 1, tableViewDataInside[1] crashes.


On the othen end, tableViewData has 2 items:

        tableViewData = [CellData(opened: false, title: "1", sectionData: ["1.1", "1.2","1.3","1.4"]),
            CellData(opened: false, title: "2", sectionData: ["2.1", "2.2", "2.3"])
        ]

Why this difference ?


Why isn't tableViewDataInside initialized similarly ?

Something like:


tableViewDataInside = [DataInside(opened: false, title: "1.1.1", sectionData: ["1.1.1"]),
                                     DataInside(opened: false, title: "2.1.1", sectionData: ["2.1.1"])
                                     ]



In addition, those table views should probably be build dynamically. Where is it done ?

Thanks for the help


the difference between: tableViewData and tableViewDataInside


is the constraints.

Here's the class of tableViewData

this UITableViewCell class should display a message covert all the cell with 90% of heigh and 90% of width of the cell

with only 1 Label



import UIKit

let menuCellIdita = "celldita"

class TableSupportCell: UITableViewCell {
    

    
    
    var optionTitle: UILabel = {
        
        let optionTitle = UILabel()
        optionTitle.font = UIFont(name: "Helvetica-bold", size: 14)
        optionTitle.textAlignment = .justified
        optionTitle.text = "label example"
        optionTitle.adjustsFontSizeToFitWidth = true
        optionTitle.textColor = blueMifel
        optionTitle.translatesAutoresizingMaskIntoConstraints = false
        optionTitle.backgroundColor = UIColor.clear
        optionTitle.numberOfLines = 0
        return optionTitle
        
    }()
    

    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        
        addSubview(optionTitle)
       // addSubview(iconImage)
        
       setAutoLayout()
        
        self.backgroundColor = UIColor.clear
        
    }
    
  
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    private func setAutoLayout() {
        
        NSLayoutConstraint.activate([

            optionTitle.centerXAnchor.constraint(equalTo: self.centerXAnchor),
            optionTitle.widthAnchor.constraint(equalTo: self.widthAnchor, multiplier: 0.90),
            optionTitle.heightAnchor.constraint(equalTo: self.heightAnchor, multiplier: 0.90),
            optionTitle.centerYAnchor.constraint(equalTo: self.centerYAnchor)

            ])

        
    }
    
}



here's the class for tableViewDataInside


import UIKit

let menuCellda = "cellditaSub"

class TableSupportSubtitleCell: UITableViewCell {
    

    
    
    var title: UILabel = {
        
        let optionTitle = UILabel()
        optionTitle.font = UIFont(name: "Helvetica-bold", size: 14)
        optionTitle.textAlignment = .justified
        optionTitle.text = "label example"
        optionTitle.adjustsFontSizeToFitWidth = true
        optionTitle.textColor = blueMifel
        optionTitle.translatesAutoresizingMaskIntoConstraints = false
        optionTitle.backgroundColor = UIColor.clear
        optionTitle.numberOfLines = 0
        return optionTitle
        
    }()
    
    var subtitle: UILabel = {
        
        let optionTitle = UILabel()
        optionTitle.font = UIFont(name: "Helvetica-bold", size: 14)
        optionTitle.textAlignment = .justified
        optionTitle.text = "label example"
        optionTitle.adjustsFontSizeToFitWidth = true
        optionTitle.textColor = blueMifel
        optionTitle.translatesAutoresizingMaskIntoConstraints = false
        optionTitle.backgroundColor = UIColor.clear
        optionTitle.numberOfLines = 0
        return optionTitle
        
    }()
    

    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        
        addSubview(title)
        addSubview(subtitle)

       setAutoLayout()
        
        self.backgroundColor = UIColor.clear
        
    }
    
  
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    private func setAutoLayout() {
        
        NSLayoutConstraint.activate([

            title.centerXAnchor.constraint(equalTo: self.leadingAnchor, constant: 8),
            title.widthAnchor.constraint(equalTo: self.widthAnchor, multiplier: 0.30),
            title.heightAnchor.constraint(equalTo: self.heightAnchor, multiplier: 0.90),
            title.centerYAnchor.constraint(equalTo: self.centerYAnchor),
            
            subtitle.centerXAnchor.constraint(equalTo: self.trailingAnchor, constant: -8),
            subtitle.widthAnchor.constraint(equalTo: self.widthAnchor, multiplier: 0.60),
            subtitle.heightAnchor.constraint(equalTo: self.heightAnchor, multiplier: 0.90),
            subtitle.centerYAnchor.constraint(equalTo: self.centerYAnchor)

            
            ])

        
    }
    
}


this class should display 2 messages one in the leading and one in the trailing anchor, that's why the difference between


this should be the main title for the cell

tableViewData = [CellData(opened: false, title: "1", sectionData: ["1.1", "1.2","1.3","1.4"]),
            CellData(opened: false, title: "2", sectionData: ["2.1", "2.2", "2.3"])
        ]


and then if user clicks whatever title should display this array, and show title and subtitle in the open cell

tableViewDataInside = [DataInside(opened: false, title: "1.1.1", sectionData: ["1.1.1"])]



title should be an array thats for fact, if i want to display multiple information in multiple sub-cells witih sub-titles

tableViewDataInside = [DataInside(opened: false, title: ["1.1.1", "2.2.2", "3.3.3"], sectionData: ["1.1.1", "2.2.2", "3.3.3"])]

like this


---------------------------------

TITLE MAIN 1.1 (user clicks here)

---------------------------------

expandible table opens and shows

---------------------------

TITLE SUBTITLE

---------------------------


and the whole representation of what im trying to do is



-------------------------------

MAIN TITLE 1 of CellData(user clicks here), expandible table opens and shows ⬇

---------------------------------

---------------------------------

1.1 sectionData of CellData (user clicks here), expandible table opens and shows

--------------------------------

------------------------------

TITLE SUBTITLE (of DataInside where TITLE is: title and SUBTITLE is: sectionData)

---------------------------