I have to resize a single row of my tableView when clicked. How I can do this? Anybody could help me?
My view controller class:
class DayViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet var daysWorkPointTable: UITableView
override func viewDidLoad() {
super.viewDidLoad()
var nipName = UINib(nibName: "daysWorkPointsCell", bundle: nil)
self.daysWorkPointTable.registerNib(nipName, forCellReuseIdentifier: "daysWorkCell")
}
func tableView(tableView: UITableView!, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(tableView:UITableView!, heightForRowAtIndexPath indexPath:NSIndexPath) -> CGFloat {
return 75
}
func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
var cell = tableView.dequeueReusableCellWithIdentifier("daysWorkCell", forIndexPath: indexPath) as daysWorkPointsCell
return cell
}
func tableView(tableView: UITableView!, didSelectRowAtIndexPath indexPath: NSIndexPath!) {
}
}
First you have to keep track of the indexPath of currently selected cell in a property:
var selectedCellIndexPath: NSIndexPath?
It should be an optional, because you can have no cell selected.
Next lets declare heights for selected and unselected state (change the values to whatever you want):
let selectedCellHeight: CGFloat = 88.0
let unselectedCellHeight: CGFloat = 44.0
Now you have to implement tableView(_:, heightForRowAtIndexPath:):
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if selectedCellIndexPath == indexPath {
return selectedCellHeight
}
return unselectedCellHeight
}
Now in your tableView(_:, didSelectRowAtIndexPath:) method you have to check wether the selected row or an unselected row has been tapped:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if selectedCellIndexPath != nil && selectedCellIndexPath == indexPath {
selectedCellIndexPath = nil
} else {
selectedCellIndexPath = indexPath
}
tableView.beginUpdates()
tableView.endUpdates()
if selectedCellIndexPath != nil {
// This ensures, that the cell is fully visible once expanded
tableView.scrollToRowAtIndexPath(indexPath, atScrollPosition: .None, animated: true)
}
}
The beginUpdates() and endUpdates() calls are giving you an animated height change.
If you want to change the duration of the height change animation you can wrap the beginUpdates() and endUpdates() calls in an animation block UIView.animationWithDuration(...) and set it to whatever value you want.
You can check out this sample project which demonstrates this code in action.
Related
I'm able to expand and collapse cells but i wanna call functions (expand and collapse) inside UITableViewCell to change button title.
import UIKit
class MyTicketsTableViewController: UITableViewController {
var selectedIndexPath: NSIndexPath?
var extraHeight: CGFloat = 100
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! MyTicketsTableViewCell
return cell
}
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if(selectedIndexPath != nil && indexPath.compare(selectedIndexPath!) == NSComparisonResult.OrderedSame) {
return 230 + extraHeight
}
return 230.0
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if(selectedIndexPath == indexPath) {
selectedIndexPath = nil
} else {
selectedIndexPath = indexPath
}
tableView.beginUpdates()
tableView.endUpdates()
}
}
import UIKit
class MyTicketsTableViewCell: UITableViewCell {
#IBOutlet weak var expandButton: ExpandButton!
#IBOutlet weak var detailsHeightConstraint: NSLayoutConstraint!
var defaultHeight: CGFloat!
override func awakeFromNib() {
super.awakeFromNib()
defaultHeight = detailsHeightConstraint.constant
expandButton.button.setTitle("TAP FOR DETAILS", forState: .Normal)
detailsHeightConstraint.constant = 30
}
func expand() {
UIView.animateWithDuration(0.3, delay: 0.0, options: .CurveLinear, animations: {
self.expandButton.arrowImage.transform = CGAffineTransformMakeRotation(CGFloat(M_PI * 0.99))
self.detailsHeightConstraint.constant = self.defaultHeight
self.layoutIfNeeded()
}, completion: { finished in
self.expandButton.button.setTitle("CLOSE", forState: .Normal)
})
}
func collapse() {
UIView.animateWithDuration(0.3, delay: 0.0, options: .CurveLinear, animations: {
self.expandButton.arrowImage.transform = CGAffineTransformMakeRotation(CGFloat(M_PI * 0.0))
self.detailsHeightConstraint.constant = CGFloat(30.0)
self.layoutIfNeeded()
}, completion: { finished in
self.expandButton.button.setTitle("TAP FOR DETAILS", forState: .Normal)
})
}
}
If you want the cell to get physically bigger, then where you have your store IndexPath, in heightForRow: use:
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if selectedIndexPath == indexPath {
return 230 + extraHeight
}
return 230.0
}
Then when you want to expand one in the didSelectRow:
selectedIndexPath = indexPath
tableView.beginUpdates
tableView.endUpdates
Edit
This will make the cells animate themselves getting bigger, you dont need the extra animation blocks in the cell.
Edit 2
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if(selectedIndexPath == indexPath) {
selectedIndexPath = nil
if let cell = tableView.cellForRowAtIndexPath(indexPath) as? MyTicketsTableViewCell {
cell.collapse()
}
if let cell = tableView.cellForRowAtIndexPath(NSIndexPath(forRow:indexPath.row+1, section: indexPath.section) as? MyTicketsTableViewCell {
cell.collapse()
}
} else {
selectedIndexPath = indexPath
if let cell = tableView.cellForRowAtIndexPath(indexPath) as? MyTicketsTableViewCell {
cell.expand()
}
if let cell = tableView.cellForRowAtIndexPath(NSIndexPath(forRow:indexPath.row+1, section: indexPath.section) as? MyTicketsTableViewCell {
cell.expand()
}
}
tableView.beginUpdates()
tableView.endUpdates()
}
All you need is implement UITableView Delegate this way:
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
override func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
By default, estimatedHeight is CGRectZero, when you set some value for it, it enables autoresizing (the same situation with UICollectionView), you can do even also so:
tableView?.estimatedRowHeight = CGSizeMake(50.f, 50.f);
Then you need to setup you constraints inside your cell.
Check this post: https://www.hackingwithswift.com/read/32/2/automatically-resizing-uitableviewcells-with-dynamic-type-and-ns
Hope it helps)
In MyTicketsTableViewController class, inside cellForRowAtIndexPath datasource method add target for the button.
cell.expandButton.addTarget(self, action: "expandorcollapsed:", forControlEvents: UIControlEvents.TouchUpInside)
I tried to implement lots of the given examples on this and other pages with similar questions, but none worked for me since I had to perform some logic in my custom cell (eg. hide unneeded UILables in CustomCell.swift when the cell is collapsed).
Here is the implementation that worked for me:
Create a dictionary:
private var _expandedCells: [IndexPath:Bool] = [:]
Implement the heightForRowAt method:
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return _expandedCells[indexPath] == nil ? 70 : _expandedCells[indexPath]! ? 150 : 70
}
Implement the didSelectRowAt method:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
_expandedCells[indexPath] = _expandedCells[indexPath] == nil ? true : !_expandedCells[indexPath]!
tableView.reloadRows(at: [indexPath], with: .fade)
}
Adjust your customCell:
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
guard let cell = cell as? YourCustomTableViewCell, let isExpanded = _expandedCells[indexPath] else { return }
cell.set(expanded: isExpanded)
}
In my View Controller I have a tableView and overtime I select my cell my tableview moves a bit upside I don't know why its happening, I tried to figure out why is it happening but I got nothing. Below is my code if any body can find out whats wrong then it'll be so helpful for me.
Outlets and Variables:
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var headerView: UIView!
#IBOutlet var verticalSpaceConstraint: NSLayoutConstraint!
var lastVerticalOffset: CGFloat = 0
in ViewDidLoad:
tableView.contentInset = UIEdgeInsets(top: headerViewOriginalHeight, left: 0, bottom: 0, right: 0)
tableView.scrollIndicatorInsets = tableView.contentInset
lastVerticalOffset = tableView.contentOffset.y
...........
footerView.addSubview(footerLabel)
footerView.addSubview(footerRetryButton)
footerView.addSubview(activityIndicatorView)
self.tableView.tableFooterView = footerView
Delegates:
extension myViewController: UITableViewDelegate, UITableViewDataSource {
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return reviewsArray.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell") as! TagDetailTableViewCell
..............................
return cell
}
func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if indexPath == selectedIndexPath {
return UITableViewAutomaticDimension
} else {
return 100
}
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.beginUpdates()
if indexPath == selectedIndexPath {
selectedIndexPath = NSIndexPath()
}else {
selectedIndexPath = indexPath
}
tableView.endUpdates()
print( "tableView ORIIGN Y AFETER ", tableView.contentOffset.y)
self.tableView.separatorStyle = .None
self.tableView.separatorStyle = .SingleLine
}
}
You are remove the code of tableView Reload inSide the DidSelect method. Remove tableView.beginUpdates() and tableView.endUpdates(), Now you are only reload Single Cell of TableView.
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if indexPath == selectedIndexPath {
selectedIndexPath = NSIndexPath()
}else {
selectedIndexPath = indexPath
}
NSArray* rowsToReload = [NSArray arrayWithObjects:indexPath, nil];
[myUITableView reloadRowsAtIndexPaths:rowsToReload withRowAnimation:UITableViewRowAnimationNone];
self.tableView.separatorStyle = .None
self.tableView.separatorStyle = .SingleLine
}
Remove tableView.beginUpdates() and tableView.endUpdates() from didselectrow method. You not need to use it in this context!
Manage array to keep track of selected row and manage cellforrow accordingly and reload table from didselectrow.
or in your viewDidload try
self.automaticallyAdjustsScrollViewInsets = false
I'm able to expand and collapse cells but i wanna call functions (expand and collapse) inside UITableViewCell to change button title.
import UIKit
class MyTicketsTableViewController: UITableViewController {
var selectedIndexPath: NSIndexPath?
var extraHeight: CGFloat = 100
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! MyTicketsTableViewCell
return cell
}
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if(selectedIndexPath != nil && indexPath.compare(selectedIndexPath!) == NSComparisonResult.OrderedSame) {
return 230 + extraHeight
}
return 230.0
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if(selectedIndexPath == indexPath) {
selectedIndexPath = nil
} else {
selectedIndexPath = indexPath
}
tableView.beginUpdates()
tableView.endUpdates()
}
}
import UIKit
class MyTicketsTableViewCell: UITableViewCell {
#IBOutlet weak var expandButton: ExpandButton!
#IBOutlet weak var detailsHeightConstraint: NSLayoutConstraint!
var defaultHeight: CGFloat!
override func awakeFromNib() {
super.awakeFromNib()
defaultHeight = detailsHeightConstraint.constant
expandButton.button.setTitle("TAP FOR DETAILS", forState: .Normal)
detailsHeightConstraint.constant = 30
}
func expand() {
UIView.animateWithDuration(0.3, delay: 0.0, options: .CurveLinear, animations: {
self.expandButton.arrowImage.transform = CGAffineTransformMakeRotation(CGFloat(M_PI * 0.99))
self.detailsHeightConstraint.constant = self.defaultHeight
self.layoutIfNeeded()
}, completion: { finished in
self.expandButton.button.setTitle("CLOSE", forState: .Normal)
})
}
func collapse() {
UIView.animateWithDuration(0.3, delay: 0.0, options: .CurveLinear, animations: {
self.expandButton.arrowImage.transform = CGAffineTransformMakeRotation(CGFloat(M_PI * 0.0))
self.detailsHeightConstraint.constant = CGFloat(30.0)
self.layoutIfNeeded()
}, completion: { finished in
self.expandButton.button.setTitle("TAP FOR DETAILS", forState: .Normal)
})
}
}
If you want the cell to get physically bigger, then where you have your store IndexPath, in heightForRow: use:
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if selectedIndexPath == indexPath {
return 230 + extraHeight
}
return 230.0
}
Then when you want to expand one in the didSelectRow:
selectedIndexPath = indexPath
tableView.beginUpdates
tableView.endUpdates
Edit
This will make the cells animate themselves getting bigger, you dont need the extra animation blocks in the cell.
Edit 2
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if(selectedIndexPath == indexPath) {
selectedIndexPath = nil
if let cell = tableView.cellForRowAtIndexPath(indexPath) as? MyTicketsTableViewCell {
cell.collapse()
}
if let cell = tableView.cellForRowAtIndexPath(NSIndexPath(forRow:indexPath.row+1, section: indexPath.section) as? MyTicketsTableViewCell {
cell.collapse()
}
} else {
selectedIndexPath = indexPath
if let cell = tableView.cellForRowAtIndexPath(indexPath) as? MyTicketsTableViewCell {
cell.expand()
}
if let cell = tableView.cellForRowAtIndexPath(NSIndexPath(forRow:indexPath.row+1, section: indexPath.section) as? MyTicketsTableViewCell {
cell.expand()
}
}
tableView.beginUpdates()
tableView.endUpdates()
}
All you need is implement UITableView Delegate this way:
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
override func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
By default, estimatedHeight is CGRectZero, when you set some value for it, it enables autoresizing (the same situation with UICollectionView), you can do even also so:
tableView?.estimatedRowHeight = CGSizeMake(50.f, 50.f);
Then you need to setup you constraints inside your cell.
Check this post: https://www.hackingwithswift.com/read/32/2/automatically-resizing-uitableviewcells-with-dynamic-type-and-ns
Hope it helps)
In MyTicketsTableViewController class, inside cellForRowAtIndexPath datasource method add target for the button.
cell.expandButton.addTarget(self, action: "expandorcollapsed:", forControlEvents: UIControlEvents.TouchUpInside)
I tried to implement lots of the given examples on this and other pages with similar questions, but none worked for me since I had to perform some logic in my custom cell (eg. hide unneeded UILables in CustomCell.swift when the cell is collapsed).
Here is the implementation that worked for me:
Create a dictionary:
private var _expandedCells: [IndexPath:Bool] = [:]
Implement the heightForRowAt method:
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return _expandedCells[indexPath] == nil ? 70 : _expandedCells[indexPath]! ? 150 : 70
}
Implement the didSelectRowAt method:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
_expandedCells[indexPath] = _expandedCells[indexPath] == nil ? true : !_expandedCells[indexPath]!
tableView.reloadRows(at: [indexPath], with: .fade)
}
Adjust your customCell:
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
guard let cell = cell as? YourCustomTableViewCell, let isExpanded = _expandedCells[indexPath] else { return }
cell.set(expanded: isExpanded)
}
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 Want to add a UIButton to my UITableViewCell.My cells expands in height when it is selected.. The button must only be displayed when didSelectRow is called in the expanded area ... I have 4 cells that I want to populate with different questions, forms and buttons. Should I sub-class each cell?
This is my code so far:
let SelectedCellHeight: CGFloat = 500.0
let UnselectedCellHeight: CGFloat = 44.0
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 4
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let logItemCell = tableView.dequeueReusableCellWithIdentifier("LogCell", forIndexPath: indexPath) as! UITableViewCell
}
// Used to expand cell when selected
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!) {
if let selectedCellIndexPath = selectedCellIndexPath {
if selectedCellIndexPath == indexPath {
self.selectedCellIndexPath = nil
} else {
self.selectedCellIndexPath = indexPath
}
} else {
selectedCellIndexPath = indexPath
}
tableView.beginUpdates()
tableView.endUpdates()
}
Yes, you should make 4 prototype cells with different identifier and class and return the one you want in 'cellForRowAtIndexPath' depending on the indexPath.
You should subclass each cell that you want. And when didSelectRow is called and the cell is expanded button.hidden = false should be called