Swift3: UITableViewCell and UIButton - Show and hide UITableViewCell by clicking the UIButton - ios

I'm pretty new to Swift 3.0 and am currently working on a school project on IOS app.
I'm having difficulties with the linkage of the UITableViewCell and UIButton.
This is how my app looks like
Basically, what I'm trying to do is that when I click the UIButton, it will decrease the height of cells (Diary and Trend) to 0 (to hide the cells)
and
increase the height of cells (Share and How to use the BP Monitor)(to show the cells) - assuming that the cells (Share and How to use the BP Monitor) are 'hidden' now.
Appreciate if there's an example to follow.
Thank you very much :)
*After trying the example given by #Sandeep Bhandari
This is the code in my ViewController:
class TableViewController: UITableViewController {
var expandedCellIndex : IndexPath? = nil
override func numberOfSections(in tableView: UITableView) -> Int { return 1 }
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell : UITableViewCell! = nil
if indexPath == expandedCellIndex {
cell = tableView.dequeueReusableCell(withIdentifier: "expandedCell") as! ExpandedTableViewCell
(cell as! ExpandedTableViewCell).label1.text = "shown"
(cell as! ExpandedTableViewCell).label2.text = "Efgh"
(cell as! ExpandedTableViewCell).label3.text = "xyz"
}
else {
cell = tableView.dequeueReusableCell(withIdentifier: "simpleCell") as! SimpleTableViewCell
(cell as! SimpleTableViewCell).label.text = "abcd"
}
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if self.expandedCellIndex == indexPath {
self.expandedCellIndex = nil
}
else {
self.expandedCellIndex = indexPath
}
self.tableView.reloadData()
}
override func viewDidLoad() {
super.viewDidLoad()
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 140
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
I couldn't get the same output as the answer suggested.
My Output
Thank you so much!

Here is a simple code which will not only allow you to expand specific cell you can expand all the cell. In case you want to expand only one cell you can do that as well.
Step 1:
Declare two dynamic prototype cells in storyboard.
Lets call the red one as SimpleCell and Green one as ExpandedCell.
Step 2:
Add reusable identifier for each cell.
Step 3
Create custom classes for both these cells and created IBOutlets to labels.
Step 4:
Now write this in ViewDidLoad of your VC.
override func viewDidLoad() {
super.viewDidLoad()
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 140
}
Step 5:
Assuming there can only be one cell expanded at a time I am declaring single IndexPathVariable you can always declare array if you want multiple cells expanded.
var expandedCellIndex : IndexPath? = nil
Step 6:
Write tableView data source code.
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell : UITableViewCell! = nil
if indexPath == expandedCellIndex {
cell = tableView.dequeueReusableCell(withIdentifier: "expandedCell") as! ExpandedTableViewCell
(cell as! ExpandedTableViewCell).label1.text = "abcd"
(cell as! ExpandedTableViewCell).label2.text = "Efgh"
(cell as! ExpandedTableViewCell).label3.text = "xyz"
}
else {
cell = tableView.dequeueReusableCell(withIdentifier: "simpleCell") as! SimpleTableViewCell
(cell as! SimpleTableViewCell).label.text = "abcd"
}
return cell
}
Step 7:
I am expanding cell on cell tap you can do it on button tap as well :) write this logic in IBAction of button lemme write it in didSelectRow.
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if self.expandedCellIndex == indexPath {
self.expandedCellIndex = nil
}
else {
self.expandedCellIndex = indexPath
}
self.tableView.reloadData()
}
Conclusion :
Disclaimer
I am using Dynamic cell height hence has not written heightForRowAtIndexPath as height of the cell will be automatically calculated.
If you are using any UIComponents in cell which does not have implicit size, you might have to implement heightForRowAtIndexPath as below.
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if self.expandedCellIndex == indexPath {
return expnadedCellHeight
}
else {
return simpleCellHeight
}
}

Related

One tableView With two different cell.xib ,If i press one cell it need to show second cell. How?

I take tableView and two different cell.xib files , I want to display when i click cell1 then i should display cell2 data.
class TableView: UIViewController,UITableViewDelegate,UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
var array1 = ["Click1","Click2"]
var array2 = [[ "one","two","Three"],["Four","Five"]]
var selectedArray = [String]()
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(UINib(nibName: "MainCell", bundle: nil) , forCellReuseIdentifier: "MainCell")//This is used to add xib file with identifier
tableView.register(UINib(nibName: "SecondCell", bundle: nil) , forCellReuseIdentifier: "SecondCell")
}
//MARK:DataSource Methods
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return array1.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCell(withIdentifier: "MainCell") as! MainCell
cell.textLabel?.text = array1[indexPath.row]
return cell
}
//MARK: tableViewDelegate Method
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = self.tableView.dequeueReusableCell(withIdentifier: "SecondCell") as! SecondCell
selectedArray = array2[indexPath.row]
cell.textLabel?.text = selectedArray[indexPath.row]
}
}
Tell how can i do that if i press first cell it should show 2nd cell values as per indexPath.row
I think you want to achieve expandable cells. You can use the header cell for this one.
You might want to read this:
Hope this helps!
For example, you can add a flag which indicates whether the first cell was tapped.
var wasFirstCellTapped = false
Then numberOfRowsInSection depends on this flag:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return wasFirstCellTapped ? array1.count : 1
}
In didSelectRowAt:indexPath set this flag to true. And perform tableView changes. The simplest way is to call tableView.reloadData(). But you can animate this using insertRowsAtIndexPaths
var allCellsArray = ["Click1","Click2"]
var displayingCellsArray = ["Click1"]
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return displayingCellsArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell: UITableViewCell!
if displayingCellsArray[indexPath.row] == "Click1" {
cell = self.tableView.dequeueReusableCell(withIdentifier: "MainCell") as! MainCell
}else {
cell = self.tableView.dequeueReusableCell(withIdentifier: "SecondCell") as! SecondCell
}
cell.textLabel?.text = displayingCellsArray[indexPath.row]
return cell
}
//MARK: tableViewDelegate Method
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let data = displayingCellsArray[indexPath.row]
//Below logic will show (Cell2 if Cell1 is clicked and hide Cell1)
// (and show Cell1 if clicked Cell2 and hide Cell2)
if data == "Click1" {
displayingCellsArray = [allCellsArray.last!]
}else {
displayingCellsArray = [allCellsArray.first!]
}
tableView.reloadData()
}
You should handle such logic in a separate datasource array by adding/removing that sort of data from this array which shows particular cells.
E.g
In your case you have two values in array Click1 & Click2
Click1 shows MainCell & Click2 shows SecondCell
So first add Click1 in your array and when this cell is tapped simply add Click2 in your array and reload. If you want to remove MainCell when SecondCell is displaying then while adding Click2 simply remove Click1
You can expand the second cell on click of first cell..
Refer this example to expand and collapse the cells
https://github.com/jonasman/JNExpandableTableView

Swift - Create a UITableViewController with one static cell at the top and the rest be dynamic cells?

I am having trouble making a table view with one static cell at the very top of my table view. This table view will hold 4 buttons and the rest of the views will hold a list of the user's songs.
I have already looked into other answers on here but all of them seem to be written in Objective C not swift.
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return sortedSongs.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "RecentCell", for: indexPath) as! RecentCell
//cell.songTitle.text = albumList[indexPath.row]
//cell.songArtist.text = artistList[indexPath.row]
//cell.songImage.image = imageList[indexPath.row]
return cell
}
This is what I have been using to just set the regular table views. How would I go about modifying this code to allow for a static cell at the top and dynamic cells for the rest?​
Don't use Static Cells. Choose Dynamic Prototypes in your table view and create 2 prototype cells.
And return first cell in first section, other cells in second section
override func numberOfSections(in tableView: UITableView) -> Int {
return 2
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0 {
return 1
} else if section == 1 {
return sortedSongs.count
} else if section == 2 {
return anotherArrayCount
}
return 0
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.section == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "FirstCell", for: indexPath) as! FirstCell
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "RecentCell", for: indexPath) as! RecentCell
//cell.songTitle.text = albumList[indexPath.row]
//cell.songArtist.text = artistList[indexPath.row]
//cell.songImage.image = imageList[indexPath.row]
return cell
}
}
You can use the HeaderView of the table for that, just give your custom view to the . tableHeaderView property
Example:
override func viewDidLoad() {
super.viewDidLoad()
let myCustomView = MyCustomView()
tableView.tableHeaderView = myCustomView
}
Documentation: https://developer.apple.com/documentation/uikit/uitableview/1614904-tableheaderview

expand and contract label not working in UITableViewCell

I am trying to use a UITableView and have cell contents which will expand or contract when the user clicks on the label.
However, the behavior I'm seeing is that the cell will contract (e.g. I am changing the label's numberOfLines from 0 to 1, and then the label will contract). However, when I change the label's numberOfLines from 1 to 0 it doesn't expand.
I put together a simple test program to show the issue I'm having.
I'm using a UITapGestureRecognizer to handle the tap event for the label, and that is where I expand or contract the label. Does anyone have any idea what I'm doing wrong?
Here's my storyboard and view controller code.
import UIKit
class MyCell : UITableViewCell {
#IBOutlet weak var myLabel: UILabel!
}
class TableViewController: UITableViewController {
let cellID = "cell"
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.rowHeight = UITableViewAutomaticDimension
self.tableView.estimatedRowHeight = 75
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 12
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return "section " + String(section)
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return 4
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: self.cellID, for: indexPath) as! MyCell
cell.myLabel.isUserInteractionEnabled = true
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(self.handleCellTapped(_:)))
cell.myLabel!.addGestureRecognizer(tapGesture)
// Configure the cell...
if indexPath.row % 2 == 0 {
cell.myLabel?.numberOfLines = 1
cell.myLabel.text = "This is some long text that should be truncated. It should not span over multiple lines. Let's hope this actually works. \(indexPath.row)"
} else {
cell.myLabel?.numberOfLines = 0
cell.myLabel.text = "This is some really, really long text. It should span over multiple lines. Let's hope this actually works. \(indexPath.row)"
}
return cell
}
#objc func handleCellTapped(_ sender: UITapGestureRecognizer) {
print("Inside handleCellTapped...")
guard let label = (sender.view as? UILabel) else { return }
//label.translatesAutoresizingMaskIntoConstraints = false
// expand or contract the cell accordingly
if label.numberOfLines == 0 {
label.numberOfLines = 1
} else {
label.numberOfLines = 0
}
}
}
Do two things.
Set the Vertical Content hugging priority and
Vertical Content compression resistance priority of the Label to 1000.
After changing the number of lines of the Label call the tableView.beginUpdates() and tableView.endUpdates()
This should work definitely.
You almost get it, but here is a couple of things you should care about.
First, handle the label by UIGestureRecognizer it's quite overhead. For that purposes UITableViewDelegate has own method:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
Second, you're using self-sizing cell, because of
self.tableView.rowHeight = UITableViewAutomaticDimension
self.tableView.estimatedRowHeight = 75
There is one important rule for that: you should pin myLabel to each side of superview (see official docs why):
Last step, when the numberOfLines changed, you should animate cell's height (expand or collapse) without reloading the cell:
tableView.beginUpdates()
tableView.endUpdates()
Docs:
You can also use this method followed by the endUpdates() method to animate the change in the row heights without reloading the cell.
Full code:
class MyCell: UITableViewCell {
#IBOutlet weak var myLabel: UILabel!
}
class TableViewController: UITableViewController {
let cellID = "cell"
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.rowHeight = UITableViewAutomaticDimension
self.tableView.estimatedRowHeight = 75
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 12
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return "section " + String(section)
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 4
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: self.cellID, for: indexPath) as! MyCell
cell.selectionStyle = .none // remove if you need cell selection
if indexPath.row % 2 == 0 {
cell.myLabel?.numberOfLines = 1
cell.myLabel.text = "This is some long text that should be truncated. It should not span over multiple lines. Let's hope this actually works. \(indexPath.row)"
} else {
cell.myLabel?.numberOfLines = 0
cell.myLabel.text = "This is some really, really long text. It should span over multiple lines. Let's hope this actually works. \(indexPath.row)"
}
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: false)
guard let cell = tableView.cellForRow(at: indexPath) as? MyCell else { return }
cell.myLabel.numberOfLines = cell.myLabel.numberOfLines == 0 ? 1 : 0
tableView.beginUpdates()
tableView.endUpdates()
}
}
Try
tableView.beginUpdates()
if label.numberOfLines == 0 {
label.numberOfLines = 1
} else {
label.numberOfLines = 0
}
tableView.endUpdates()

how to expand or replace the cell with another cell, when an particular cell select in table view

I have already asked this doubt/problem in SO. but not get get solution. Please help me out....
i have one table view which will show the list of name data till 10 datas. But what i need is , when user press any cell, that cell should be replace with another cell, which have some image, phone number, same data name. How to do that.
I have two xib : 1. normalcell, 2. expandable/replace cell
Here is my viewconrolelr.swift
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var Resultcount: UILabel!
var tableData = ["thomas", "Alva", "Edition", "sath", "mallko", "techno park",... till 10 data]
let cellSpacingHeight: CGFloat = 5
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
var nib = UINib(nibName:"customCell", bundle: nil)
tableView.registerNib(nib, forCellReuseIdentifier: "cell")
Resultcount.text = "\(tableData.count) Results"
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return self.tableData.count
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return cellSpacingHeight
}
// Make the background color show through
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerView = UIView()
headerView.backgroundColor = UIColor.clearColor()
return headerView
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell:customCell = self.tableView.dequeueReusableCellWithIdentifier("cell") as! customCell
cell.vendorName.text = tableData[indexPath.section]
return cell
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Starting my cell will look like this :
When i press that cell, i need some thing to do like this with replace ment of like below cell :
But when i press same cell again, again it should go to normal cell.
How to do that ??
First modify your tableView:cellForRowAtIndexPath: implementation as follows. Then you need to implement the click handler. One way would be in the MyCell class. Another would be to override selectRowAtIndexPath. Without knowing more about what you want (e.g. multiple vs single selection), it's hard to give actual code but here's something.
BOOL clickedRows[MAX_ROWS]; // Init this array as all false in your init method. It would be better to use NSMutableArray or something similar...
// selectRowAtIndexPath code
int row = indexPath.row
if(clickedRows[row]) clickedRows[row]=NO; // we reverse the selection for the row
else clickedRows[row]=YES;
[self.tableView reloadData];
// cellForRowAt... code
MyCell *cell = [tableView dequeueResuableCell...
if(cell.clicked) { // Nice Nib
[tableView registerNib:[UINib nibWithNibName... for CellReuse...
} else { // Grey Nib
[tableView registerNib:[UINib nibWithNibName... for CellReuse...
}
You need to create two independent cell on xib. Then you can load using check.You can copy and paste it will work perfectly.
in cellForRowAt like this:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if selectedIndexPath == indexPath && self.isExpand == true{
let cell = tableView.dequeueReusableCell(withIdentifier: "LeaveBalanceExpandedCell", for: indexPath) as! LeaveBalanceExpandedCell
cell.delegate = self
return cell
}
else{
let cell = tableView.dequeueReusableCell(withIdentifier: "LeaveBalanceNormalCell", for: indexPath) as! LeaveBalanceNormalCell
return cell
}
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
// cell.animateCell(cell)
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if selectedIndexPath == indexPath{
if isExpand == true{
self.isExpand = false
}
else{
self.isExpand = true
}
}
else{
selectedIndexPath = indexPath
self.isExpand = true
}
self.tableView.reloadData()
}

Dynamic Dimension of UITableViewCell ok, but how hide them if empty?

I have a UITableView with 3 prototyped cells (ex. 1st cell: image, 2nd cell: Description, 3. Links,...).
I would like to hide them if for a cell the data from the backend is empty (Ex. if there is no image, hide the first cell). In order to do that, I have override the heightForRowAtIndexPath function in this way:
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
switch (indexPath.row) {
case 0:
if event?.photo_urls.count == 0{
return 0
}
else{
return 80.0
}
case 1:
if event?.description == ""{
return 0
}
else{
return 90.0
}
default:
return 100.0
}
}
and hidden the cell by doing
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
switch (indexPath.row) {
case 0:
let cell = tableView.dequeueReusableCellWithIdentifier("PhotoCell", forIndexPath: indexPath) as! UITableViewCell
if event?.photo_urls.count != 0 {
// ...
}
else{
cell.hidden = true
}
return cell
case 1:
let cell = tableView.dequeueReusableCellWithIdentifier("DesCell", forIndexPath: indexPath) as! UITableViewCell
if event?.description != "" {
// ...
}
else{
cell.hidden = true
}
return cell
default:
let cell = tableView.dequeueReusableCellWithIdentifier("PhotoCell", forIndexPath: indexPath) as! UITableViewCell
return cell
}
}
Until here no problem, it works properly!
Now, THE PROBLEM is that I would like to make the cells dynamics according to the cell contents (ex. description height). In order to do that, I have used
override func viewDidLoad() {
super.viewDidLoad()
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 80.0
}
and if I comment the heightForRowAtIndexPath the cells are actually dynamics but I can't hide them anymore.
Do you have any suggestion on how to be able to hide the cells if they are empty and apply the automatic dimension according to their content?
lets say you have dynamic data and you want to show it in tableview so you need to create an array of your data to display.
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet
var tableView: UITableView
var items: [String] = ["We", "Heart", "nothing" ,"" ,"imageurl", "", "xyz"]
override func viewDidLoad() {
super.viewDidLoad()
reloadTableAfterSorting()
self.tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "cell")
}
func reloadTableAfterSorting(){
for var i = 0; i < self.items.count; i++
{
if self.items[i] == ""{
self.items.removeAtIndex(2)
}
}
self.tableView.reloadData()
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.items.count;
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell:UITableViewCell = self.tableView.dequeueReusableCellWithIdentifier("cell") as UITableViewCell
cell.textLabel?.text = self.items[indexPath.row]
return cell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
println("You selected cell #\(indexPath.row)!")
}
}
For that i recommend you to sort the array before displaying it in the table view. Hiding the cell is not a good idea and its not good according to Apple recommendations. So you can do one thing except hiding the cell: remove the index from the array. In this way you can always have data to show in table and it will behave properly. So don’t try to hide the cell just pop the index from array.

Resources