Making Simple Accordion TableView in swift? - swift

Is there any way that I can make simple Accordion View in swift like the one at Calendar Event Create? I don't want to use other third party library as well as other code.
I found many answer at github and over google. But,still don't meet my requirement.
Actually I want to add two table view.
The first one is section which show City such as (New York,Los Angles,Las Vegas,etc)
When I tapped one of the city,it will show store address in tableview which mean there are many stores.
All the store and data will got from json.
The accordion view that i want to do is as simple as the one at Calendar App on iOS. But,the data that I gonna insert into two tableView (Section Header & Inner Records inside each section) which is dynamic.
Any Help? Please Guide me,Help me out.
UPDATE : Please take a look

The answer provided by #TechBee works fine using sections for those interested in not using sections and use cells.
The implementation of an Accordion Menu in Swift can be achieved using UITableView in a very simple way, just having two cells one for the parent cells and another for the childs cells and in every moment keep the track for the cells expanded or collapsed because it can change the indexPath.row every time a new cell is expanded or collapsed.
Using the functions insertRowsAtIndexPaths(_:withRowAnimation:) and deleteRowsAtIndexPaths(_:withRowAnimation:) always inside a block of call to tableView.beginUpdates() and tableView.endUpdates() and updating the total of items in the data source or simulating it changes we can achieve the insertion of deletion of new cells in the UITableView in a very easy way with animation included.
I've implemented myself a repository in Github with all the explained above AccordionMenu using Swift and UITableView in a easy and understandable way. It allows several cells expanded or only one at time.

Try this:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
arrayForBool = ["0","0","0"]
sectionTitleArray = ["Pool A","Pool B","Pool C"]
var tmp1 : NSArray = ["New Zealand","Australia","Bangladesh","Sri Lanka"]
var string1 = sectionTitleArray .objectAtIndex(0) as? String
[sectionContentDict .setValue(tmp1, forKey:string1! )]
var tmp2 : NSArray = ["India","South Africa","UAE","Pakistan"]
string1 = sectionTitleArray .objectAtIndex(1) as? String
[sectionContentDict .setValue(tmp2, forKey:string1! )]
self.tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "Cell")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return sectionTitleArray.count
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
if(arrayForBool .objectAtIndex(section).boolValue == true)
{
var tps = sectionTitleArray.objectAtIndex(section) as! String
var count1 = (sectionContentDict.valueForKey(tps)) as! NSArray
return count1.count
}
return 0;
}
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return "ABC"
}
func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 50
}
func tableView(tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return 1
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if(arrayForBool .objectAtIndex(indexPath.section).boolValue == true){
return 100
}
return 2;
}
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerView = UIView(frame: CGRectMake(0, 0, tableView.frame.size.width, 40))
headerView.backgroundColor = UIColor.grayColor()
headerView.tag = section
let headerString = UILabel(frame: CGRect(x: 10, y: 10, width: tableView.frame.size.width-10, height: 30)) as UILabel
headerString.text = sectionTitleArray.objectAtIndex(section) as? String
headerView .addSubview(headerString)
let headerTapped = UITapGestureRecognizer (target: self, action:"sectionHeaderTapped:")
headerView .addGestureRecognizer(headerTapped)
return headerView
}
func sectionHeaderTapped(recognizer: UITapGestureRecognizer) {
println("Tapping working")
println(recognizer.view?.tag)
var indexPath : NSIndexPath = NSIndexPath(forRow: 0, inSection:(recognizer.view?.tag as Int!)!)
if (indexPath.row == 0) {
var collapsed = arrayForBool .objectAtIndex(indexPath.section).boolValue
collapsed = !collapsed;
arrayForBool .replaceObjectAtIndex(indexPath.section, withObject: collapsed)
//reload specific section animated
var range = NSMakeRange(indexPath.section, 1)
var sectionToReload = NSIndexSet(indexesInRange: range)
self.tableView .reloadSections(sectionToReload, withRowAnimation:UITableViewRowAnimation.Fade)
}
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{
let CellIdentifier = "Cell"
var cell :UITableViewCell
cell = self.tableView.dequeueReusableCellWithIdentifier(CellIdentifier) as! UITableViewCell
var manyCells : Bool = arrayForBool .objectAtIndex(indexPath.section).boolValue
if (!manyCells) {
// cell.textLabel.text = #"click to enlarge";
}
else{
var content = sectionContentDict .valueForKey(sectionTitleArray.objectAtIndex(indexPath.section) as! String) as! NSArray
cell.textLabel?.text = content .objectAtIndex(indexPath.row) as? String
cell.backgroundColor = UIColor .greenColor()
}
return cell
}

Related

When I scroll down in my TableView, the cell data changes - Swift 4

When I scroll down in my Table View, the cell data that has disappeared then changes. How can I solve this?
class TableViewController: UITableViewController {
var number = 1
let finishNumber = 10
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return finishNumber
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
cell.textLabel?.text = "\(number)"
number = number + 1
return cell
}
}
You update number every time the table view asks for a cell. That has no direct relation to the row being displayed.
It's unclear why you even have the number property.
If you just want to show the corresponding row number in each cell, get rid of the number property and update:
cell.textLabel?.text = "\(number)"
with:
cell.textLabel?.text = "\(indexPath.row)"
Unclear question but I think you want to scroll to bottom when tableview reload right ?
extension UITableView {
func scrollToBottom(animated: Bool = false) {
let section = self.numberOfSections
if (section > 0) {
let row = self.numberOfRows(inSection: section - 1)
if (row > 0) {
self.scrollToRow(at: IndexPath(row: row - 1, section: section - 1), at: .bottom, animated: animated)
}
}
}
}
When calling, you need to use "async"
DispatchQueue.main.async(execute: {
yourTableView.reloadData()
yourTableView.scrollToBottom()
}
Good luck.

How to make first table view cell as static and then load data from second cell using swift

class FiorstTableViewController: UITableViewController {
var data = ["1","2","3","4","5","6","7","8"]
override func viewDidLoad() {
super.viewDidLoad()
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0{
return 1
}
return data.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("SecondCellfortable") as! CheckTableViewCell
cell.LabelNumber.text = data[indexPath.row]
if indexPath.row == 0 {
print("my first cell")
}
return cell
}
}
I need to load data from the second table view cell and making first cell as static.i have searched other stack overflow answers nothing works out for me..help me to do this one
You have two ways to implement that:
1) You can make two cell for UITableView first one always return when indexPath.row == 0
and for all return your SecondCellfortable
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 50 //row count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if indexPath.row == 0 {
let cell = tableView.dequeueReusableCellWithIdentifier("FirstCellfortable") as! CheckTableViewCellFirst
cell.LabelNumber.text = data[indexPath.row]
print("my first cell")
return cell
} else {
tableView.dequeueReusableCellWithIdentifier("SecondCellfortable") as! CheckTableViewCellSecond
return cell
}
}
OR
2) You can add the UITableViewHeader
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let view = UIView(frame: CGRect(x: 0, y: 0, width: tableView.frame.size.width, height: tableView.sectionHeaderHeight))
view.backgroundColor = UIColor.redColor()
// Do your customization
return view
}
You cannot have static and dynamic cells in the same tableview. I recommend setting up your "first table cell" as a UIView then setting the tableheaderview to your custom view.
let headerView = Bundle.main.loadNibNamed("YourCustomView", owner: self, options: nil)?[0] as? YourCustomView
tableView.tableHeaderView = headerView

swift 2 how to remove empty rows in a tableview in a viewcontroller

Is there a way to reset tableview height so that no empty rows are showed. For an example, a tableview displays 3 rows but there is only one row having real data. I'd like the tableview shrinks it size so there is only one row display
I guess you have a View Controller like this
class Controller: UITableViewController {
private var data: [String?] = ["One", nil, "Three", nil, nil]
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("MyCellID")!
cell.textLabel?.text = data[indexPath.row]
return cell
}
}
Since your model (data in my example) contains nil values you are getting a table view like this
One
_
Three
_
_
Removing the empty rows
Now you want instead a table view like this right?
One
Three
Filtering your model
Since the UI (the table view) is just a representation of your model you need to change your model.
It's pretty easy
class Controller: UITableViewController {
private var data: [String?] = ["One", nil, "Three", nil, nil]
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("MyCellID")!
cell.textLabel?.text = data[indexPath.row]
return cell
}
override func viewDidLoad() {
data = data.filter { $0 != nil }
super.viewDidLoad()
}
}
As you can see, inside viewDidLoad() I removed the bill values from the data array. Now you'll get only cells for real values.
I think you need remove the empty data before execute func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int.
//EDITED
I give you this possible solution, maybe there are more ways to do it, you can try with this.
class MyViewController: UITableViewDataSource, UITableViewDelegate {
private var realHeight = 0
private var data : [String] = []
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("MyCellID")!
cell.textLabel?.text = data[indexPath.row]
self.realHeight += self.tableView.rowHeight //Increase real height value.
return cell
}
func loadData() { //This function reload the data
self.realHeight = 0
self.tableView.reloadData()
//Update tableViewHeight here, use self.realHeight. You can use constraints or update directly the frame.
}
}
The solution is have a counter that represents the sum of all visibles rows height. e.g If you have n cells then your height will be self.tableView.rowHeight * n, then you ever will have an effective height.
I recommend you to create a constraint for the table height and when the cells change, you only need change the constant of the constraint, is more easy than change the frame.

UITableView as inputView for UITextField didSelectRowAtIndexPath randomly not fired

I have a UITableViewController that I'm using to override the inputView of a UITextField subclass. Sometimes the tableView didSelectRowAtIndexPath stops getting fired until a user scolls the table and tries to click again. I can't seem to get a fix on what would cause the cells to become unresponsive. Most of the time it works great, but randomly the cells stop responding to touches until the user scrolls the tableview a bit.
I don't have any TapGestureRecognizers in the view to my knowledge, and have played around with overriding HitTest in the UITableViewCell, but can't seem to figure out the issue.
Any ideas on how to better debug this?
Here is the tableView controller code:
protocol MultiSelectPickerDelegate
{
func didSelectOption(optionIndex:Int, group:Int)
}
class MultiSelectPicker: UITableViewController {
var multiSelectGroups = [MultiSelectGroup](){
didSet{
self.tableView.reloadData()
}
}
var delegate:MultiSelectPickerDelegate?
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return multiSelectGroups.count
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return multiSelectGroups[section].multiSelectOptions.count
}
//MARK: UITableView datasourse function
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
delegate?.didSelectOption(indexPath.row, group: indexPath.section)
}
override func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let view = UIView(frame: CGRectMake(0, 0, tableView.frame.size.width, 40))
let label = UILabel(frame: CGRectMake(10, 0, tableView.frame.size.width, 40))
label.font = UIFont.systemFontOfSize(14)
label.text = multiSelectGroups[section].name
label.textColor = UIColor.whiteColor()
view.addSubview(label)
view.backgroundColor = UIColor.MyAppBlue()
return view
}
override func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 40
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("reuseIdentifier")
if cell == nil{
cell = UITableViewCell(style: .Default, reuseIdentifier: "reuseIdentifier")
}
cell?.textLabel?.text = multiSelectGroups[indexPath.section].multiSelectOptions[indexPath.row].name
return cell!
}
}
I was having same problem. On my table view it was not clicking very last row of my table on the first tap. It highlights it, but didSelectRowAtIndexPath isn't being called. All the other rows work fine. But if I turn the tableview bounce on then it seems to solve the problem
Also try removing Show Selection on Touch in Interface Builder as follows:
Hope it helps

Expandable Sections UITableView IndexPath SWIFT

Im pretty new to coding, I only know Swift. I have found several tutorials to produce drop down sections in a table. Basically it will represent a TV show, the headers will be the seasons and the drop down list of episodes from each season.
I managed to get this working perfectly for what I want from https://github.com/fawazbabu/Accordion_Menu
This all looks good, however I need to be able to select from the drop down items. I have added didSelectRowAtIndexPath with just a simple print of the rows to start with. When I select a row, section or cell, random index paths are returned, the same row can be pressed a second time and return a different value. I thought this was just something I had added to the issue. So I added didSelectRowAtIndexPath to the original code. This has the same issue.
I assume this is because a UIGestureRecogniser is being used as well as didSelectRowAtIndexPath. But I am not sure what the alternative is.
Could someone tell me where I am going wrong please?
import UIKit
import Foundation
class test: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var tableView: UITableView!
var sectionTitleArray : NSMutableArray = NSMutableArray()
var sectionContentDict : NSMutableDictionary = NSMutableDictionary()
var arrayForBool : NSMutableArray = NSMutableArray()
override func awakeFromNib() {
super.awakeFromNib()
tableView.delegate = self
tableView.dataSource = self
arrayForBool = ["0","0"]
sectionTitleArray = ["Pool A","Pool B"]
var tmp1 : NSArray = ["New Zealand","Australia","Bangladesh","Sri Lanka"]
var string1 = sectionTitleArray .objectAtIndex(0) as? String
[sectionContentDict .setValue(tmp1, forKey:string1! )]
var tmp2 : NSArray = ["India","South Africa","UAE","Pakistan"]
string1 = sectionTitleArray .objectAtIndex(1) as? String
[sectionContentDict .setValue(tmp2, forKey:string1! )]
self.tableView.registerNib(UINib(nibName: "CategoryNameTableCell", bundle: NSBundle.mainBundle()), forCellReuseIdentifier: "CategoryNameTableCell")
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return sectionTitleArray.count
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
if(arrayForBool .objectAtIndex(section).boolValue == true){
var tps = sectionTitleArray.objectAtIndex(section) as! String
var count1 = (sectionContentDict.valueForKey(tps)) as! NSArray
return count1.count
}
return 0;
}
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return "ABC"
}
func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 50
}
func tableView(tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return 0
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if(arrayForBool .objectAtIndex(indexPath.section).boolValue == true){
return 50
}
return 2;
}
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let cell = self.tableView.dequeueReusableCellWithIdentifier("CategoryNameTableCell") as! CategoryNameTableCell
cell.downArrow.hidden = false
cell.backgroundColor = UIColor.blackColor()
cell.tag = section
cell.CategoryLabel.text = sectionTitleArray.objectAtIndex(section) as? String
let cellTapped = UITapGestureRecognizer (target: self, action:"sectionHeaderTapped:")
cell .addGestureRecognizer(cellTapped)
return cell
}
func sectionHeaderTapped(recognizer: UITapGestureRecognizer) {
println("Tapping working")
println(recognizer.view?.tag)
var indexPath : NSIndexPath = NSIndexPath(forRow: 0, inSection:(recognizer.view?.tag as Int!)!)
if (indexPath.row == 0) {
var collapsed = arrayForBool .objectAtIndex(indexPath.section).boolValue
collapsed = !collapsed;
arrayForBool .replaceObjectAtIndex(indexPath.section, withObject: collapsed)
var range = NSMakeRange(indexPath.section, 1)
var sectionToReload = NSIndexSet(indexesInRange: range)
self.tableView .reloadSections(sectionToReload, withRowAnimation:UITableViewRowAnimation.Fade)
tableView.reloadData()
}
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{
let cell = self.tableView.dequeueReusableCellWithIdentifier("CategoryNameTableCell") as! CategoryNameTableCell
var manyCells : Bool = arrayForBool .objectAtIndex(indexPath.section).boolValue
if (!manyCells) {
} else {
var content = sectionContentDict .valueForKey(sectionTitleArray.objectAtIndex(indexPath.section) as! String) as! NSArray
cell.CategoryLabel.text = content .objectAtIndex(indexPath.row) as? String
cell.backgroundColor = UIColor( red: 49/255, green: 46/255, blue:47/255, alpha: 1.0 )
}
return cell
}
func tableView(tableView: UITableView, didDeselectRowAtIndexPath indexPath: NSIndexPath) {
println(indexPath.row)
}
I don't know why you need sections to do it what you want, you can achieve with normal cells, no need to complicate at all. Just differentiating the parent cells of the child cells is all you need.
I have a repository in Github that achieve what you want, without using sections neither UITapGestureRecognizer. As soon as possible I'll try to update the project to better performance and more levels of depth in the dropdown.
You can reach it AccordionMenu
, and feel free to post anything you need as issue.
I hope this help you.