I am trying to delete a row in a tableview. When the row is deleted, the tableview should adjust, so that there is not a blank row in the tableview. This is what it looks like before any rows are deleted:
This is what it looks like when a row is about to be deleted:
This is what it looks like after a row has been deleted:
There should not be that extra blank row after the fourth row is deleted. How do I get rid of this extra row?
Here is my code currently. As you can see, the size of the tableview is adjusted dynamically. The overall size of the tableview is different based on whether there are 4 items (as shown in the first image) or whether there are 6 items. In both cases, before any deletions occur, there are no extra rows in the table (so there are only 4 rows total in the first case, and only 6 rows total in the second case). In addition, the button is supposed to be 80 pixels below the ending of the tableview. When a row is deleted, the button moves correctly, as you can see that the button has moved up from images 2 to 3. However, it still looks like there is an extra row in the tableview.
#IBOutlet var tableView: UITableView!
#IBOutlet var newButton: UIButton!
var items: [String] = ["Swift", "Is", "So", "Amazing"]
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.items.count;
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell:UITableViewCell = self.tableView.dequeueReusableCell(withIdentifier: "Cell") as! UITableViewCell
// Make sure the table view cell separator spans the whole width
cell.preservesSuperviewLayoutMargins = false
cell.separatorInset = UIEdgeInsets.zero
cell.layoutMargins = UIEdgeInsets.zero
cell.textLabel?.text = self.items[indexPath.row]
return cell
}
override func viewWillAppear(_ animated: Bool) {
// Adjust the height of the tableview
tableView.frame = CGRect(x: tableView.frame.origin.x, y: tableView.frame.origin.y, width: tableView.frame.size.width, height: tableView.contentSize.height)
// Add a border to the tableView
tableView.layer.borderWidth = 1
tableView.layer.borderColor = UIColor.black.cgColor
}
// This function is used for adjusting the height of the tableview
override func viewDidLayoutSubviews(){
tableView.frame = CGRect(x: tableView.frame.origin.x, y: tableView.frame.origin.y, width: tableView.frame.size.width, height: tableView.contentSize.height)
tableView.reloadData()
//Get the current height of the tableview
var tableViewHeight = self.tableView.contentSize.height
var tableViewEnding = 134 + tableViewHeight
var buttonPlacement = tableViewEnding + 80
// The New Button is 80 points below the ending of the tableView
newButton.frame.origin.y = buttonPlacement
print("Table View Height: \(tableViewHeight)")
}
// Allow cell deletion in tableview
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
let delete = UITableViewRowAction(style: .destructive, title: "Delete") { (action, indexPath) in
// delete item at indexPath
self.items.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)
self.viewDidLayoutSubviews()
print(self.items)
print("Number of rows: \(tableView.numberOfRows(inSection: 0))")
}
delete.backgroundColor = UIColor.blue
return [delete]
}
Just add following in view did load method
tableView.tableFooterView = UIView()
Now there will be no extra empty rows.
Next step is
Add Height constraint to tableview and take IBOutlet of it.
After any updates on tableview set constraints's constant value with tableview's contentSize.height
Note: If your cell has some heavy task to do then setting constant next to the reload data may not work properly in that case you may try viewDidLayoutSubviews methdo
Hope it is helpful
Related
I have a tableView and cells. The Cells are loaded from a xib and they have a label with automatic height. I need to narrow one cell if the user taps on it.
I have tried hiding - doesn't work
I have tried removeFromSuperView()- doesn't work
Is there any alternative?
When setting up your tableViewCell store the height anchor you want to update
var yourLabelHeightAnchor: NSLayoutConstraint?
private func setupLayout() {
yourLabelHeightAnchor = yourLabel.heightAnchor.constraint(equalToConstant: 50)
// Deactivate your height anchor as you want first the content to determine the height
yourLabelHeightAnchor?.isActive = false
}
When the user clicks on a cell, notify the tableView that the cell is going to change, and activate the height anchor of your cell.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.dequeueReusableCell(withIdentifier: "YourTableViewCellIdentifier") as? YourCell
self.tableView.beginUpdates()
cell?.yourLabelHeightAnchor?.isActive = true
self.tableView.endUpdates()
}
Did you try to do something like this:
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
var result: CGFloat
if (indexPath.row==0) {
result = 50 }
else {result = 130}
return result
}
This is just an example where height is changed for the first row. I tested on my application and it gave result like this.
I have setup a tableview with dynamic height cells aka UITableView.automaticDimension using Autolayout. This works fine. Now what I am trying to achieve is to change the height of cell & animate it. The issue is that when I change cell height & animate it, the animation is weirdly jumping. The jump only occurs if I scroll down a bit & then expand/collapse cells.
I have a simple table view cell. It has a label & an empty UIView with fixed height constraint. When I want to collapse/expand the cell, I simply change the constant of that height constraint to 0 or 300.
I have tried many collapsable tableview examples off the internet. All of them have this issue. One exception is https://github.com/Ramotion/folding-cell, but that uses fixed heights for cells.
I have tried quite a few options to animate the cell height change.
1-> On didSelectRow, I change the height constraint & call tableview beginUpdate & endUpdates. Doesn't solve the jump issue.
2-> Change my model & call tableView.reloadRows. Doesn't solve the jump issue.
This is screenshot of my tableview cell setup.
https://drive.google.com/open?id=12nba6cwRszxRlaSA-IhrX3X_vLZ4AWxy
A link to video of this issue:
https://drive.google.com/open?id=19Xmc0PMXT0EuHTJeeGHm4M5aPoChAtf3
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
tableView.delegate = self
tableView.estimatedRowHeight = 50
tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedSectionHeaderHeight = 0
tableView.estimatedSectionFooterHeight = 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! OuterTableViewCell
let height: CGFloat = isCellExpanded[indexPath.row] ? 300 : 0
cell.labelText.text = "Cell Number: \(indexPath.row + 1)"
cell.buttonExpansionToggle.setImage(UIImage(named: isCellExpanded[indexPath.row] ? "arrow-down" : "arrow-right"),
for: .normal)
cell.viewContainerHeight.constant = height
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
isCellExpanded[indexPath.row] = !isCellExpanded[indexPath.row]
tableView.reloadRows(at: [indexPath], with: .automatic)
}
Another form of didSelectRow:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
guard let cell = tableView.cellForRow(at: indexPath) as? OuterTableViewCell else { return }
isCellExpanded[indexPath.row] = !isCellExpanded[indexPath.row]
let height: CGFloat = self.isCellExpanded[indexPath.row] ? 300 : 0
cell.viewContainerHeight.constant = height
cell.layoutIfNeeded()
UIView.animate(withDuration: 0.3, delay: 0, options: .curveEaseOut, animations: {
tableView.beginUpdates()
tableView.endUpdates()
// fix https://github.com/Ramotion/folding-cell/issues/169
if cell.frame.maxY > tableView.frame.maxY {
tableView.scrollToRow(at: indexPath, at: UITableView.ScrollPosition.bottom, animated: true)
}
}, completion: nil)
}
I have also tried to call beginUpdates() & endUpdates() outside animation block, yet the issue persists.
I expect the animation to be smooth. Hope someone can help. If someone can setup a simple demo project on github that would be awesome.
Demo project link: https://gitlab.com/FahadMasoodP/nested-tableviews
Help in any form is appreciated. Thanks.
My solution is adding dynamic estimate row height. I think it is bug of UIKit. iOS 13 issue will be not occur.
First, You need add property estimateRowHeightStorage to store estimate height by indexpath
var estimateRowHeightStorage: [IndexPath:CGFloat] = [:]
Second, You need store current height of cell for use later
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
estimateRowHeightStorage[indexPath] = cell.frame.size.height
}
Final, you use estimateRowHeightStorage to set estimate row height.
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
if estimateRowHeightStorage[indexPath] != nil {
return estimateRowHeightStorage[indexPath]!
}
return 50
}
Run and feel.
I did found new solution in your case. If you hardfix height when expand. Only need change some thing.
Replace all code above with estimatedHeightForRowAt function
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
let height: CGFloat = isCellExpanded[indexPath.row] ? 373 : 73
return height
}
I am creating a IOS app in swift and want to add spacing between cells like this
I would like to give space of each table view cell same like my attach image.
How I can do that? and Right now all cells are coming without any space.
swift3
you can try this in your class of tableView cell:
class cell: UITableViewCell{
override var frame: CGRect {
get {
return super.frame
}
set (newFrame) {
var frame = newFrame
frame.origin.y += 4
frame.size.height -= 2 * 5
super.frame = frame
}
}
}
From Storyboard, your view hierarchy should be like this. View CellContent (as highlighted) will contain all the components.
Give margin to View CellContent of 10px from top, bottom, leading & trailing from its superview.
Now, select the tblCell and change the background color.
Now run your project, make sure delegate and datasource are properly binded.
OUTPUT
NOTE: I just added 1 UILabel in View CellContent for dummy purpose.
Update: UIEdgeInsetsInsetRect method is replaced now you can do it like this
contentView.frame = contentView.frame.inset(by: margins)
Swift 4 answer:
in your custom cell class add this function
override func layoutSubviews() {
super.layoutSubviews()
//set the values for top,left,bottom,right margins
let margins = UIEdgeInsets(top: 0, left: 0, bottom: 10, right: 0)
contentView.frame = UIEdgeInsetsInsetRect(contentView.frame, margins)
}
You can change values as per your need
***** Note *****
calling super function
super.layoutSubviews()
is very important otherwise you will get into strange issues
If you are using UITableViewCell to achieve this kind of layout, there is no provision to provide spacing between UITableViewCells.
Here are the options you can choose:
Create a custom UIView within UITableViewCell with clear background, so that it appears like the spacing between cells.
You need to set the background as clear of: cell, content view.
You can use UICollectionView instead of UITableView. It is much more flexible and you can design it the way you want.
Let me know if you want any more details regarding this.
One simple way is collection view instead of table view and give cell spacing to collection view and use
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAtIndexPath indexPath: IndexPath) -> CGSize {
let widthSize = collectionView.frame.size.width / 1
return CGSize(width: widthSize-2, height: widthSize+20)
}
And if you want tableview only then add background view as container view and set background color white and cell background color clear color set backround view of cell leading, trilling, bottom to 10
backgroundView.layer.cornerRadius = 2.0
backgroundView.layer.masksToBounds = false
backgroundView.layer.shadowColor = UIColor.black.withAlphaComponent(0.2).cgColor
Please try it. It is working fine for me.
You can use section instead of row.
You return array count in numberOfSectionsInTableView method and set 1 in numberOfRowsInSection delegate method
Use [array objectAtIndex:indexPath.section] in cellForRowAtIndexPath method.
Set the heightForHeaderInSection as 40 or according to your requirement.
Thanks,Hope it will helps to you
- Statically Set UITableViewCell Spacing - Swift 4 - Not Fully Tested.
Set your tableView Row height to whatever value you prefer.
override func viewDidLoad() {
super.viewDidLoad()
tableView.estimatedRowHeight = <Your preferred cell size>
tableView.rowHeight = UITableViewAutomaticDimension
// make sure to set your TableView delegates
tableView.dataSource = self
tableView.delegate = self
}
extension YourClass : UITexFieldDelegate, UITextFieldDataSource {
//Now set your cells.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell : UITableViewCell = tableView.dequeueReusableCell(withIdentifier: "yourCell", for: indexPath) as! UITableViewCell
//to help see the spacing.
cell.backgroundColor = .red
cell.textLabel?.text = "Cell"
return cell
}
//display 3 cells
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 3
}
//now lets insert a headerView to create the spacing we want. (This will also work for viewForHeaderInSection)
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
//you can create your own custom view here
let view = UIView()
view.frame = CGRect(x: 0, y: 0, width: tableView.frame.width, height: 44) //size of a standard tableViewCell
//this will hide the headerView and match the tableView's background color.
view.backgroundColor = UIColor.clear
return view
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 44
}
}
I have created a UITableView with multiple sections consisting two cells with dynamic cell height. Added a button on second cell. On click of the button increasing the second cell height by changing the constraint height. Action method connected to the button is :
func increaseHeightOnClickOfButton(sender: UIButton) {
let indexPath = IndexPath(item: 1, section: sender.tag)
let cell = myTableView.cellForRow(at: indexPath) as! customCell
cell.viewHeightConstraint.constant = 100
self.myTableView.reloadRows(at: [indexPath], with: .automatic)
}
Issue: On clicking the button, cell height of second section increases rather than the first one.
I have also tried giving fix value of section like this :
let indexPath = IndexPath(item: 1, section: 0)
but it's not working.
It works only when I try this code:
func increaseHeightOnClickOfButton(sender: UIButton) {
let indexPath = IndexPath(item: 1, section: sender.tag)
let cell = myTableView.cellForRow(at: indexPath) as! customCell
cell.viewHeightConstraint.constant = 100
self.myTableView.reloadData()
}
But I don't want to reload the whole table view. Can anyone help me what I am doing wrong or is there any other approach to achieve it ?
Edit :
I did one more change, I removed :
self.myTableView.reloadRows(at: [indexPath], with: .automatic)
After clicking the button when I start scrolling the tableview, coming back to the same cell increases it's height. So I guess there is some issue with reloadRows.
You can use the following code to reload cell's height without reloading the whole data:
tableView.beginUpdates()
tableView.endUpdates()
Edit:
Modified first post to include a DataSource to track each row's height constraint constant.
Assuming your cell looks something like this:
you have added a UIView to the cell (orange view in my example)
added a label and button to the UIView
set leading, trailing, top and bottom constraints for the view
added a Height constraint to the view
connected IBOutlets and an IBAction for the button tap inside the cell class
set your table class for UITableViewAutomaticDimension
Then this should be a working example. Each time you tap a button, the Height constraint in that cell will be increased by 20-pts. No need to call reload anything...
class TypicalCell: UITableViewCell {
#IBOutlet weak var theLabel: UILabel!
#IBOutlet weak var viewHeightConstraint: NSLayoutConstraint!
var btnAction: (() -> ())?
#IBAction func didTap(_ sender: Any) {
btnAction?()
}
}
class TypicalTableViewController: UITableViewController {
// 2-D array of Row Heights
// 4 sections, with 4, 2, 6 and 3 rows
// all initialized to 40.0
var rowHeights: [[CGFloat]] = [
[40.0, 40.0, 40.0, 40.0],
[40.0, 40.0],
[40.0, 40.0, 40.0, 40.0, 40.0, 40.0],
[40.0, 40.0, 40.0]
]
override func viewDidLoad() {
super.viewDidLoad()
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 40.0
}
override func numberOfSections(in tableView: UITableView) -> Int {
return rowHeights.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return rowHeights[section].count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Typical", for: indexPath) as! TypicalCell
// Configure the cell...
cell.theLabel.text = "\(indexPath)"
// cells are reused, so set the cell's Height constraint every time
cell.viewHeightConstraint.constant = rowHeights[indexPath.section][indexPath.row]
// "call back" closure
cell.btnAction = {
// increment the value in our DataSource
self.rowHeights[indexPath.section][indexPath.row] += 20
// tell tableView it's being updated
tableView.beginUpdates()
// set the cell's Height constraint to the incremented value
cell.viewHeightConstraint.constant = self.rowHeights[indexPath.section][indexPath.row]
// tell tableView we're done updating - this will trigger an auto-layout pass
tableView.endUpdates()
}
return cell
}
}
Try reloading the Section:
self.tableView.reloadSections(IndexSet(integer: sender.tag), with: .automatic)
In Core Data I have a one-to-many relationship between contact and location entities. In my tableview I want to display the contact information as header in a Section and the associated locations for each contact inside the cell as multiple rows.
I am able to display the header (with contact information) and the cell ( with locations) dynamically. I set the cell as Custom with Row Height 200. I also set the rowHeight in my program as below:
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
//tableView.estimatedRowHeight = 200.0
//tableView.rowHeight = UITableViewAutomaticDimension
//return 200.0
return UITableViewAutomaticDimension
}
override func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
Here is my cellForRowAt function:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellView:UIView = UIView()
let cell:EndPtTableViewCell = tableView.dequeueReusableCell(withIdentifier: "endPtCell", for: indexPath) as! EndPtTableViewCell
// Configure the cell...
let endpt:EndPtListTableViewController.EndPt = allEndpts[indexPath.row]
var yVal:Double = 10.0
for locationDict in locationDictArray {
let location:String = locationDict["locationName"]!
let zip:String = locationDict["zip"]!
let locationAndZipLabel = UILabel()
locationAndZipLabel.text = location + ", " + zip
locationAndZipLabel.font = UIFont.preferredFont(forTextStyle: UIFontTextStyle.body)
locationAndZipLabel.frame = CGRect(x: 30.0, y:yVal, width: 350, height: 15)
locationAndZipLabel.tag = 5
yVal = yVal + 25.0
cellView.addSubview(locationAndZipLabel)
}
cell.contentView.addSubview(cellView)
return cell
}
The cell displays the location and Zip values for each location in different rows, but does not expand the cell row height dynamically. As you can see the second row is partially displayed.
How can I dynamically expand the cell Row Height based on number of rows inserted?