I have a tableView that's populated with data, where all my cells can currently expand / collapse when tapping on the row.
However, the last cell in my UITableView is a different kind of data, and it should not be expandable when tapped on.
I have it set up so only one cell can be expanded at a time, so if another cell is currently expanded and they tap on the last cell in the UITableView that cell should still collapse, but the last one shouldn't expand.
The way I'm checking if it's the last cell that shouldn't be expandable is checking one of it's labels: if cell.tickerLabel.text == " CASH"
Which I can verify is correct and that if statement runs for the last cell.
I've tried a lot of different implementations of inserting that if statement into my code where I expand and collapse my cells. But none of what I tried has worked as intended.
Here's how my code for expanding / collapsing cells looks like:
My didSelectRowAtIndexPath function
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let cell = tableView.dequeueReusableCellWithIdentifier("com.Stocks.portfolioCell", forIndexPath: indexPath) as! portfolioCell
if let selectedCellIndexPath = selectedCellIndexPath {
if selectedCellIndexPath == indexPath {
self.selectedCellIndexPath = nil
currentCollapsing = true
tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .None)
} else {
lastCollapsing = true
self.selectedCellIndexPath = indexPath
tableView.reloadRowsAtIndexPaths([lastIndexPath], withRowAnimation: .None)
tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .None)
self.lastIndexPath = indexPath
}
} else {
selectedCellIndexPath = indexPath
self.lastIndexPath = indexPath
tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.None)
}
tableView.beginUpdates()
tableView.endUpdates()
}
My heightForRowAtIndexPath function
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
let SelectedCellHeight: CGFloat = 210
let UnselectedCellHeight: CGFloat = 50
if let selectedCellIndexPath = selectedCellIndexPath {
if selectedCellIndexPath == indexPath {
return SelectedCellHeight
}
}
return UnselectedCellHeight
}
And lastly the part of my cellForRowAtIndePath function that is relevant to this question:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("com.Stocks.portfolioCell", forIndexPath: indexPath) as! portfolioCell
cell.heightSeperator.hidden = true
cell.stockNameView.hidden = true
cell.purchasePriceView.hidden = true
cell.lastPriceView.hidden = true
cell.daysHeldView.hidden = true
cell.endSeperator.hidden = true
if indexPath.row % 2 == 0 {
cell.backgroundColor = UIColor.formulaWhiteColor()
} else {
cell.backgroundColor = UIColor.whiteColor()
}
if selectedCellIndexPath == indexPath {
cell.tickerLabel.textColor = UIColor.formulaBlueColor()
cell.heightSeperator.hidden = false
cell.stockNameView.hidden = false
cell.purchasePriceView.hidden = false
cell.lastPriceView.hidden = false
cell.daysHeldView.hidden = false
cell.endSeperator.hidden = false
tableHeight.constant = tableHeight.constant+210
self.scrollView.contentSize = CGSize(width:self.view.bounds.width, height: self.contentView.frame.height)
self.view.setNeedsDisplay()
}
if lastCollapsing == true || currentCollapsing == true {
lastCollapsing = false
currentCollapsing = false
tableHeight.constant = tableHeight.constant-210
self.scrollView.contentSize = CGSize(width:self.view.bounds.width, height: self.contentView.frame.height)
self.view.setNeedsDisplay()
}
}
Can anyone help pointing me in the right direction for how I can make it so my last cell isn't expandable while still allowing any other open cell to collapse when tapping on it?
Any help would be greatly appreciated!
You should check if the indexPath is the last one inside heightForRow.
Since you can select your last row, the logic will identify that the last row is selected and when calculating it's height, it will calculate as if it were selected (which it was). so you should avoid the last row from being expanded by handling it inside your heightForRow delegate method.
Update
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
let SelectedCellHeight: CGFloat = 210
let UnselectedCellHeight: CGFloat = 50
//Add this here, where numberOfRows is self explanatory
//So replace it with the actually variable.
if indexPath.row == numberOfRows - 1 {
return UnselectedCellHeight;
}
if let selectedCellIndexPath = selectedCellIndexPath {
if selectedCellIndexPath == indexPath {
return SelectedCellHeight
}
}
return UnselectedCellHeight
}
Related
I followed this app to build a table with custom cells: https://github.com/awseeley/Custom-Table-Cell
I am trying to expand a cell and show different content when the cell is clicked.
Here is what I have in my view controller:
var cellTapped:Bool = true
var currentRow = 0;
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
//CODE TO BE RUN ON CELL TOUCH
let selectedRowIndex = indexPath
currentRow = selectedRowIndex.row
let cell:TblCell = self.tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! TblCell
cell.image.hidden = true
tableView.beginUpdates()
tableView.endUpdates()
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if indexPath.row == currentRow {
if cellTapped == false {
cellTapped = true
return 141
} else {
cellTapped = false
return 70
}
}
return 70
}
The cell expands when it is clicked and shrinks when it is clicked again. But the image does not hide. How do I get the image to hide? Is there a better way to show another custom .xib file when the cell is selected and have an expand/collapse effect?
You should add this implement below to your cellForRowAtIndexPath
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if indexPath.row == currentRow {
if cellTapped == false {
cell.image.hidden = false
} else {
cell.image.hidden = true
}
}
return cell
}
And didSelectRowAtIndexPath you should remove wrong code:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
//CODE TO BE RUN ON CELL TOUCH
let selectedRowIndex = indexPath
currentRow = selectedRowIndex.row
tableView.reloadData()
}
If you want use want use tableView.beginUpdate() for better animation, you can reference to my demo:
Demo hide image on cell
Hope this help!
I'm not familiarized with it, but you can look at Stack View. I think it's can help you doing what you want:
http://www.raywenderlich.com/114552/uistackview-tutorial-introducing-stack-views
Any idea on how I can manage to do the use case I have below. Although I manage to do this using UITableView, I still get some issue every time I scroll the table view.
Scenario: The table view needs to adjust the height of the cell dynamically based on which item is selected. Each item would have different options inside of it. When Options is selected it should be able to show the options under the item and automatically adjust the height.
Solution: I subclassed UITableView and UITableViewCell. Every time Options is selected/tapped I will call beginUpdate and endUpdate which is working perfectly.
Problem: Every time the user scrolls down the options are not showing correctly.
Everytime when you scrolls up or down the uitableview will call the cellForRowAtIndexPath and the tableView will reload.
So in this case you need to track down the cell which is selected before scrolling up or down and after scrolling you just need to replace the states (e.g. heights) with that desired cell.
In my case I am using some veriable to keep track of the hegiht of the cell.
Here is my code below : -
import UIKit
class WaitingListClass: UIViewController,UITableViewDataSource, UITableViewDelegate {
var selectedCellIndexPath: NSIndexPath!
let SelectedCellHeight: CGFloat = 88.0
let UnselectedCellHeight: CGFloat = 44.0
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
let appdelegateObjForThisClass = UIApplication.sharedApplication().delegate as! AppDelegate
tableView.delegate = self
tableView.dataSource = self
self.tableView.rowHeight = UITableViewAutomaticDimension
self.tableView.backgroundColor = UIColor.clearColor()
self.tableView.separatorColor = tableViewSeperatorColor
self.tableView.tableFooterView = UIView(frame: CGRectZero)
self.tableView.contentInset = UIEdgeInsetsZero
}
internal func numberOfSectionsInTableView(tableView: UITableView) -> Int
{
return 1
}
internal func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
if noDataForTable == true{
return 1
}
return appdelegateObjForThisClass.nameDataForTable.count
}
internal func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{
let cellIdentifier = "WaitingCell"
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! CellClassWaitingList
cell.nameDisplayLbl.text = appdelegateObjForThisClass.nameDataForTable[indexPath.row]["name"]!
cell.backgroundColor = tableViewCellColor
cell.nameDisplayLbl.textColor = UIColor(red: 191/255, green: 193/255, blue: 192/255, alpha: 1)
cell.idDisplayLbl.textColor = UIColor(red: 48/255, green: 143/255, blue: 94/255, alpha: 1)
cell.idDisplayLbl.text = appdelegateObjForThisClass.indexForTable[indexPath.row]
cell.userInteractionEnabled = true
cell.clipsToBounds = true
cell.selectionStyle = UITableViewCellSelectionStyle.None
let heightOfCell : CGFloat = self.tableView.delegate!.tableView!(self.tableView, heightForRowAtIndexPath: indexPath)
if heightOfCell == self.UnselectedCellHeight{
cell.stackOfBtnInCell.hidden = true
}else if heightOfCell == self.SelectedCellHeight{
cell.stackOfBtnInCell.hidden = false
}
return cell
}
internal func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let cell = tableView.cellForRowAtIndexPath(indexPath) as! CellClassWaitingList
if let selectedCellIndexPath = selectedCellIndexPath {
if selectedCellIndexPath == indexPath {
self.selectedCellIndexPath = nil
UIView.animateWithDuration(0.5, animations: {
cell.stackOfBtnInCell.hidden = true
self.view.layoutIfNeeded()
})
} else {
UIView.animateWithDuration(0.5, animations: {
cell.stackOfBtnInCell.hidden = false
self.view.layoutIfNeeded()
})
self.selectedCellIndexPath = indexPath
}
} else {
UIView.animateWithDuration(0.5, animations: {
cell.stackOfBtnInCell.hidden = false
self.view.layoutIfNeeded()
})
selectedCellIndexPath = indexPath
}
tableView.beginUpdates()
tableView.endUpdates()
}
func tableView(tableView: UITableView, didDeselectRowAtIndexPath indexPath: NSIndexPath) {
let cell = tableView.cellForRowAtIndexPath(indexPath) as! CellClassWaitingList
cell.stackOfBtnInCell.hidden = true
}
// MARK: - Cell Separator Setting to Full Size
internal func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
if(self.tableView.respondsToSelector(Selector("setSeparatorInset:"))){
self.tableView.separatorInset = UIEdgeInsetsZero
}
if(self.tableView.respondsToSelector(Selector("setLayoutMargins:"))){
self.tableView.layoutMargins = UIEdgeInsetsZero
}
if(cell.respondsToSelector(Selector("setLayoutMargins:"))){
cell.layoutMargins = UIEdgeInsetsZero
}
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if let selectedCellIndexPath = selectedCellIndexPath {
if selectedCellIndexPath == indexPath {
return SelectedCellHeight
}
}
return UnselectedCellHeight
}
}
Now what I am doing here is taking a variable called selectedCellIndexPath which will take the selected cell's indexpath first. Next I am taking two more variable called SelectedCellHeight and UnselectedCellHeight which generally stores some height value to interchange each other for the cell in some cases like -
When tableview will be scrolled.
When cell will be selected and deselected.
For rest of the implementation try to look over the tableView datasource and delegate methods.
Thank you,
Hope this helped.
you need to keep a list of your cell states and in your table view data source cellAtIndexPath: and heightForCellAtIndexPath: you should respectively format your cell and specify the desired cell height.
You could create an NSMutableSet and add/remove NSIndexPath objects to it, or use an NSMutableDictionary and check whether a certain key corresponding to a cell's index has a value.
what do you mean "the options are not showing correctly"?
Also, how I would do it:
Create another UITableViewCell in the UITableView via the Storyboard. That TableView cell has the custom height you need.
Create a new UITableViewCell class file.
Assign the new class to the cell in the storyboard.
Call TableView.reloadData() when Options is pressed. And turn a Bool to true for the selected cell.
In the cellForRowAtIndexPath() use the custom UITableViewCell class when the Bool is on true.
What I'm trying to achieve is, when a cell in UITableview is tapped, it will expand and display custom cell1, other cells will remain custom cell2.
What I achieved so far is expand the cell, but the custom cell will not change.
After initial investigation, I thought it's the currentRow value wasn't changed. But then I realized if it isn't changed, the row won't expand. Thank you for your help.
Here are the code:
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var currentRow = 0
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if indexPath.row == currentRow {
let cell:selectedWeatherViewCell = self.weatherCityTable.dequeueReusableCellWithIdentifier("selectedWeatherCell") as! selectedWeatherViewCell
cell.selectedCityLabels.text = city[indexPath.row]
cell.selectedTempLabels.text = tempertureF[indexPath.row]
cell.backgroundColor = UIColor.redColor()
return cell
} else {
let cell:cityWeatherViewCell = self.weatherCityTable.dequeueReusableCellWithIdentifier("cityWeatherViewCell") as! cityWeatherViewCell
cell.cityLabels.text = city[indexPath.row]
cell.tempLabels.text = tempertureF[indexPath.row]
cell.backgroundColor = UIColor.orangeColor()
return cell
}
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let selectedRowIndex = indexPath
currentRow = selectedRowIndex.row
tableView.beginUpdates()
tableView.endUpdates()
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if indexPath.row == currentRow {
return 260
} else {
return 100
}
}
}
You need to reload the affected row(s) in order for the cell type to change -
var currentRow:Int?
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
var reloadRows=[NSIndexPath]()
if self.currentRow != nil && indexPath.row != self.currentRow! {
reloadRows.append(NSIndexPath(forRow: self.currentRow!, inSection: indexPath.section))
}
self.currentRow=indexPath.row
reloadRows.append(NSIndexPath(forRow: self.currentRow!, inSection: indexPath.section))
tableView.reloadRowsAtIndexPaths(reloadRows, withRowAnimation: UITableViewRowAnimation.Automatic)
tableView.deselectRowAtIndexPath(indexPath, animated: true)
}
I am trying to implement a feature where if a user clicks on one of the items in my UICollectionView (that is embedded within a UITableViewCell) it causes another section to appear in the UITableView (below the UICollectionView) with information about that item. I'm having problems with the height of the section that is to appear when clicked. If I set it to 0 initially in heightForRowAtIndexPath there doesn't seem to be a way to alter the cells height later on. I tried giving the cell an initial height then hiding it with cell.hidden but that still leaves the section visible. Maybe there is an alternative way to do this, but after a lot of googling i'm coming up short.
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if indexPath.section == 3 {
return 90
}
else if indexPath.section == 1 {
return 185
}
else if indexPath.section == 2 {
return 155
}
else if indexPath.section == 4 {
return 100
}
return UITableViewAutomaticDimension
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
...
}
else if indexPath.section == 3 {
let cell = tableView.dequeueReusableCellWithIdentifier("weeklyCell", forIndexPath: indexPath) as! WxTableViewCell
cell.selectionStyle = UITableViewCellSelectionStyle.None
cell.forecastCollectionView.delegate = self
cell.forecastCollectionView.dataSource = self
cell.forecastCollectionView.reloadData()
cell.forecastCollectionView.tag = indexPath.row
cell.forecastCollectionView.showsHorizontalScrollIndicator = false
return cell
}
else if indexPath.section == 4 {
let cell = tableView.dequeueReusableCellWithIdentifier("dailyInformation", forIndexPath: indexPath) as! WxTableViewCell
cell.hidden = true
cell.contentView.hidden = true
return cell
}
...
}
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
let index = NSIndexPath(forRow: 0, inSection: 3)
let myindex = NSIndexPath(forRow: 0, inSection: 4)
var myTable = self.tableView
var lastCell = myTable.cellForRowAtIndexPath(index)
var dailyCell = myTable.cellForRowAtIndexPath(myindex)
var myView = UIView(frame: CGRectMake(20, 0, lastCell!.frame.width, lastCell!.frame.height))
dailyCell.frame.size = myView.frame.size
}
Never use indexPath numbers directly to address cells and sections in a switch/case. It's hard to handle when you want add/remove a row/section in the middle. Instead create an enum which have all sections and use an array that contains sections to be shown. You can have cell or section properties like cell identifiers or height in that enum.
enum FileDetailsCell: Printable {
case FileName, Location, Size
var description: String {
switch self {
case .FileName: return "noname"
case .Location: return"Location"
case .Size: return "Size"
}
}
var defaultValue: String {
switch self {
case .Location: return "/"
case .Size: return "Unknown"
default: return ""
}
}
var cellIdentifier: String {
switch self {
case .FileName: return "fileNameCell"
default: return "detailCell"
}
}
}
Here I use description for Cell title. defaultValue for initial value of cell and cellIdentifier is obvious for which purpose.
Then define an array which contains active cells:
var cellsArray: [FileDetailsCell] = [.FileName, .Size]
you can modify this array when you want change visible cells. Now, Location cell is hidden but you can make it visible later only by appending ".Location" to this array and reload tableView.
For tableview delegate:
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1;
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return cellsArray.count;
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cellInfo = cellsArray[indexPath.row]
let cell = tableView.dequeueReusableCellWithIdentifier(cellInfo.cellIdentifier, forIndexPath: indexPath) as! UITableViewCell
switch cellInfo {
case .FileName:
cell.textLabel?.text = item.fileName;
case .Location:
cell.textLabel?.text = cellInfo.description
cell.detailTextLabel?.attributedText = item.locationFormatted ?? cellInfo.defaultValue;
case .Size:
cell.textLabel?.text = cellInfo.description
cell.detailTextLabel!.text = item.size ?? cellInfo.defaultValue;
}
return cell
}
In this example I assumed sections are only one to avoid confusion. You can get similar approach for sections.
I'm having an issue with making an image appear when a row is selected and then making the image disappear when another row is selected. If I touch a selected row it does not get deselected – this is OK as that is the behaviour that I want. I only want the currently selected row to be deselected when I touch another row.
I am writing in Swift.
I am using Kai Engelhardt's solution to expand the selected row, as answered here.
This UIImage should appear/disappear: cellContent.ringImage.image = UIImage(named: "ring.png")
I'm guessing that my logic is wrong in the selectedCellIndexPath part below.
This is my code:
In my TVC:
class MenuViewController: UIViewController{
var selectedCellIndexPath: NSIndexPath?
let SelectedCellHeight: CGFloat = 222.0
let UnselectedCellHeight: CGFloat = 64.0
let menuItems = [
("1","test 1"),
("2","test 2"),
("3","test 3"),
("4","test 4"),
("5","test 5")]
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if tableView == menuTable {
return menuItems.count
} else {
return 0}
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! MenuTableViewCell
if tableView == menuTable {
let (my, section) = menuItems[indexPath.row]
cell.myLabel.text = my
cell.sectionLabel.text = section
cell.selected = true
}
}
func tableView(tableView: UITableView!, heightForRowAtIndexPath indexPath: NSIndexPath!) -> CGFloat {
if let selectedCellIndexPath = selectedCellIndexPath {
if selectedCellIndexPath == indexPath {
return SelectedCellHeight
}
}
return UnselectedCellHeight
}
func tableView(tableView: UITableView!, didSelectRowAtIndexPath indexPath: NSIndexPath!) {
var cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! MenuTableViewCell
var cellContent = tableView.cellForRowAtIndexPath(indexPath) as! MenuTableViewCell
let cellLabel = cellContent.sectionLabel.text
if let selectedCellIndexPath = selectedCellIndexPath {
if selectedCellIndexPath != indexPath {
self.selectedCellIndexPath = indexPath
cellContent.ringImage.image = UIImage(named: "ring.png")
} else {
self.selectedCellIndexPath != indexPath
cellContent.ringImage.hidden = true
tableView.deselectRowAtIndexPath(indexPath, animated: true)
// cellContent.testbutton.removeFromSuperView
}
} else {
selectedCellIndexPath = indexPath
cellContent.ringImage.hidden = true
tableView.deselectRowAtIndexPath(indexPath, animated: true)
}
tableView.beginUpdates()
tableView.endUpdates()
}
Your help is greatly appreciated!
Thanks
Mikee
It seems that self.selectedCellIndexPath != indexPath does not do the deselect mark effect you want. You may try to use tableView.indexPathsForSelectedRows() to get the currently selected indexPath, compare it with the indexPath in the argument, and then complete your logic without assigning self.selectedCellIndexPath.
(Edited)
As I find that you also need the varialbe self.selectedCellIndexPath to identify the height, you could try to convert it to a counter variable which counts the selected time of currently selected row. If it is odd, it is selected, while when it's even that you know you would deselect it and reset the counter varialbe to zero.
func tableView(tableView: UITableView!, heightForRowAtIndexPath indexPath: NSIndexPath!) -> CGFloat {
if (indexPath == tableView.indexPathsForSelectedRows()[0] && self.counter % 2 == 1) {
return SelectedCellHeight
}
return UnselectedCellHeight
}