I have a tableView on mainStoryboard with two custom cells. I would like to reduce the spacing between two cells.
I was trying to find the answer but could not find any. I have image and code added below.
class HomeViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, MFMailComposeViewControllerDelegate {
#IBOutlet var tblStoryList: UITableView!
var array = PLIST.shared.mainArray
override func viewDidLoad() {
super.viewDidLoad()
//spacing between header and cell
self.tblStoryList.contentInset = UIEdgeInsetsMake(-20, 0, 0, 0)
//delete separator of UITableView
tblStoryList.separatorStyle = .none
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.array.count + 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "HeaderCell", for: indexPath) as! HeaderCell
cell.headerTitle.text = "First Stage"
return cell
}
let cell = tableView.dequeueReusableCell(withIdentifier: "StoryTableviewCell", for: indexPath) as! StoryTableviewCell
//making plist file
let dict = self.array[indexPath.row - 1]
let title = dict["title"] as! String
let imageName = dict["image"] as! String
let temp = dict["phrases"] as! [String:Any]
let arr = temp["array"] as! [[String:Any]]
let detail = "progress \(arr.count)/\(arr.count)"
//property to plist file
cell.imgIcon.image = UIImage.init(named: imageName)
cell.lblTitle.text = title
cell.lblSubtitle.text = detail
cell.selectionStyle = UITableViewCellSelectionStyle.none
return cell
}
header cell
import UIKit
class HeaderCell: UITableViewCell {
#IBOutlet weak var headerTitle: UILabel!
override func layoutSubviews() {
super.layoutSubviews()
headerTitle.layer.cornerRadius = 25
headerTitle.layer.masksToBounds = true
}
}
I think you have set some static height to the cells in heightForRowAt indexPath method, set it to UITableViewAutomaticDimension. and estimatedHeightForRowAt indexPath set it to static value for better performance.
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return 40 //expected minimum height of the cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
And not to forget that you need to set "correct constraints" to get the desired result by this method. Constraints are required to let know the table cell about its height.
Try this..
self.tableView.rowHeight = 0; // in viewdidload
[self.tableView setSeparatorStyle:UITableViewCellSeparatorStyleNone]; // in viewdidload
-(CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section{
return 0.01f;
}
-(CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section{
return <your header height>;
}
-(UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section{
return [[UIView alloc] initWithFrame:CGRectZero];
}
-(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section{
return <your header view>;
}
Also have table seprator as none.
You need to return height for each cell accordingly . for example
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if indexPath.row == 0 {
// return height for first row, i.e
return 40
}
// return height for other cells. i.e
return 90
}
You can set table each and every cell height dynamic with Autolayout table view will take cell hight from Autolayout
for that you dont need write this 2 method
Remove this method
tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) ->
CGFloat
{
}
Write this 2 line in ViewDidload method
self.tblView?.rowHeight = UITableViewAutomaticDimension
self.tblView?.estimatedRowHeight = 60
And set your imageview bottom space with Autolayout that you want to keep for example 10 and set relation of bottom layout is greater then or equal and set priority is 252 instead of 1000
You will get clear idea about this in my screen shot check that as well how to do this Do this in all custom cell this way you can set height dynamic no need to calculate height of each and every cell and not need to return
Related
I have a UITableViewController with a custom UITableViewCell. Each cell has 2 labels. When the cell is selected it expands to a fixed value, I do it via tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat . I also have set the rowHeight = UITableViewAutomaticDimension , as some cells have to display multiple lines of text.
What I want to achieve is when a cell needs to be expanded I want to add 50 points to its current height. So here's the question, how can I get current height of the cell, when the rowHeight = UITableViewAutomaticDimension is set?
Here's my code for the fixed height for the selected state:
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if selectedIndexPath == indexPath {
return selectedHeight
}else{
return tableView.estimatedRowHeight
}
}
EDIT: I also need to change it after that, by adding some variable to it.
Building on HamzaLH's answer you might do something like this...
import UIKit
class TableViewController: UITableViewController {
var selectedRow: Int = 999 {
didSet {
tableView.beginUpdates()
tableView.endUpdates()
}
}
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, heightForRowAt indexPath: IndexPath) -> CGFloat {
if indexPath.row == selectedRow { //assign the selected row when touched
let thisCell = tableView.cellForRow(at: indexPath)
if let thisHeight = thisCell?.bounds.height {
return thisHeight + 50
}
}
return 60 //return a default value in case the cell height is not available
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
selectedRow = indexPath.row
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier", for: indexPath)
cell.detailTextLabel?.text = "test"
return cell
}
}
I'm animating the expansion of the height with a didSet when a the selectedRow is altered.
Also, don't forget you may still need to connect your dataSource and delegate by dragging the outlets in Interface Builder to your View Controller in the storyboard. After that you still need to add this to ViewDidLoad in your ViewController's swift file.
tableView.delegate = self
tableView.dataSource = self
I also have an Outlet declared for the tableView like below, and connected in Interface Builder storyboard.
#IBOutlet weak var tableView: UITableView!
You need to get the cell using cellForRow and then get the height of the cell.
let cell = tableView.cellForRow(at: indexPath)
let height = cell.bounds.height
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()
I have a tableView in which every cell contains another tableView with dynamic row height.
My question is how can I set the row height of the first table to fit the height of the inner tableView?
I'm using this code but it does not work as it should.
var heights: [CGFloat] = []
var loades: [Bool] = []
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return self.heights[indexPath.row]
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "eventCell", for: indexPath) as! EventCell
cell.date.text = ev.date
cell.delegate = self
cell.event = ev
self.heights[indexPath.row] = cell.tv.frame.height
if self.loades[indexPath.row] == false{
self.loades[indexPath.row] = true
tableView.reloadRows(at: [indexPath], with: .none)
}
return cell}
I'd try getting the nested tableview's contentSize in your heightForRow method.
I hope you can access to your tableview.contentSize.height, the contentSize gets the value of all tableview content if your tableview is dynamic, the content of this height you can divide in the number of rows what you have. This is just a simply solution, some like this:
let size = tableView.contentSize.height
let cellSize = size / models.count
where models is the array of objects printed in tableviewcell inside tableview
Recommend:
Put a height constraint inside your cell to your tableView, and try to refresh with de sizeCell sum
cell.tableViewInsideCell.reloadData()
cell.heightConstraint.constant = tableView.contentSize.height
and set
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
I'm using TableView to implement it on ViewController. The problem that I'm facing is the number of cells. Currently the cells supposed to return 2 rows but it only show one row.
What I want to achieve
Storyboard - You can ignore the rest of the components except the tableview
import UIKit
class ViewController: UIViewController {
#IBOutlet var locationTableView: UITableView!
let locationArray = ["Current Location", "Where to"]
let picArray = ["currentPic.png", "whereTo.png"]
override func viewDidLoad() {
super.viewDidLoad()
locationTableView.delegate = self
}
}
extension ViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return locationArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "GetLocation", for: indexPath)
let location = locationArray[indexPath.row]
let pic = picArray[indexPath.row]
print(location)
if let locationCell = cell as? GetLocationTableViewCell {
locationCell.locationTitle.text = location
locationCell.iconImage.image = pic
}
return cell
}
}
GetLocationTableViewCell.swift
import UIKit
class GetLocationTableViewCell: UITableViewCell {
#IBOutlet var iconImage: UIImageView!
#IBOutlet var locationTitle: UILabel!
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
The outcome is really weird
On Simulator
On my iphone
The number of rows differ on both platforms. What did I do wrong? Is it because of constraints?
Here is problem locationArray[indexPath.section] , picArray[indexPath.section] your section is return 0 so you get two times display "Current Location" you need to use indexPath.row
Try this
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "GetLocation", for: indexPath)
let location = locationArray[indexPath.row]
let pic = picArray[indexPath.row]
print(location)
if let locationCell = cell as? GetLocationTableViewCell {
locationCell.locationTitle.text = location
locationCell.iconImage.image = pic
}
return cell
}
Change your table delegate method like this:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "GetLocation", for: indexPath)
let location = locationArray[indexPath.section]
let pic = picArray[indexPath.row]//replace this line
print(location)
if let locationCell = cell as? GetLocationTableViewCell {
locationCell.locationTitle.text = location
locationCell.iconImage.image = pic
}
return cell
}
You can try like Bellow. You can write array like
let locationArray = [
["textName":"Current Location", "imageName":"currentPic"],
["textName":"Where to", "imageName":"whereTo"]
]
Table view protocol method look like
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return locationArray.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier", for: indexPath) CustomCell
cell.textLabel?.text = locationArray[indexPath.row]["textName"]
cell.imageView?.image = UIImage(named: locationArray[indexPath.row]["imageName"]!)
return cell
}
Replace method Following thing in cellRowAtPath
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "GetLocation", for: indexPath)
let location = locationArray[indexPath.row]
let pic = picArray[indexPath.row]
print(location)
if let locationCell = cell as? GetLocationTableViewCell {
locationCell.locationTitle.text = location
locationCell.iconImage.image = pic
}
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 102 //Give cell height
}
As Per your problem i suggest you to manage constraints that your are applying to tableview and Cell. Here i have provided instruction to follow to get solution. Please Manage to follow same instruction on your project Hope this will solve your problem.
1. Manage tableview constraints
a. Pin your tableview to superview from top,left right
b. Set height constraints of tableview.
c. Make outlet of height constraints for tableview so that we can alter value on runtime.
2. Manage Cell constraints
a. Pin your cell contents to superview from top,left, right,bottom
3. manage tableview constraints on runtime
a. set height constraints of tableview view to content size of table view from cell for tow at index path.
self.tableHeight.constant = self.locationTableView.contentSize.height
Full controller Code.
// Controller.swift
// Copyright © 2017 dip. All rights reserved.
//
import UIKit
class ViewController1: UIViewController {
//1:
//Outlet of tableview Height constraints
#IBOutlet weak var tableHeight: NSLayoutConstraint!
#IBOutlet var locationTableView: UITableView!
let locationArray = ["Current Location", "Where to"]
let picArray = ["currentPic.png", "whereTo.png"]
override func viewDidLoad() {
super.viewDidLoad()
locationTableView.delegate = self
}
}
extension ViewController1: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return locationArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "GetLocation", for: indexPath)
let location = locationArray[indexPath.row]
let pic = picArray[indexPath.row]
print(location)
if let locationCell = cell as? GetLocationTableViewCell {
locationCell.locationTitle.text = location
locationCell.iconImage.image = pic
}
//2. set tableivew view height to content Height
self.tableHeight.constant = self.locationTableView.contentSize.height
return cell
}
}
Out Put
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
}
}