I have collapse and expand animation in UITableView. Tableview has two section in which first section data is collapse and expand. This thing perfectly working with ios 10 but in ios 11 Section view repeated or overlapped with cell data which is expanded.
Below is my code
//MARK: -Table View delegate Method
func numberOfSections(in tableView: UITableView) -> Int {
return read_Localizable("titleHelpSection").components(separatedBy: ",").count
}
//MARK: -Table View Datasource Method
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return UITableViewAutomaticDimension
}
func tableView(_ tableView: UITableView, estimatedHeightForHeaderInSection section: Int) -> CGFloat{
return 44.0
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
var headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "headerView")
let arrSection = read_Localizable("titleHelpSection").components(separatedBy: ",")
if headerView == nil
{
headerView = UITableViewHeaderFooterView(reuseIdentifier: "headerView")
headerView?.contentView.backgroundColor = UIColor.white
let lblResult = UILabel()
lblResult.tag = 123456
lblResult.font = AppCommonSNMediumFont()
lblResult.textColor = UIColor.black
lblResult.translatesAutoresizingMaskIntoConstraints = false
headerView?.contentView.addSubview(lblResult)
let seperator = UIView()
seperator.translatesAutoresizingMaskIntoConstraints = false
seperator.backgroundColor = UIColor.black
headerView?.contentView.addSubview(seperator)
headerView?.contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[seperator]|", options: [], metrics: nil, views: ["seperator":seperator]))
headerView?.contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-[lable]-(>=8)-|", options: [], metrics: nil, views: ["lable":lblResult]))
headerView?.contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-[lable]-[seperator(1)]|", options: [], metrics: nil, views: ["lable":lblResult,"seperator":seperator]))
}
if let lblResult = headerView?.contentView.viewWithTag(123456) as? UILabel
{
lblResult.text = arrSection[section]
}
return headerView
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return 20.0
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0
{
return (arrHelpData.count)
}
else
{
return 1
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.section == 0
{
var cell = tableView.dequeueReusableCell(withIdentifier: "HelpCell") as? CellHelp;
if cell == nil {
cell = CellHelp(style: .default, reuseIdentifier: "HelpCell")
cell?.selectionStyle = .none
cell?.txtContain.delegate = self
}
if let objModel = arrHelpData.object(at: indexPath.row) as? HelpModel
{
cell?.lblTitle.text = objModel.helpTitle
if objModel.isExpanded == true
{
cell?.txtContain.text = objModel.helpDesc
}
else
{
cell?.txtContain.text = ""
}
cell?.imgArrow.isHighlighted = !objModel.isExpanded
}
return cell!
}
else
{
var cell = tableView.dequeueReusableCell(withIdentifier: "DefultCell")
if cell == nil
{
cell = UITableViewCell(style: .default, reuseIdentifier: "DefultCell")
cell?.textLabel?.textColor = color1F87A3()
cell?.textLabel?.font = AppCommonSNRegularFont()
cell?.selectionStyle = .none
cell?.textLabel?.numberOfLines = 0
}
cell?.textLabel?.text = read_Localizable("titleSettings")
return cell!
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if indexPath.section == 0 && indexPath.row < (arrHelpData.count)
{
if let objModel = arrHelpData.object(at: indexPath.row) as? HelpModel
{
if objModel.isExpanded == true
{
objModel.isExpanded = false
}
else
{
objModel.isExpanded = true
}
tableView.reloadData()
}
}
}
Actual view
Section overlapped on cell data
This is very frustrating iOS11 issue, something to do around estimatedHeight issue, If you really want to keep the self sized row and header then u need to go with the below approach.
Declare variable which holds the height of the cell/header and store height into that and used it as below:
var cellHeightDictionary: NSMutableDictionary // To overcome the issue of iOS11.2
override func viewDidLoad() {
super.viewDidLoad()
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 125
}
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
cellHeightDictionary.setObject(cell.frame.size.height, forKey: indexPath as NSCopying)
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
if cellHeightDictionary.object(forKey: indexPath) != nil {
let height = cellHeightDictionary.object(forKey: indexPath) as! CGFloat
return height
}
return UITableViewAutomaticDimension
}
This is the only solution which worked for me for iOS11 issues with auto sizing cells. Otherwise people suggest to keep estimatedHeight 0 to get rid off such issues.
In your case first try doing this for cell and that doesn't solve the issue completely then do same for header height also. Hope this helps!
Don't forget to test in both iOS11.1 and iOS11.2.
Related
I have fix make the cell to cliptobounds in the table view and also assign constraints to fix the table position and height.
Below are some parts of my code.
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if hiddenRow.contains(indexPath.row) || hiddenRow2.contains(indexPath.row){
rowHeight.append(300)
return 300 //Expanded
}
else{
rowHeight.append(120)
return 120 //Not Expanded
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "med_reusable_cell", for: indexPath as IndexPath) as! MedListTableViewCell
cell.backgroundColor = TRANSPARENT
cell.layer.cornerRadius = DEFAULT_CORNER_RADIUS
active_table_height.constant = self.view.frame.size.height * 11/36
expired_table_height.constant = self.view.frame.size.height * 11/36
cell overflow
The different between my code and others are
This is an expendable view cell which the height will be change based whether the cell is expended
I use a reusable cell for two tables.
How can I solve this?
You can achieve this by adding a header to each cell, then when you'll click it, reload the table view with the opened cell look at this example :
DataModel :
struct DataItem {
var isExpand: Bool
var title: String
var value:String
init(isExpand:Bool = false, title:String, value:String) {
self.isExpand = isExpand
self.title = title
self.value = value
}
}
Custom Header witch will listen to events :
protocol CustomHeaderViewDelegate: AnyObject {
func headerViewTap(_ section: Int)
}
class CustomHeaderView: UITableViewHeaderFooterView {
weak var delegate: CustomHeaderViewDelegate?
var sectionNumber: Int?
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
let gesture = UITapGestureRecognizer(target: self, action: #selector(CustomHeaderView.tableViewSectionTapped(_:)))
self.addGestureRecognizer(gesture)
}
#objc func tableViewSectionTapped(_ gesture: UIGestureRecognizer) {
if let sectionNumber = sectionNumber{
delegate?.headerViewTap(sectionNumber)
}
}
}
TableView and Custom Header delegates
extension ViewController : UITableViewDelegate, UITableViewDataSource{
//The number of sections fits the number of cells, the current list is an array of DataObject, holding a title and a content.
func numberOfSections(in tableView: UITableView) -> Int {
return self.currentList.count
}
//Each section(group of cells) contains one row
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as UITableViewCell
return cell
}
//update heights for row if the header has been taped
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
let isExpanded = self.currentList[indexPath.section].isExpand
if isExpanded {
return UITableView.automaticDimension
}
return 0
}
//update the estimatedHeightForRowAt if the hader has been taped
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
let isExpanded = self.currentList[indexPath.section].isExpand
if isExpanded{
return UITableView.automaticDimension
}
return 0
}
//returns a custom header
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "Header") as! CustomHeaderView
return headerView
}
}
extension ViewController : CustomeHeaderViewDelegate{
func headerViewTap(_ section: Int) {
selectedItem = self.currentList[section]
let output = self.currentList.map({ (item:DataItem) -> DataItem in
var result = item
if result.title == self.selectedItem?.title{
result.isExpand = !result.isExpand
}
return result
})
self.currentList = output
self.tableView.reloadSections(IndexSet(integer: section), with: UITableView.RowAnimation.automatic)
self.tableView.endUpdates()
}
}
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”;
}
}
Is there a way to customize a section of cell? Probably the easiest way is to design a cell in the storyboard but I do not know how to implement it in my code.
This is what I got so far. It is pretty basic and copied from a tutorial on youtube. So sectionData should be replaced with the input for the customized section/subCell.
The upper cell should be the 'mainCell' and the cell below should be displayed after the mainCell is touched
import UIKit
struct cellData {
var opened = Bool()
var title = String()
var sectionData = [String]()
}
class ViewController: UITableViewController {
var tableViewData = [cellData]()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
tableViewData = [cellData(opened: false, title: "Title1", sectionData: ["Cell1","Cell2","Cell3"]),
cellData(opened: false, title: "Title2", sectionData: ["Cell1","Cell2","Cell3"]),
cellData(opened: false, title: "Title3", sectionData: ["Cell1","Cell2","Cell3"]),
cellData(opened: false, title: "Title4", sectionData: ["Cell1","Cell2","Cell3"])]
}
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
} else {
return 1
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let dataIndex = indexPath.row - 1
if indexPath.row == 0 {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "cell") else {return UITableViewCell()}
cell.textLabel?.text = tableViewData[indexPath.section].title
return cell
} else {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "cell") else {return UITableViewCell()}
cell.textLabel?.text = tableViewData[indexPath.row].sectionData[dataIndex]
return cell
}
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
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)
}
}
}
}
https://www.appcoda.com/expandable-table-view/
you can follow this tutorial. You can reload the cell which you want to expand using below code. I have added in the `didSelectRowAt. Set expandCell variable to true for changing height of cell when reloading.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// Expand View
self.expandCell = true
self.tableView.beginUpdates()
self.tableView.reloadRows(at: [indexPath], with: UITableViewRowAnimation.automatic)
self.tableView.endUpdates()
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if indexPath.row == expandRowIndex && self.expandCell {
return 200
}
return UITableViewAutomaticDimension
}
but the question you asked is irrelevant to the once you want to implement. anyway the answer for your question is, you can implement viewForHeaderInSection and viewForFooterInSection to customize your tableview sections.
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let cell = "create your custom cell here or you can init from your nib"
return cell
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 60
}
if you want to do it in storyboard, just drag and drop UITableViewCell inside your tableview, assign some reuserIdentifier. call this tableview cell in your viewForHeaderInSection
I'm new to swift and working on a project in Swift 3.0 where I have a UITableView with three custom cells. In the first one I just have a image,button and a label. In the second one I have an image plus a label along with expandable and collapsible headers.Thus I have three different sections for this second cell. And lastly the third one is also contains just a label. In the first cell the UILabel is set underneath the image which contains a description about a person (constraints are been set). My requirement is only for the first cell dynamically adjust the cell size based on the size of the description. Help would much appreciate, the code as bellow.
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
print("Number of Sections: \(section)")
return arrayForTableView[section]
}
func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) {
let headerView : UITableViewHeaderFooterView = view as! UITableViewHeaderFooterView
headerView.contentView.backgroundColor = UIColor.yellow.withAlphaComponent(1.0)
}
func tapped(sender: UITapGestureRecognizer)
{
if let tag = sender.view?.tag{
expanedSections[tag] = !expanedSections[tag]
}
tableView.reloadData()
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerView = UITableViewHeaderFooterView()
headerView.tag = section
let tapRecog = UITapGestureRecognizer(target: self, action: #selector(tapped))
tapRecog.numberOfTapsRequired = 1
tapRecog.numberOfTouchesRequired = 1
tapRecog.delegate = self
headerView.addGestureRecognizer(tapRecog)
return headerView
}
func numberOfSections(in tableView: UITableView) -> Int {
return arrayForTableView.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch section {
case 0:
return 1
case 1:
return expanedSections[section] ? getItemsForSection(self.tableData.freeGifts): 0
case 2:
return expanedSections[section] ? getItemsForSection(self.tableData.exclusiveOffers) : 0
case 3:
return expanedSections[section] ? getItemsForSection(self.tableData.allAudios) : 0
case 4:
return expanedSections[section] ? getItemsForSection(self.tableData.testamonials) : 0
default:
return 0
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// var cell: UITableViewCell?
print("Section : \(indexPath.section) : \(indexPath.row)")
switch indexPath.section {
case 0:
let cell = tableView.dequeueReusableCell(withIdentifier: "HealerDetailsCell", for: indexPath) as! HealerDetailsTableViewCell
//cell.aboutLabel.preferredMaxLayoutWidth = (tableView.bounds)
cell.aboutLabel.sizeToFit()
populateHealerDetails.populateTable(cell, self.tableData.healerDetails)
return cell
case 1:
if tableData.freeGifts.count > 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "OffersCell",for: indexPath)
PopulateHealerDetailsAndOffers.populateTable(cell, self.tableData.freeGifts[indexPath.row] as! NSDictionary)
return cell
} else {
let cell = UITableViewCell(style: .default, reuseIdentifier: "Cell")
cell.textLabel?.text = "No Free Gifts At This Time"
return cell
}
case 2:
if tableData.exclusiveOffers.count > 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "OffersCell",for: indexPath)
PopulateHealerDetailsAndOffers.populateTable(cell, self.tableData.exclusiveOffers[indexPath.row] as! NSDictionary)
return cell
}else {
let cell = UITableViewCell(style: .default, reuseIdentifier: "Cell")
cell.textLabel?.text = "No Exclusive Offers At This Time"
return cell
}
case 3:
if tableData.allAudios.count > 0{
let cell = tableView.dequeueReusableCell(withIdentifier: "OffersCell",for: indexPath)
PopulateHealerDetailsAndOffers.populateTable(cell, self.tableData.allAudios[indexPath.row] as! NSDictionary)
return cell
}else{
let cell = UITableViewCell(style: .default, reuseIdentifier: "Cell")
cell.textLabel?.text = "NO Audios To Display"
return cell
}
case 4:
if tableData.testamonials.count > 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "TestamonialsCell", for: indexPath)
return cell
}else{
let cell = UITableViewCell(style: .default, reuseIdentifier: "Cell")
cell.textLabel?.text = "No Testamonials"
return cell
}
default:
let cell = tableView.dequeueReusableCell(withIdentifier: "TestamonialsCell", for: indexPath)
return cell
}
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
//let indexPath = tableView.indexPathForSelectedRow!
//let currentCellValue = tableView.cellForRow(at: indexPath)! as UITableViewCell
}
1.Set the constraint of label.
2.Put numberOflines is equal to 0(Through storyboard or programmatically)
Add this code in viewDidLoad:
tableView.estimatedRowHeight = 300
tableView.rowHeight = UITableViewAutomaticDimension
Use this delegate method
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
For version >= iOS 8
override func viewDidLoad()
{
self.tableView.rowHeight = UITableViewAutomaticDimension;
self.tableView.estimatedRowHeight = 44.0; // set to whatever your "average" cell height is
}
Steps to set constraints on storyboard:here
Important
If multiple lines labels, don't forget set the numberOfLines to 0.
Don't forget label.preferredMaxLayoutWidth =
CGRectGetWidth(tableView.bounds)
I think you want to expand/elapse UITableViewCell depending on the data each cell would have at runtime. I suppose, you already have implemented all of the first aid options regarding UITableView in swift.
Please try this method, which will always be called when your each cell is loaded.
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
//CellAnimator.animateCell(cell: cell, withTransform: CellAnimator.TransformWave, andDuration: 1)
//This commented line possibly might not be your requirement.
//But this is actually used to animate cell while loading.
//You can try some constraints or cell height related stuff here which would definitely work for each cell differently.
//Try calling cell specific loads either.
tableView.scrollToRow(at: indexPath as IndexPath, at: .none, animated: true)
/*let indexPath = IndexPath(item: (selectedCellIndexPath?.row)!, section: 0)
tableView.reloadRows(at: [indexPath], with: .none)
checkTrue = true
*/
}
Please check out this:
import UIKit
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var tableView: UITableView!
var tableViewDataSource = ["fewhf wfh wf wfw h dfw \n\n wufhw f ewfw wf w \n\n f wefe wfef w","fewhf wfh wf wfw h dfw \n\n wufhw f ewfw wf w \n\n f wefe wfef w",
"fewhf wfh wf wfw h dfw \n\n wufhw f ewfw wf w \n\n f wefe wfef w",
"fewhf wfh wf wfw h dfw \n\n wufhw f ewfw wf w \n\n f wefe wfef w"
]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
tableView.delegate = self
tableView.dataSource = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
//Tableview
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return tableViewDataSource.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CellHere") as! TableViewCellHere
cell.cellHere.text = tableViewDataSource[indexPath.row]
cell.cellHere.textAlignment = .justified
return cell
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
if indexPath.row == 0 || indexPath.row == 1
{
return 120.0
}
else
{
return 50.0
}
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if indexPath.row == 0 || indexPath.row == 1
{
return 120.0
}
else
{
return 50.0
}
}
}
This is working for me like this:
enter image description here