Below I am trying to load data into a UITableView and every time the data ends up either in the wrong cell or duplicated into cells it should never had access to. I have cut out a lot of extra code and am still running into this problem. I think I am missing something fundamental about UITableViews.
func setUpFeedTable() {
self.tableFeed.isScrollEnabled = true
for i in 0 ..< allDictionary.count {
let dictionary = allDictionary[i]
if dictionary?["type"]! != nil {
let l = i
print("text", l)
self.tableFeed.cellForRow(at: IndexPath(row: l, section: 0))?.imageView?.layer.borderColor = UIColor.black.cgColor
self.tableFeed.cellForRow(at: IndexPath(row: l, section: 0))?.imageView?.layer.borderWidth = 1
self.tableFeed.cellForRow(at: IndexPath(row: l, section: 0))?.imageView?.layer.cornerRadius = 1
//self.tableFeed.cellForRow(at: IndexPath(row: i, section: 0))?.imageView?.clipsToBounds = true
self.editProfileButton.imageView?.contentMode = UIViewContentMode.scaleAspectFill
//self.tableFeed.reloadData()
//self.tableFeed.reloadRows(at: [IndexPath(row: l, section: 0)], with: UITableViewRowAnimation(rawValue: 0)!)
self.tableFeed.cellForRow(at: IndexPath(row: l, section: 0))?.imageView?.frame = CGRect(x: 0, y: UIScreen.main.bounds.height/2, width: 100, height: 100)
self.tableTextOverLays(i: l, type: Int((dictionary?["type"])! as! NSNumber))
//self.tableFeed.reloadRows(at: [IndexPath(row: l, section: 0)], with: UITableViewRowAnimation(rawValue: 0)!)
self.tableFeed.reloadDate()
}
else if (dictionary?["type_"] as! String) == "Image" {
print("image", i)
}
else {
print("video")
}
}
}
func tableTextOverLays(i: Int, type: Int){
if type == 0{
print("saw type 0", i)
self.tableFeed.cellForRow(at: IndexPath(row: i, section: 0))?.imageView?.image = #imageLiteral(resourceName: "Geo-fence-50")
}
else if type == 1 {
self.tableFeed.cellForRow(at: IndexPath(row: i, section: 0))?.imageView?.image = #imageLiteral(resourceName: "Happy-50")
}
else if type == 2 {
self.tableFeed.cellForRow(at: IndexPath(row: i, section: 0))?.imageView?.image = #imageLiteral(resourceName: "Information-50")
}
else if type == 3 {
self.tableFeed.cellForRow(at: IndexPath(row: i, section: 0))?.imageView?.image = #imageLiteral(resourceName: "Home-50")
}
}
Update: I have edited my code and things are working far better (so thank you!) but I am now running into the issue of a images being place both into the first two cells and then the 4th and 5th cells.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if tableView == self.table {
return users2.count
}
else {
//print("married barry", tableFeedCount)
return tableFeedCount
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if tableView == self.table {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath as IndexPath) as UITableViewCell
cell.textLabel?.text = self.users2[indexPath.row].name
return cell
}
else {
//print("working?")
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath as IndexPath) as UITableViewCell
let dictionary = allDictionary[indexPath.row]
if dictionary?["type"]! != nil {
cell.imageView?.layer.borderColor = UIColor.black.cgColor
cell.layer.borderWidth = 1
cell.imageView?.layer.cornerRadius = 1
cell.imageView?.frame = CGRect(x: 0, y: UIScreen.main.bounds.height/2, width: 100, height: 100)
self.tableTextOverLays(i: indexPath.row, type: Int((dictionary?["type"])! as! NSNumber), cell: cell)
}
else if (dictionary?["type_"] as! String) == "Image" {
print("image")
}
else {
print("video")
}
return cell
}
}
The reason is because you'r trying to set UITableviewCells metadata requesting cell's to the tableView, Cells are reused in UITableView protocol method cellforRowAtIndexPath and that is why you see duplicates or metadata in other cells.
What you need to do is set the metadata of each cell in cellforRowAtIndexPath method.
For example if you have an array with info like this
let metadata = ["title1", "title2", "title3"]
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return metadata.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
let title = metadata[indexPath.row]
cell.titleLabel.text = title
return cell
}
It isn't right to populate a TableView like that.
Each TableView must be related to a DataSource or to a UITableViewDelegate, and this, for example, should implement methods such as tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
For instance:
class MyViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var tableView: UITableView!
var myStrings = ["1", "2", "3", "4", "5"]
override func viewDidLoad() {
self.tableView.delegate = self
self.tableView.dataSource = self
}
#available(iOS 2.0, *)
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0 {
return 1
}
return myStrings.count
}
func numberOfSections(in tableView: UITableView) -> Int {
return 2
}
#available(iOS 2.0, *)
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "reuseId", for: indexPath)
if indexPath.section == 0 {
cell.textLabel?.text = "This is the row in the first section"
} else {
cell.textLabel?.text = myStrings[indexPath.row]
}
return cell
}
}
Related
I have attached the image click the card view expands the same card inside the table cell dynamically its passible to achieve this?
I have searched a lot but not working
Hear my code added header cell with CardView
added arrow button to click the button expand the cell
its able expand but not in parent card it was showing diff card
I have adde my source code
var hiddenSections = Set<Int>()
let tableViewData = [
["1","2","3","4","5"],
["1","2","3","4","5"],
["1","2","3","4","5"],
]
override func viewDidLoad() {
super.viewDidLoad()
let CustomeHeaderNib = UINib(nibName: "CustomSectionHeader", bundle: Bundle.main)
historyTableView.register(CustomeHeaderNib, forHeaderFooterViewReuseIdentifier: "customSectionHeader")
}
func numberOfSections(in tableView: UITableView) -> Int {
return self.tableViewData.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if self.hiddenSections.contains(section) {
return 0
}
return self.tableViewData[section].count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell()
cell.textLabel?.text = self.tableViewData[indexPath.section][indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return view.frame.width/4
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let header = self.historyTableView.dequeueReusableHeaderFooterView(withIdentifier: "customSectionHeader") as! CustomSectionHeader
header.setupCornerRadious()
let sectionButton = header.expandBtn
sectionButton?.setTitle(String(section),
for: .normal)
sectionButton?.tag = section
sectionButton?.addTarget(self,action: #selector(self.hideSection(sender:)), for: .touchUpInside)
return header
}
#objc
private func hideSection(sender: UIButton) {
let section = sender.tag
func indexPathsForSection() -> [IndexPath] {
var indexPaths = [IndexPath]()
for row in 0..<self.tableViewData[section].count {
indexPaths.append(IndexPath(row: row,
section: section))
}
return indexPaths
}
if self.hiddenSections.contains(section) {
self.hiddenSections.remove(section)
self.historyTableView.insertRows(at: indexPathsForSection(),
with: .fade)
} else {
self.hiddenSections.insert(section)
self.historyTableView.deleteRows(at: indexPathsForSection(),
with: .fade)
}
}
With out sections also you can achieve this. To do this,
1.Return cell height as section height. If user clicks on the cell then return total content height to the particular cell.
2.You need to take an array, if user selects cell, add indexPath number in to array. If selects already expand cell remove it from array. In height for row at index check indexPath is in array or not.
This is one of the way. With sections also you can do that.
//MARK:- UITableView Related Methods
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return arrDict.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
// var cel = tblExpandedTest.dequeueReusableCellWithIdentifier("expCell", forIndexPath: indexPath) as! CDTableViewCell
var cel : CaseHearingTabTVC! = tableView.dequeueReusableCell(withIdentifier: "caseHearingTabCell") as! CaseHearingTabTVC
if(cel == nil)
{
cel = Bundle.main.loadNibNamed("caseHearingTabCell", owner: self, options: nil)?[0] as! CaseHearingTabTVC;
}
//cell?.backgroundColor = UIColor.white
cel.delegate = self
if indexPath != selctedIndexPath{
cel.subview_desc.isHidden = true
cel.subview_remarks.isHidden = true
cel.lblHearingTime.isHidden = true
}
else {
cel.subview_desc.isHidden = false
cel.subview_remarks.isHidden = false
cel.lblHearingTime.isHidden = false
}
return cel
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
selectIndex = true;
if(selectedInd == indexPath.row) {
selectedInd = -1
}
else{
let currentCell = tableView.cellForRow(at: indexPath)! as! CaseHearingTabTVC
cellUpdatedHeight = Float(currentCell.lblHearingTime.frame.origin.y + currentCell.lblHearingTime.frame.size.height) + 2;
selectedInd = -1
tblCaseHearing.reloadData()
selectedInd = indexPath.row
}
let previousPth = selctedIndexPath
if indexPath == selctedIndexPath{
selctedIndexPath = nil
}else{
selctedIndexPath = indexPath
}
var indexPaths : Array<IndexPath> = []
if let previous = previousPth{
indexPaths = [previous]
}
if let current = selctedIndexPath{
indexPaths = [current]
}
if indexPaths.count>0{
tblCaseHearing.reloadRows(at: indexPaths, with: UITableView.RowAnimation.automatic)
}
}
func tableView(_ tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowIndexPath indexPath:IndexPath) {
(cell as! CaseHearingTabTVC).watchFrameChanges()
}
func tableView(_ tableView: UITableView, didEndDisplayingCell cell: UITableViewCell, forRowIndexPath indexPath:IndexPath) {
(cell as! CaseHearingTabTVC).ignoreFrameChanges()
}
func tableView(_ TableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat{
if indexPath == selctedIndexPath{
return CGFloat(cellUpdatedHeight)
}else{
return CaseHearingTabTVC.defaultHeight
}
}
Best approach is to create two different cells for normal card and expanded card.
fileprivate var selectedIndex: Int?
func registerTableViewCells() {
tableView.register(UINib(nibName:Nib.CardCell , bundle: nil), forCellReuseIdentifier: "CardCell")
tableView.register(UINib(nibName:Nib.ExpandedCardCell , bundle: nil), forCellReuseIdentifier: "ExpandedCardCell")
}
override func viewDidLoad() {
super.viewDidLoad()
self.registerTableViewCells()
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
guard let index = selectedIndex else {
return 115
}
if index == indexPath.row{
return 200
}
return 115
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let selected = selectedIndex, selected == indexPath.row{
let cell = tableView.dequeueReusableCell(withIdentifier: "ExpandedCardCell", for: indexPath) as! ExpandedCardCell
return cell
}
let cell = tableView.dequeueReusableCell(withIdentifier: "CardCell", for: indexPath) as! CardCell
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if selectedIndex == indexPath.row{
selectedIndex = nil
}
else{
selectedIndex = indexPath.row
}
UIView.performWithoutAnimation {
tableView.reloadData()
}
}
I am trying to load my different controller using Expandable Tableview but my headerview is set
as switch condition
For Header XXX1 -> two sub menu a and b ..
For Header XXX2-> sub menu c
but for Header XXX3 no sub menu ,, So i will work on click with XXX3(currently working with check SectionData.count == 0 ) but for multiple how to manage .. check out my code
sectionNames = ["xxxx1","xxxx2","xxx3","xxxx4"] //this is main header
sectionItems = [ ["a","b"],[c],[],[],[],[],[],[]]// This is sub menu items
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if (self.expandedSectionHeaderNumber == section) {
let arrayOfItems = self.sectionItems[section] as! NSArray
return arrayOfItems.count;
} else {
return 0;
}
//return arraylist.count
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if (self.sectionNames.count != 0) {
return self.sectionNames[section] as? String
}
return ""
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 60.0;
}
func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
let footerView = UIView(frame: CGRect(x: 0, y: 0, width: tableView.frame.size.width, height: 50))
return footerView
}
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return 0.5
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifer, for: indexPath)
let section = self.sectionItems[indexPath.section] as! NSArray
cell.textLabel?.textColor = UIColor.black
cell.textLabel?.text = section[indexPath.row] as? String
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if indexPath.row == 0 {
}
let indexPath = tableView.indexPathForSelectedRow
// print(indexPath as Any)
//getting the current cell from the index path
let currentCell = tableView.cellForRow(at: indexPath!)! as UITableViewCell
// print(currentCell as Any)
//getting the text of that cell
let currentItem = currentCell.textLabel!.text
print(currentItem!)
switch currentItem {
case "XXXX1":
//// Here unable to do any work
break
case "a":
APICalla()
case "b":
APICallb ()
default:
break
}
return
}
Using this link
Sorry this tutorial is quite poor.
Swift is an object oriented language so use a custom model, a generic Section object with name, items and the information if the section is collapsed
class Section<T> {
var name : String
var items = [T]()
var isCollapsed = false
init(name : String, items : [T] = []) {
self.name = name
self.items = items
}
}
and a suitable struct for the items with a title and a closure to be called in didSelect
struct Item {
let title : String
let selectorClosure : (() -> Void)?
}
Rather than using multiple arrays populate the data source array consistently
var sections = [Section<Item>(name:"xxxx1", items: [Item(title: "a", selectorClosure: APICalla), Item(title: "b", selectorClosure: APICallb)]),
Section<Item>(name:"xxxx2", items: [Item(title: "c", selectorClosure: APICallc)]),
Section<Item>(name:"xxxx3")]
In numberOfRowsInSection return the proper number of items depending on isCollapsed
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let currentSection = sections[section]
return (currentSection.isCollapsed) ? 0 : currentSection.items.count
}
In cellForRow don't use typeless Foundation collection types
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifer, for: indexPath)
let item = sections[indexPath.section].items[indexPath.row]
cell.textLabel?.textColor = UIColor.black
cell.textLabel?.text = item.title
return cell
}
In the method to collapse/expand the sections just toggle isCollapsed
let currentSection = sections[section]
currentSection.isCollapsed.toggle()
and perform the animation
titleForHeaderInSection is much simpler, too
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return sections[section].name
}
In didSelectRow never get any data from the view (the cell) get it from the model (the data source array) and call the selector closure. With this logic a switch is not needed.
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: false)
let item = sections[indexPath.section].items[indexPath.row]
item.selectorClosure?()
}
Swift4 I think this will helps you
// declare globally
var isExpanded : Bool = true
var indexOfSection = Int()
var yourArray = [ModelName]()
override func viewDidLoad() {
super.viewDidLoad()
indexOfSection = 999
}
extension ViewController: UITableViewDelegate, UITableViewDataSource
{
func numberOfSections(in tableView: UITableView) -> Int {
if yourArray.count > 0{
return yourArray.count
}else{
return 0
}
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerView = UIView(frame: CGRect(x: view.frame.origin.x,y: 0 , width: view.frame.size.width ,height: 60))
headerView.backgroundColor = .white
let collapseBtn = UIButton(frame: CGRect(x: headerView.frame.origin.x,y: headerView.frame.origin.y , width: view.frame.size.width ,height: 60))
collapseBtn.addTarget(self, action: #selector(expandSection(sender:)), for: .touchUpInside)
collapseBtn.tag = section
collapseBtn.backgroundColor = .clear
headerView.addSubview(collapseBtn)
return headerView
}
#objc func expandSection(sender:UIButton){
print(sender.tag)
if isExpanded == true{
indexOfSection = sender.tag
mIdeaTableView.reloadData()
isExpanded = false
mTableView.reloadSections([indexOfSection], with: UITableView.RowAnimation.bottom)
}else{
indexOfSection = 999
isExpanded = true
self.mTableView.reloadData()
}
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 60
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if yourArray.count > 0{
if yourArray[section].items!.count > 0{
if indexOfSection == section{
return yourArray[section].items!.count
}else{
return 0
}
}else{
return 0
}
}else{
return 0
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: “CellID”, for: indexPath) as! Cell
if yourArray[indexPath.section]. items!.count > 0{
if yourArray[indexPath.section]. items!.count > 0{
let ideas = yourArray[indexPath.section].ideaItems
if ideas!.count > 0{
if indexOfSection == indexPath.section{
cell.mLbl.text = ideas![indexPath.row].name ?? ""
if ideas![indexPath.row].isExpanded == true{
cell.mAddImg.image = #imageLiteral(resourceName: "tick")
}else{
cell.mAddImg.image = #imageLiteral(resourceName: "edit213-1")
}
}
}
}
}
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 60
}
}
//Structure of my response
{
items = (
{
name = “a”;
},
{
name = “b”;
},
);
name = “xxxx1”;
}
items = (
{
name = “c”;
},
);
name = “xxxx2”;
}
}
This is the screen shot. According to this how to list the data in the tableview:
Already I have code as below.
func numberOfSections(in tableView: UITableView) -> Int {
return questionViewModel.numberOfSections()
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 100
} func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let identifier = "HeaderCell"
var headercell: questionheader! = tableView.dequeueReusableCell(withIdentifier: identifier) as? questionheader
if headercell == nil {
tableView.register(UINib(nibName: "questionheader", bundle: nil), forCellReuseIdentifier: identifier)
headercell = tableView.dequeueReusableCell(withIdentifier: identifier) as? NH_questionheader
} headercell.setReviewData(reviews:questionViewModel.titleForHeaderInSection(atsection:section))
headercell.isUserInteractionEnabled = false
return headercell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return questionViewModel.numberOfRowsIn(section: section)
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
let model = questionViewModel.titleForHeaderInSection(atsection: indexPath.section)
print(model.answerType)
print(model.answerType?.rawValue)
let c = model.answerType
return c!.cellType().getHeight()
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let model = questionViewModel.titleForHeaderInSection(atsection: indexPath.section)
print(model.answerType)
print(model.answerType?.rawValue)
let c = model.answerType
let cellClass = c?.cellType().getClass()
print(cellClass)
let cell = tableView.dequeueReusableCell(withIdentifier: (cellClass?.cellReuseIdentifier())!, for: indexPath) as! BaseCell
print(cell)
cell.selectionStyle = .none
let optionModel = questionViewModel.datafordisplay(atindex: indexPath)
cell.setOptions(Options1: optionModel)
cell.delegate = self
if optionModel.isSelected!
{
print(optionModel.isSelected)
cell.setOptions1(OptionsSelected:optionModel)
}
else {
print(optionModel.isSelected)
cell.setOptions1(OptionsisSelected:optionModel)
}
cell.type = c?.cellType()
print(cell.type)
else if cell.type == .radiotype{
cell.selectionStyle = .none
}
return cell
}
This is my code. But according to this I will get the output as below screen shot.
Actually initially I need to display the section header as: - Please tell about us
After that there is subsection. The subsection questions header are as below: -1. knowledge 2. friendly or not
Then in the subsections display the options. How to do implement?
You can just display "Please tell us about" as UILabel above the table.
Below example may help you out.
let sections = [ // All sections
[ // Sports section
["Baseball", "Softball", "Cricket"], // Bat-and-Ball sub-section
["Field Hockey", "Ice Hockey", "Roller Hockey"] // Hockey subsection
], [ // Engineering section
["Software Engineer", "Electrical Engineer"] // Computer Science subsection
]
]
func numberOfSections(in tableView: UITableView) -> Int {
return sections.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let sectionItems = sections[section]
var numberOfRows: Int = sectionItems.count // For second level section headers
for rowItems: [Any] in sectionItems as? [[Any]] ?? [] {
numberOfRows += rowItems.count // For actual table rows
}
return numberOfRows
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var sectionItems = sections[indexPath.section]
var sectionHeaders = self.sectionHeaders[indexPath.section]
let itemAndSubsectionIndex: IndexPath? = computeItemAndSubsectionIndex(for: indexPath)
let subsectionIndex = Int(itemAndSubsectionIndex?.section ?? 0)
let itemIndex: Int? = itemAndSubsectionIndex?.row
if (itemIndex ?? 0) < 0 {
// Section header
let cell: UITableViewCell = tableView.dequeueReusableCell(withIdentifier: "SECTION_HEADER_CELL", for: indexPath)
cell.textLabel?.text = sectionHeaders[subsectionIndex] as? String
return cell
}
else{
let cell: UITableViewCell = tableView.dequeueReusableCell(withIdentifier: "ROW_CONTENT_CELL", for: indexPath)
cell.textLabel?.text = sectionItems[subsectionIndex][itemIndex ?? 0] as? String
return cell
}
}
func computeItemAndSubsectionIndex(for indexPath: IndexPath?) -> IndexPath? {
var sectionItems = sections[Int(indexPath?.section ?? 0)]
var itemIndex: Int? = indexPath?.row
var subsectionIndex: Int = 0
for i in 0..<sectionItems.count {
// First row for each section item is header
itemIndex = (itemIndex ?? 0) - 1
// Check if the item index is within this subsection's items
let subsectionItems = sectionItems[i] as? [Any]
if (itemIndex ?? 0) < Int(subsectionItems?.count ?? 0) {
subsectionIndex = i
break
} else {
itemIndex = itemIndex! - (subsectionItems?.count)!
}
}
return IndexPath(row: itemIndex ?? 0, section: subsectionIndex)
}
And for objective-C Help you may following URL
http://sapandiwakar.in/nested-sections-in-uitableview/
I have Implemented Tableview in swift, but I want to Make an Expandable TableView, please give me an idea.
This is the code for Tableview,
//MARK: - TableView Delegate and Datasource
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return MenuNameArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MenuTableViewCell", for: indexPath) as! MenuTableViewCell
cell.menuNameLabel.text = NameArray[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
}
Here You Go , Try This Out : -
struct ExpandableNames {
var isExpanded : Bool
var names : [String]
}
struct Contact {
let names : String
}
in your Class - >
var twoDArray = [
ExpandableNames(isExpanded : true,names:["Krishna","Rishabh","Aditya","Chandan","Nipun","Navdeesh","Steve"].map
{
Contact(names: $0)
}),
ExpandableNames(isExpanded : true,names:["Carl","Michal","Tommy","Jennny","Vikram","Swati"].map
{
Contact(names: $0)
}),
ExpandableNames(isExpanded : true,names:["David","dude","dfff","dcc","daa","dee","dsss"].map
{
Contact(names: $0)
}),
ExpandableNames(isExpanded : true,names:[Contact(names: "Pattrick", hasFav: false)])
]
let cellId = "cellID"
let identifier = "attachmentCellID"
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat
{
return 50
}
func numberOfSections(in tableView: UITableView) -> Int
{
return twoDArray.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
if !twoDArray[section].isExpanded
{
return 0
}
return twoDArray[section].names.count
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView?
{
let btn = UIButton(type: .system)
if(section == 0)
{
btn.setTitle("Tap To View Classes", for: .normal)
}
else if(section == 1)
{
btn.setTitle("Tap To View Admins", for: .normal)
}
btn.setTitleColor(.black, for: .normal)
btn.titleLabel?.font = UIFont.boldSystemFont(ofSize: 14)
btn.addTarget(self, action: #selector(handleExpandClose), for: .touchUpInside)
btn.backgroundColor = AppColors.greyBorderColor
btn.tag = section
return btn
}
#objc func handleExpandClose(button : UIButton)
{
let section = button.tag
var indexPaths = [IndexPath]()
for row in twoDArray[section].names.indices
{
let indexPath = IndexPath(row: row, section: section)
indexPaths.append(indexPath)
}
let isExpanded = twoDArray[section].isExpanded
twoDArray[section].isExpanded = !isExpanded
button.setTitle(isExpanded ? "Tap To View Classes" : "Classes", for: .normal)
if isExpanded
{
tableView.deleteRows(at: indexPaths, with: .fade)
}
else
{
tableView.insertRows(at: indexPaths, with: .fade)
}
}
And Rest in cellForRowAt -
let contact = twoDArray[indexPath.section].names[indexPath.row]
cell.textLabel?.text = contact.names
First you will need a model defining if the cell is open, for example, an array of indexPaths:
var openPaths = [IndexPath]()
now when select the cell you toggle wether it is open or not and reload the cell
if let index = openPaths.index(indexPath) {
openPaths.remove(atIndex: index)
} else {
openPaths.append(indexPath)
}
tableView.beginUpdates()
tableView.reloadRows(at: [indexPath], with: .automatic)
tableView.endUpdates()
Now in your cell setup, use a stackview, and in the cell config, you hide the expandable part based on if the cells indexPath is in openPaths
Use heightForRowAt delegate method to change height of cell:-
Add a boolean property to your object say isExpanded, change the value in
didSelectRowAt delegate by
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
YourModel[indexPath.row].isExpanded = !YourModel[indexPath.row].isExpanded
tableView.reloadRows(at: [indexPath], with: Animation)
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return 50
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if YourModel[indexPath.row].isExpanded {
return 200
} else {
return 50
}
}
When we click on table view cell it will expand but if other cells are already expanded they will not collapse means we can see all cells expanded at a time..
i used below code for single cell expand but not getting how to do for multiple cell
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if indexPath.section == 2 && indexPath.row == selectedRowIndex && thereIsCellTapped {
switch indexPath.row{
case 0:
return 119
case 1:
return 93
case 2:
return 117
case 3:
return 135
case 4:
return 93
case 5:
return 230
default:
return 140
}
}
return 55
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let currentCell = tableView.cellForRowAtIndexPath(indexPath) as! PatientDetailsTableViewCell
if indexPath.section == 2 {
self.tableView.cellForRowAtIndexPath(indexPath)?.backgroundColor = UIColor.whiteColor()
if self.selectedRowIndex != -1 {
self.tableView.cellForRowAtIndexPath(NSIndexPath(forRow: self.selectedRowIndex, inSection: 2))?.backgroundColor = UIColor.whiteColor()
}
if indexPath.section == 2 && selectedRowIndex != indexPath.row {
self.thereIsCellTapped = true
self.selectedRowIndex = indexPath.row
currentCell.lblRightArrow.text = "P"
print(selectedRowIndex)
}
else {
// there is no cell selected anymore
self.thereIsCellTapped = false
self.selectedRowIndex = -1
currentCell.lblRightArrow.text = "p"
}
self.tableView.beginUpdates()
self.tableView.endUpdates()
}
}
I used that code for expanding single cell but how to do for multiple cell I am not getting
please help....thanks in advance
After too much work i got the solution for expanding multiple cell so i am sharing here to help others..
for expanding cell you have to store the indexPath in array when click on cell then use that array at tableview delegate method for height.My code is below..
var selectedIndexPath : NSIndexPath?
var indexPaths : Array<NSIndexPath> = []
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
selectedIndexPath = indexPath
if !indexPaths.contains(selectedIndexPath!){
indexPaths += [selectedIndexPath!]
}
else {
let index = indexPaths.indexOf(selectedIndexPath!)
indexPaths.removeAtIndex(index!)
}
tableView.beginUpdates()
tableView.endUpdates()
}
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if indexPaths.count>0 {
if indexPaths.contains(indexPath){
return 200
}
else {
return 50
}
}
return 50
}
Create a new file named as TableViewController subclass of UITableViewController and also take a UITableViewController from storyboard and assign the class TableViewController from Identity Inspector of Xcode and also name the cell identifier as Cell. Then implement the class bellow:
class TableViewController: UITableViewController {
var groupArray = [String]()
var boolArray : [String]!
var dataDic = [String : [String]]()
override func viewDidLoad() {
super.viewDidLoad()
groupArray = ["A","B","C"]
boolArray = [String](count: groupArray.count, repeatedValue: "0")
let groupA = ["CELL ONE","CELL TWO","CELL THREE","CELL FOUR","CELL FIVE"]
let groupB = ["CELL ONE","CELL TWO","CELL THREE","CELL FOUR","CELL FIVE"]
let groupC = ["CELL ONE","CELL TWO","CELL THREE","CELL FOUR","CELL FIVE"]
dataDic["A"] = groupA
dataDic["B"] = groupB
dataDic["C"] = groupC
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int{
return groupArray.count
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
let boolForSec = boolArray[section] as String
if (Int(boolForSec) != 0) {
var arr = dataDic[groupArray[section]]
return arr!.count
}else {
return 0
}
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! UITableViewCell
let boolForSec = boolArray[indexPath.section] as String
if (Int(boolForSec) != 0) {
var arr : [String] = dataDic[groupArray[indexPath.section]]!
cell.textLabel?.text = arr[indexPath.row] as String
}else {
}
// cell.textLabel?.text = "row \(indexPath.row)"
return cell
}
override func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 50
}
override func tableView(tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return 1
}
override func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerView = UIView(frame: CGRectMake(0, 0, tableView.frame.size.width, 40))
headerView.backgroundColor = UIColor.blueColor()
headerView.tag = section
let headerString = UILabel(frame: CGRect(x: 10, y: 10, width: tableView.frame.size.width-10, height: 30)) as UILabel
headerString.text = groupArray[section] as String
headerView .addSubview(headerString)
let headerTapped = UITapGestureRecognizer (target: self, action:"sectionHeaderTapped:")
headerView .addGestureRecognizer(headerTapped)
return headerView
}
func sectionHeaderTapped(tapped: UITapGestureRecognizer){
let section = tapped.view?.tag
let boolForSec = boolArray[section!] as String
if (Int(boolForSec) != 0) {
boolArray[section!] = "0"
}else {
boolArray[section!] = "1"
}
tableView.reloadSections(NSIndexSet(index: section!), withRowAnimation: UITableViewRowAnimation.Fade)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}