UITableView changes position when inserting new rows - ios

I have a UITableView that implements a type of 'infinite scrolling'.
This is done by calculating the IndexPath of the new data and then passing to tableView.insertRows(at: indexPaths, with: .none).
My data is returned from an api in pages of 50. What I am seeing however is when a user scrolls very quickly to the bottom, the table will insert rows and then jump to a section further up, essentially losing their place.
My table is created using this snippet
private func addTableView() {
tableView = UITableView(frame: .zero)
tableView.showsVerticalScrollIndicator = false
tableView.showsHorizontalScrollIndicator = false
tableView.rowHeight = UITableView.automaticDimension
tableView.bounces = true
tableView.estimatedRowHeight = 200
tableView.separatorStyle = .none
tableView.isHidden = true
tableView.backgroundColor = .clear
tableView.tableFooterView = UIView()
addSubview(tableView)
tableView.position(top: topAnchor, leading: leadingAnchor, bottom: bottomAnchor, trailing: trailingAnchor)
tableView.register(UITableViewCell.self, forCellReuseIdentifier: UITableViewCell.reuseID)
}
And the reload is triggered using
func insertRows(_ indexPaths: [IndexPath]) {
UIView.performWithoutAnimation {
tableView.insertRows(at: indexPaths, with: .none)
self.tableView.isHidden = false
}
}
I am using performWithoutAnimation as I did not like any of the animations for inserting rows.
In my view model I inject a FeedTableViewProvider conforming to UITableViewDataSource, UITableViewDelegate and has the following methods
protocol FeedTableViewProviderType: class {
var data: Feed? { get set }
var feed: [FeedItem] { get }
var insertRows: (([IndexPath]) -> Void)? { get set }
var didRequestMoreData: ((Int) -> Void)? { get set }
}
class FeedTableViewProvider: NSObject, FeedTableViewProviderType {
var insertRows: (([IndexPath]) -> Void)?
var didRequestMoreData: ((Int) -> Void)?
var data: Feed? {
didSet {
guard let data = data else { return }
self.addMoreRows(data.feed)
}
}
private(set) var feed = [FeedItem]() {
didSet {
isPaginating = false
}
}
private var isPaginating = false
private func addMoreRows(_ data: [FeedItem]) {
var indexPaths = [IndexPath]()
data.indices.forEach { indexPaths.append(IndexPath(row: feed.count + $0, section: 0)) }
feed.append(contentsOf: data.sorted(by: { $0.props.createdDate > $1.props.createdDate }))
insertRows?(indexPaths)
}
private func requestNextPage() {
guard let currentPage = data?.currentPage, let totalPages = data?.totalPages, currentPage < totalPages else { return }
didRequestMoreData?(currentPage + 1)
}
}
extension FeedTableViewProvider: TableViewProvider {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return feed.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: UITableViewCell.reuseID, for: indexPath)
cell.textLabel?.text = "Cell # \(indexPath.row)"
return cell
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if indexPath.item == feed.count - 1 && !isPaginating {
isPaginating = true
requestNextPage()
}
}
}

I suspect the cause of this is actually to do with using
tableView.rowHeight = UITableView.automaticDimension
....
tableView.estimatedRowHeight = 200
The position changes as the offset is changing when new cells are inserted.
I would start by keeping some sort of cache containing your cell heights
private var sizeCache: [IndexPath: CGFloat] = [IndexPath: CGFloat]()
You can then capture that as the cell is scrolled off screen
func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
sizeCache[indexPath] = cell.frame.size.height
}
Now make sure to apply that size from the cache
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return sizeCache[indexPath] ?? UITableView.automaticDimension
}
Now as cells are inserted and they jump with the new offset, they should render with their correct height, meaning the view should essentially stay on position.

use this code while adding new data into tableview.
UIView.setAnimationsEnabled(false)
self.tableView.beginUpdates()
self.tableView.reloadSections(NSIndexSet(index: 0) as IndexSet, with: UITableViewRowAnimation.none)
self.tableView.endUpdates()

You can use this function on every new row data insertion in Tableview to prevent scrolling to the bottom.
func scrollToTop(){
DispatchQueue.main.async {
let indexPath = IndexPath(row: self.dataArray.count-1, section: 0)
self.tableView.scrollToRow(at: indexPath, at: .top, animated: false)
}
}

Related

Dequeue reusable cell with delay

I have a tableView where I insert 20 rows with delay using DispatchQueue method. First 10 rows appear fine. Problem starts with 11th one when Xcode begins to dequeue reusable rows. In simulator it looks like it starts to insert by 2 rows nearly at the same time (11th+12th, then 13th+14th).
I wonder why is that. Do DispatchQueue and tableView.dequeueReusableCell methods conflict? And if yes how to organize things properly?
var numberOfCells = 0
//My TableView
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TextCell")! as UITableViewCell
return cell
}
//Function that inserts rows
func updateTableView(nextPassageID: Int) {
for i in 0...numberOfCells - 1 {
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + Double(i)) {
self.numberOfCells += 1
let indexPath = IndexPath(row: i, section: 0)
self.tableView.insertRows(at: [indexPath], with: .fade)
}
}
}
I think using a Timer is the better solution in your use case:
private var cellCount = 0
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return cellCount
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = "Cell \(indexPath.row)"
return cell
}
func addCells(count: Int) {
guard count > 0 else { return }
var alreadyAdded = 0
Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] t in
guard let self = self else {
t.invalidate()
return
}
self.cellCount += 1
let indexPath = IndexPath(row: self.cellCount - 1, section: 0)
self.tableView.insertRows(at: [indexPath], with: .fade)
alreadyAdded += 1
if alreadyAdded == count {
t.invalidate()
}
}
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
addCells(count: 20)
}
Your code didn't work for me. May be you missed something to mention in your question. But with the information that I understood, I did some modification and now it runs (tested in iPhone X) as expected. Following is the working full source code.
import UIKit
class InsertCellViewController: UIViewController, UITableViewDataSource {
var dataArray:Array<String> = []
let reusableCellId = "AnimationCellId"
var timer = Timer()
var index = -1
#IBOutlet weak var tableView: UITableView!
// UIViewController lifecycle
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(UITableViewCell.self, forCellReuseIdentifier: reusableCellId)
tableView.separatorStyle = .none
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
updateTableView()
}
// MARK : UITableViewDataSource
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dataArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: reusableCellId)!
cell.textLabel?.text = dataArray[indexPath.row]
return cell
}
// Supportive methods
func updateTableView() {
timer = Timer.scheduledTimer(timeInterval: 2, target: self, selector: #selector(updateCounting), userInfo: nil, repeats: true)
}
#objc func updateCounting(){
if index == 19 {
timer.invalidate()
}
index += 1
let indexPath = IndexPath(row: index, section: 0)
self.tableView.beginUpdates()
self.dataArray.append(String(index))
self.tableView.insertRows(at: [indexPath], with: .fade)
self.tableView.endUpdates()
}
}
Try to place code inside DispatchQueue.main.asyncAfter in
self.tableView.beginUpdates()
self.tableView.endUpdates()

How to customize a section of cell

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

Custom cells for expandable UiTableView

I have a data set that has an inner array and I have to show that Inner array in Expand collapse fashion.
For that I have designed 2 nib files. One for the sections, and other for the cell in sections.
I have attached UitableView and the delegated methods. I am successful to show the Header view, I am registering the header view like this.
let nib = UINib.init(nibName: "headerItemSavedListCell", bundle: nil)
self.lvSavedList.register(nib, forCellReuseIdentifier: "headerItemSavedListCell")
and for cell I am doing in the following method
if(indexPath.row == 0){
let header = Bundle.main.loadNibNamed("headerItemSavedListCell", owner: self, options: nil)?.first as! headerItemSavedListCell
return header
}else{
let cell = Bundle.main.loadNibNamed("ItemSavedListCell", owner: self, options: nil)?.first as! ItemSavedListCell
return cell
}
But its not working.
**So my questions is: **
How to load the inner cell view ?
How to expand collapse cell view that lies inside the Sections?
Please help if you have any tutorial regarding expandable Uitableview
the class i am using here are connected to xibs
make xib of view and bind below class
so first you have to make headerview like below
protocol HeaderDelegate:class{
func didSelectHeader(Header:HeaderFooter,at index:Int)
}
class HeaderFooter: UITableViewHeaderFooterView {
#IBOutlet weak var lblTitle: UILabel!
weak var delegate:HeaderDelegate?
var Expand = false
override func awakeFromNib() {
let tap = UITapGestureRecognizer.init(target: self, action: #selector(didSelect(_:)))
self.addGestureRecognizer(tap)
self.isUserInteractionEnabled = true
}
#objc func didSelect(_ tap:UITapGestureRecognizer)
{
delegate?.didSelectHeader(Header: self, at: self.tag)
}
override func prepareForReuse() {
Expand = false
}
}
above i added tap gesture to detect touch on headerViews
next make cell like below
class ExpandableCell: UITableViewCell {
var isExpanded = false
override func awakeFromNib() {
super.awakeFromNib()
isExpanded = false
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
in your view controller
tblView.register(UINib.init(nibName: "HeaderFooter", bundle: nil), forHeaderFooterViewReuseIdentifier: "HeaderFooter")
in tablview dataSorce and Delegate Method
func numberOfSections(in tableView: UITableView) -> Int {
return numberofsections
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return numberofrows
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "ExpandableCell") as? ExpandableCell else {
return UITableViewCell()
}
//configure cell here
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
guard let header = tableView.headerView(forSection: indexPath.section) as? HeaderFooter
else {return 0}
if header.Expand
{
return UITableViewAutomaticDimension
}
else
{
return 0
}
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return UITableViewAutomaticDimension
}
func tableView(_ tableView: UITableView, estimatedHeightForHeaderInSection section: Int) -> CGFloat {
return 50
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
guard let headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "HeaderFooter") as? HeaderFooter else {return nil}
//configure header View here
headerView.tag = section
return headerView
}
//MARK:-headerDelegate
func didSelectHeader(Header: HeaderFooter, at index: Int) {
Header.Expand = !Header.Expand
//comment below part if you dont want to collapse other rows when other section opened
for i in 0..<tblView.numberOfSections
{
if i != index
{
guard let header = tblView.headerView(forSection: i) as? HeaderFooter else {return}
header.Expand = false
for j in 0..<tblView.numberOfRows(inSection: i)
{
tblView.reloadRows(at: [IndexPath.init(row: j, section: i)], with: .automatic)
}
}
else
{
for j in 0..<tblView.numberOfRows(inSection: i)
{
tblView.reloadRows(at: [IndexPath.init(row: j, section: i)], with: .automatic)
}
}
}
}

Dropdown table view with multiple and single selection in iOS swift?

I have filter which is done in dropdown table view in an view controller. The dropdown table view contains three section namely section 1, section 2 and section 3. For section 1 and section 3 should single selection and section 2 should be multiple selection. When tapping section 1 it expands table view cell and when tapping on section 2 will expand and section 1 will close the expansion.
When selecting the option from each section should stored even user close and reopens the filter dropdown table view.
I have four questions:
When user tap on different section it should automatically close already open sections?
Table view should adjust height and its position based number cells in each sections?
how to do multiple and single selection for three sections?
selected should be stored even if dropdown table view is close and reopened.
Here is the code which tried so far all question which i have mentioned above:
extension HomeViewController : UITableViewDelegate, UITableViewDataSource, ExpandableHeaderViewDelegate {
func numberOfSections(in tableView: UITableView) -> Int {
if locationListBool == true {
return 1
} else {
return sectionss.count
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if locationListBool == true {
return autocompleteplaceArray.count
} else {
return sectionss[section].category.count
}
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
if locationListBool == true {
return 0
} else {
return 30
}
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if locationListBool == true {
return 30
} else {
if (sectionss[indexPath.section].expanded) {
return 30
} else {
return 0
}
}
}
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
if locationListBool == true {
return 0
} else {
return 2
}
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
if locationListBool == true {
return nil
} else {
let header = ExpandableHeaderView()
header.contentView.backgroundColor = UIColor.white
header.customInit(title: sectionss[section].genre, section: section, delegate: self)
return header
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if locationListBool == true {
let cell = tableView.dequeueReusableCell(withIdentifier: "placecell", for: indexPath) as! locationNameTableViewCell
guard autocompleteplaceArray.count > 0 else {
return cell
}
cell.locationName.text = autocompleteplaceArray[indexPath.row]
return cell
} else {
let cell = dropDownTbl.dequeueReusableCell(withIdentifier: "dropDownCell", for: indexPath) as! dropDownCell
cell.dropDownLbl.text = sectionss[indexPath.section].category[indexPath.row]
cell.selectionStyle = .none
return cell
}
}
func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) {
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
cell.backgroundColor = UIColor.clear
if locationListBool == true {
let lastRowIndex = tableView.numberOfRows(inSection: 0)
if indexPath.row == lastRowIndex - 1 {
tableView.allowsSelection = true
} else {
tableView.allowsSelection = true
}
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if locationListBool == true {
if let indexPath = tableView.indexPathForSelectedRow {
let currentCell = tableView.cellForRow(at: indexPath) as! UITableViewCell
searchText.text = (currentCell.textLabel?.text)
searchText.text = autocompleteplaceArray[indexPath.row]
placeIDString = autocompletePlaceIDArray[indexPath.row]
print("placeIDString::::\(String(describing: placeIDString))")
if placeIDString != nil {
getPlaceIDLatLong(placeIDs: placeIDString!)
print("get lat long \(getPlaceIDLatLong(placeIDs: placeIDString!))")
}
// PrefsManager.sharedinstance.lastlocation = searchText.text
locationText = searchText.text
print("locationText::::\(String(describing: locationText))")
}
self.locationTableList.isHidden = true
}
else {
}
}
func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
switch indexPath.section {
case 0:
if let previousIndexPath = indexPathOfSelectedRowPaidBy {
dropDownTbl.deselectRow(at: previousIndexPath as IndexPath, animated: false)
dropDownTbl.cellForRow(at: previousIndexPath as IndexPath)?.accessoryType = UITableViewCellAccessoryType.none
}
indexPathOfSelectedRowPaidBy = indexPath as NSIndexPath?
dropDownTbl.cellForRow(at: indexPath)?.accessoryType = UITableViewCellAccessoryType.checkmark
case 1:
dropDownTbl.cellForRow(at: indexPath)?.accessoryType = UITableViewCellAccessoryType.checkmark
default:
break
}
return indexPath
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath)
{
switch indexPath.section {
case 0:
if let previousIndexPath = indexPathOfSelectedRowPaidBy {
dropDownTbl.deselectRow(at: previousIndexPath as IndexPath, animated: false)
dropDownTbl.cellForRow(at: previousIndexPath as IndexPath)?.accessoryType = UITableViewCellAccessoryType.none
}
indexPathOfSelectedRowPaidBy = nil
case 1:
dropDownTbl.cellForRow(at: indexPath)?.accessoryType = UITableViewCellAccessoryType.none
default:
break
}
}
func toogleSection(header: ExpandableHeaderView, section: Int) {
sectionss[section].expanded = !sectionss[section].expanded
dropDownTbl.beginUpdates()
if sectionss[0].expanded{
dropDownTbl.layer.frame = CGRect(x: 15, y: 152, width: 345, height: 300)
} else if sectionss[1].expanded {
dropDownTbl.layer.frame = CGRect(x: 15, y: 152, width: 345, height: 230)
} else if sectionss[2].expanded {
dropDownTbl.layer.frame = CGRect(x: 15, y: 152, width: 345, height: 330)
} else {
dropDownTbl.layer.frame = CGRect(x: 15, y: 152, width: 345, height: 90)
}
for i in 0 ..< sectionss[section].category.count {
dropDownTbl.reloadRows(at: [IndexPath(row: i, section: section)], with: .automatic)
}
dropDownTbl.endUpdates()
}
}
Expandable table view header::
import UIKit
protocol ExpandableHeaderViewDelegate {
func toogleSection(header: ExpandableHeaderView, section: Int)
}
class ExpandableHeaderView: UITableViewHeaderFooterView {
var delegate: ExpandableHeaderViewDelegate?
var section: Int!
var collapaseHandlerArray = [String]()
let button = UIButton()
let button2 = UIButton()
override init(reuseIdentifier: String?){
super.init(reuseIdentifier: reuseIdentifier)
self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(selectheaderAction)))
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
#objc func selectheaderAction(gestureRecognizer: UITapGestureRecognizer) {
let cell = gestureRecognizer.view as! ExpandableHeaderView
}
func customInit(title: String, section: Int, delegate: ExpandableHeaderViewDelegate) {
self.textLabel?.text = title
self.section = section
self.delegate = delegate
}
override func layoutSubviews() {
super.layoutSubviews()
self.textLabel?.font = UIFont(name: "Nunito-Light", size: 12)
self.textLabel?.textColor = UIColor(red: 64.0/255, green: 75.0/255, blue: 105.0/255, alpha: 1.0)
self.contentView.backgroundColor = UIColor.white
}
}
Dropdown table view cell:
class dropDownCell: UITableViewCell {
#IBOutlet weak var dropDownLbl: UILabel!
#IBOutlet weak var dropDwnBtn: UIButton!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
Here is the screen shots when selection done in cell and after reopen filter selections are removed or options selected are changed, sections are not closed already expanded sections. Excepted result:
There is no built-in support for allowing differing numbers of cells to be selected in different sections of a table view.
However, the UITableViewDelegate protocol includes the function tableView(_:willSelectRowAt:).
If you read the docs on that function, it says:
Return Value
An index-path object that confirms or alters the selected
row. Return an NSIndexPath object other than indexPath if you want
another cell to be selected. Return nil if you don't want the row
selected.
So you should be able to set your view controller up as the delegate of the table view, set the allowsMultipleSelection flag to true, and implement logic in the tableView(_:willSelectRowAt:) function that provides the selection rules you want.
Take a stab at writing such a function and if you have trouble, post your code, tell us how it fails to meet your needs, and we'll try to help you fix it.

Make first section rows cell already expanded in Expandable table view cells

In my App I implemented Expandable tableview. It's worked perfectly but now I want to change the first section was already expandable mode but I unable to do that.
Here I have implemented my own native code for creating expandable tableview not using any third party libraries.
Here I post my Full code for Expandable TableView:
#IBOutlet weak var tableViewSecond: UITableView!
var hidden = [true]
override func viewDidLoad() {
super.viewDidLoad()
tableViewSecond.delegate = self
tableViewSecond.dataSource = self
InspectionArray = [["inspection_name":"AVM Inspection"], ["inspection_name":"Simple Inspection"], ["inspection_name":"BVM Inspection"]]
InspectionSectionArray = [["inspection_Session":"Current Inspection"], ["inspection_Session":"Past Inspection"], ["inspection_Session":"Future Inspection"]]
}
func numberOfSections(in tableView: UITableView) -> Int {
for _ in 0..<InspectionSectionArray.count {
hidden.append(true)
}
return InspectionSectionArray.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if hidden[section] {
return 0
} else {
return InspectionArray.count
}
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerView = UIView()
headerView.backgroundColor = UIColor.orange
headerView.tag = section
let label = UILabel()
label.text = (InspectionSectionArray[section] as AnyObject).value(forKey: "inspection_Session") as? String
label.frame = CGRect(x: 45, y: 5, width: 150, height: 35)
label.font = UIFont.boldSystemFont(ofSize: 15)
headerView.addSubview(label)
label.tag = section
let tapForheaderView = UITapGestureRecognizer(target: self, action: #selector(SecondViewController.tapFunction))
headerView.isUserInteractionEnabled = true
headerView.addGestureRecognizer(tapForheaderView)
return headerView
}
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return 2
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 45
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCell(withIdentifier: "SecondTableViewCell", for: indexPath) as? SecondTableViewCell
if cell == nil {
cell = UITableViewCell(style: .default, reuseIdentifier: "SecondTableViewCell") as? SecondTableViewCell;
}
cell!.dataLbl.text = (InspectionArray[indexPath.row] as AnyObject).value(forKey: "inspection_name") as? String
return cell!
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
print(indexPath.row)
print("\(indexPath.section)","\(indexPath.row)")
}
func tapFunction(sender:UITapGestureRecognizer) {
let section = sender.view!.tag
let indexPaths = (0..<InspectionArray.count).map { i in return IndexPath(item: i, section: section) }
hidden[section] = !hidden[section]
tableViewSecond.beginUpdates()
if hidden[section] {
tableViewSecond.deleteRows(at: indexPaths, with: .fade)
} else {
tableViewSecond.insertRows(at: indexPaths, with: .fade)
}
tableViewSecond.endUpdates()
}
I believe you only need to change this:
// declare hidden as this
var hidden: [Bool] = []
override func viewDidLoad() {
super.viewDidLoad()
InspectionArray = [["inspection_name":"AVM Inspection"], ["inspection_name":"Simple Inspection"], ["inspection_name":"BVM Inspection"]]
InspectionSectionArray = [["inspection_Session":"Current Inspection"], ["inspection_Session":"Past Inspection"], ["inspection_Session":"Future Inspection"]]
// initialize hidden array so that the first is false and the rest true
hidden = Array<Bool>(repeating: true, count: InspectionSectionArray.count)
hidden[0] = false
tableViewSecond.delegate = self
tableViewSecond.dataSource = self
}
// and change numberOfSections to this
func numberOfSections(in tableView: UITableView) -> Int {
return InspectionSectionArray.count
}
Just change this function
func tapFunction(sender: UITapGestureRecognizer?) {
var section = 0
if sender != nil {
section = sender!.view!.tag
}
let indexPaths = (0..<InspectionArray.count).map { i in return IndexPath(item: i, section: section) }
hidden[section] = !hidden[section]
tableViewSecond.beginUpdates()
if hidden[section] {
tableViewSecond.deleteRows(at: indexPaths, with: .fade)
} else {
tableViewSecond.insertRows(at: indexPaths, with: .fade)
}
tableViewSecond.endUpdates()
}
And call self.tapFunction(sender: nil) in viewdidLoad.

Resources