When scrolling off the screen with my custom cell in an expanded state the buttons are being hidden which should not be the case. Beneath the labels, 2 buttons are not appearing when scrolling up again. I'm guessing something needs to happen after the cell has been dequeued. Any help would be much appreciated.
I have a custom cell where initially the height for a row is 80, upon clicking on the cell it expands to 120. By default I have the buttons hidden like so:
#IBOutlet weak var followButton: UIButton! {
didSet {
followButton.isHidden = true
}
}
#IBOutlet weak var blockButton: UIButton! {
didSet {
blockButton.isHidden = true
}
}
I have an var expandedIndexSet : IndexSet = [] which tracks which cell has been expanded.
The method below updates indexSet accordingly.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
tableView.beginUpdates()
let cell = tableView.cellForRow(at: indexPath) as! StackOverflowTableViewCell
if(expandedIndexSet.contains(indexPath.row)){
expandedIndexSet.remove(indexPath.row)
} else {
expandedIndexSet.insert(indexPath.row)
}
cell.blockButton.isHidden = !cell.blockButton.isHidden
cell.followButton.isHidden = !cell.followButton.isHidden
tableView.endUpdates()
}
The height gets adjusted like so:
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if expandedIndexSet.contains(indexPath.row) {
return 140
}
else {
return 80
}
}
The following snippet might be helpful, please try.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = ....
cell.followButton.isHidden = !expandedIndexSet.contains(indexPath.row)
cell.blockButton.isHidden = cell.followButton.isHidden
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
// Update View Model
if(expandedIndexSet.contains(indexPath.row)){
expandedIndexSet.remove(indexPath.row)
} else {
expandedIndexSet.insert(indexPath.row)
}
// Table View Animation
tableView.beginUpdates()
tableView.reloadRows(at: indexPaths, with: .automatic)
tableView.endUpdates()
}
Related
I am trying to make a FAQ view controller using table view, I need little bit fix in the UI. here is the display of my FAQ VC right now
(please ignore the red line)
as we can see, basically there are 2 row height, 80 & 160. if the row is tapped (selected) the row height will expand from 80 (yellow line) to 160 (purple line).
I want to make the row height under the last question is still 80 not 160. I have tried but I can't set the row below the last question. here is my code I use
class FAQVC: UIViewController {
#IBOutlet weak var retryButton: DesignableButton!
#IBOutlet weak var tableView: UITableView!
var FAQs = [FAQ]()
var selectedIndexs: [IndexPath: Bool] = [:]
override func viewDidLoad() {
super.viewDidLoad()
getFAQData()
}
}
extension FAQVC : UITableViewDelegate, UITableViewDataSource {
// MARK: - Table View Delegate and Datasource
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return FAQs.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "FAQCell") as! FAQCell
cell.FAQData = FAQs[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if self.cellIsSelected(indexPath: indexPath) {
return 160
} else {
return 80
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let isSelected = !self.cellIsSelected(indexPath: indexPath)
selectedIndexs[indexPath] = isSelected
tableView.beginUpdates()
tableView.endUpdates()
}
func cellIsSelected(indexPath: IndexPath) -> Bool {
if let number = selectedIndexs[indexPath] {
return number
} else {
return false
}
}
}
Please put this code in your viewDidLoad() method of your controller.
YOUR_TABLE_VIEW.tableFooterView = UIView(frame: .zero)
this will remove extra separators.
Quickest way to do this would be to add an extra empty cell at the end with row height 80
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return FAQs.count + 1
}
Also, make sure you make a change to cellForRowAt method to accommodate this :
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row < FAQs.count {
let cell = tableView.dequeueReusableCell(withIdentifier: "FAQCell") as! FAQCell
cell.FAQData = FAQs[indexPath.row]
return cell
}
return UITableViewCell()
}
EDIT :
I just read that you don't really require separators after the last cell. In that case look here https://spin.atomicobject.com/2017/01/02/remove-extra-separator-lines-uitableview/
I have a view controller in which i have used table view controller in it. In my cell there is a button on which when user click the cell size should come to 550 and when click again it should come back to its original height. I have tried bit code after searching for it but it isn't working is their any solution that can work for me?. My code is bit this,
var indexOfCellToExpand: Int!
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if indexPath.row == indexOfCellToExpand {
return 170 + expandedLabel.frame.height - 38
}
return 170
}
Use Auto layout for Expand-collapse Cell,
Attaching Demo for that case
link:https://www.dropbox.com/s/ieltq0honml35l8/TAbleDemo.zip?dl=0.
set cell button height constraint and update constraint on Value on select- deselect event and just reload data
in main viewcontroller code
self.tblView.estimatedRowHeight = 100
self.tblView.rowHeight = UITableViewAutomaticDimension
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 3
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TblCell", for: indexPath) as! TblCell
cell.btnExpandCollepse.addTarget(self, action: #selector(ViewController.expandCollapse(sender:)), for: .touchUpInside)
return cell
}
#objc func expandCollapse(sender:UIButton) {
self.tblView.reloadData()
}
Code in Cell Class
#IBOutlet var btnExpandCollepse: UIButton!
#IBOutlet var constraintBtnHeight: NSLayoutConstraint!
#IBAction func onExpandCollepse(_ sender: UIButton) {
sender.isSelected = !sender.isSelected
if !sender.isSelected{
self.constraintBtnHeight.constant = 50
}else{
self.constraintBtnHeight.constant = 500
}
}
for constraint check below image
http://prntscr.com/hur8ym
Updated Demo for custom Content height of Lable with Autoresizing Cell
https://www.dropbox.com/s/o742kflg5yeofb8/TAbleDemo%202.zip?dl=0
https://www.dropbox.com/s/o742kflg5yeofb8/TAbleDemo%202.zip?dl=0
Swift 4.x
fileprivate var expandedIndexSet = Set<IndexPath>()
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if expandedIndexSet.contains(indexPath) {
let cell = tableView.dequeueReusableCell(withIdentifier: "CELL_EXPANDED", for: indexPath) as! CellExpanded
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "CELL_COLLAPSED", for: indexPath) as! CellCollapsed
return cell
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: false)
if expandedIndexSet.contains(indexPath) {
expandedIndexSet.remove(indexPath)
} else {
expandedIndexSet.insert(indexPath)
}
tableView.reloadRows(at: [indexPath], with: .fade)
}
I have TableView, where users can click and expand each cell (UI: click). This is the code for that (also I created .xib file to layout this cell):
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UISearchResultsUpdating {
var selectedIndex: IndexPath?
var isExpended = false
func didExpandCell() {
self.isExpended = !isExpended
self.tableView.reloadRows(at: [selectedIndex!], with: .automatic)
}
#IBOutlet weak var tableView: UITableView!
...
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "wordCell", for: indexPath) as! CellController
if searchController.isActive {
cell.word.text = filtered[indexPath.row].word
cell.translation.text = filtered[indexPath.row].translation
cell.date.text = filtered[indexPath.row].fullDate
cell.exOne.text = filtered[indexPath.row].exOne
cell.exTwo.text = filtered[indexPath.row].exTwo
} else {
cell.word.text = words[indexPath.row].word
cell.translation.text = words[indexPath.row].translation
cell.date.text = words[indexPath.row].fullDate
cell.exOne.text = words[indexPath.row].exOne
cell.exTwo.text = words[indexPath.row].exTwo
}
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.selectedIndex = indexPath
self.didExpandCell()
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if isExpended && self.selectedIndex == indexPath {
return 180
}
return 70
}
}
Also, all Cells are swipe-able. This is the code:
func tableView(_ tableView: UITableView, editActionsForRowAt: IndexPath) -> [UITableViewRowAction]? {
let edit = UITableViewRowAction(style: .normal, title: "Edit") { action, index in
print("Edit")
}
edit.backgroundColor = .blue
let delete = UITableViewRowAction(style: .normal, title: "Delete") { action, index in
print("Delete")
}
delete.backgroundColor = .red
return [delete, edit]
}
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
The problem:
When I swipe Cell, it always expends in a half, this is my UI after swiping: click. Is it possible not to expand Cell if I swipe them?
Excuse me, but the problem was that I haven't set constraints for expanded version of the cell. That's why this labels were moving with swipe menu. Now it works.
Thank you for all your replies!
I have an imageView inside my tableViewCell and i would like to have its image changed on selection. This is the code I have for it:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let myCell = tableView.cellForRow(at: indexPath) as! TableCell
myCell.resourceIcons.image = UIImage(named: "RubiusResources2")
tableView.deselectRow(at: indexPath, animated: true)
}
The code works, but some of the other rows in a different section further down the tableView also seem change.
EDIT:
Using the comments bellow I came to the following solution:
I first created a 2D bool array to the amount of sections and rows my table had and set them all to false.
var resourceBool = Array(repeating: Array(repeating:false, count:4), count:12)
I then created an if statement to check if the array at indexPath was false or true. This would be where the states of the image would change.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let myCell = tableView.dequeueReusableCell(withIdentifier: "cellId", for: indexPath) as! TableCell
if (global.resourceBool[indexPath.section][indexPath.row] == false) {
myCell.resourceIcons.image = global.systemResourceImages[0]
} else if (global.resourceBool[indexPath.section][indexPath.row] == true) {
myCell.resourceIcons.image = global.systemResourceImages[1]
}
return myCell
}
Then, in the didSelectRow function I change the array at indexPath to true and reload the tableView data.
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
global.resourceBool[indexPath.section][indexPath.row] = true
tableView.reloadData()
tableView.deselectRow(at: indexPath, animated: true)
}
From my understanding, the states of an object must always be in the cellForRow.
One of the solution would be that you need to maintain a seperate list of rows you selected, compare them in cellForRowAt method.
Code would look something like this.
var selectedArray : [IndexPath] = [IndexPath]()
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let myCell = tableView.cellForRow(at: indexPath) as! TableCell
myCell.resourceIcons.image = UIImage(named: "RubiusResources2")
tableView.deselectRow(at: indexPath, animated: true)
if(!selectedArray.contains(indexPath))
{
selectedArray.append(indexPath)
}
else
{
// remove from array here if required
}
}
and then in cellForRowAt, write this code to set proper images
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
.
.
.
if(selectedArray.contains(indexPath))
{
// use selected image
}
else
{
// use normal image
}
.
.
.
}
My table view can expand and collapse cells when they are pressed, but the content that appears when the cell expands loads before the animation is finished.
What I am left with is this:
What I would like it to look like is this example. This content appears as if it were behind a curtain and the cell expansion animation just reveals it.
Here is the code that controls the table view:
class HistoryViewController: UIViewController, UITableViewDataSource, UITableViewDelegate{
var expandedIndexPath: NSIndexPath? // Index path of the cell that is currently expanded
let collapsedHeight: CGFloat = 44.0 // Constant to set the default collapsed height
var ticketHistoryService = TicketHistoryService() // Service to gather info about Ticket History CoreData
var tickets = [Ticket]()
#IBOutlet var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Remove any appended table view cells
tableView.tableFooterView = UIView()
self.tickets = self.ticketHistoryService.fetchData() // Load inital data
}
// MARK: - Table View Methods
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.tickets.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCellWithIdentifier("cell") as! HistoryTableViewCell
let ticket = self.tickets[indexPath.row]
cell.titleLabel!.text = ticket.ticketNumber
return cell
}
func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete {
self.ticketHistoryService.removeObject(indexPath.row)
self.tickets = self.ticketHistoryService.fetchData()
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
}
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
self.tableView.beginUpdates()
let cell = self.tableView.cellForRowAtIndexPath(indexPath) as! HistoryTableViewCell
if indexPath.isEqual(self.expandedIndexPath){ // If currently selected cell was just previously selected
self.expandedIndexPath = nil
cell.commentLabel.hidden = true
}
else {
self.expandedIndexPath = indexPath
cell.commentLabel.hidden = false
}
self.tableView.endUpdates()
}
func tableView(tableView: UITableView, willDeselectRowAtIndexPath indexPath: NSIndexPath) -> NSIndexPath? {
let cell = self.tableView.cellForRowAtIndexPath(indexPath) as! HistoryTableViewCell
cell.commentLabel.hidden = true
return indexPath
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if indexPath.isEqual(self.expandedIndexPath) {
return UITableViewAutomaticDimension
}
return collapsedHeight
}
}
One approach is to have your cell clip subview content that would expand outside of itself:
let cell = self.tableView.dequeueReusableCellWithIdentifier("cell") as! HistoryTableViewCell
cell.clipsToBounds = true