I dragged a UIView to header of UITableView of UITableViewController , this view works pretty well as a header to table, but has abnormal height , its all done inside a storyboard, I want to know how I can control its height. Whatever height I set in Storyboard it doesn't affect , it always shows same height. See the attached image the gray part is UIView as a header.
There are two types of headers in UITableView
UITableView header
UITableView Section headers
Assuming you are talking about the first case.
Static Header View Height
If your header has static height and it is placed in the sotrybaord inside the Table View, you can provide height in the size inspector.
Dynamic Header View Height
If your header height is dynamic you need to go for programmatic approach.
Create a header, with all subview with their constraints, connect it to you headerView property in the Table View Controller.
Create a method updateSizeForHeaderView that will update the header view height based on the tableview width and the content inside the header.
Call you updateSizeForHeaderView from viewWillLayoutSubviews so that on rotation of screen our header view is updated.
In viewDidLoad set you header tableView.tableHeaderView = headerView
Thats all.
class TableViewController: UITableViewController {
#IBOutlet var headerView : UIView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.tableHeaderView = headerView
}
override func viewWillLayoutSubviews() {
updateSizeForHeaderView(inTableView: tableView)
}
func updateSizeForHeaderView(inTableView tableView : UITableView) {
let size = headerView.systemLayoutSizeFittingSize(tableView.frame.size, withHorizontalFittingPriority: UILayoutPriorityRequired, verticalFittingPriority: UILayoutPriorityDefaultLow)
headerView.frame.size = size
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return 5
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell_ID", forIndexPath: indexPath)
// Configure the cell...
return cell
}
}
Expanding on #BangOperator answer, you need to reassign the header on the table view afterward, or you may encounter strange behavior... this forces the table view to a proper update
...
#property (nonatomic, assign) CGSize lastTableViewHeaderSize;
#property (nonatomic, assign) BOOL tableViewHeaderSizeChanged;
#end
- (void)viewWillLayoutSubviews {
[super viewWillLayoutSubviews];
CGSize size = [self.tableView.tableHeaderView systemLayoutSizeFittingSize:self.tableView.bounds.size withHorizontalFittingPriority:UILayoutPriorityRequired verticalFittingPriority:UILayoutPriorityDefaultLow];
if (!CGSizeEqualToSize(size, self.lastTableViewHeaderSize)) {
CGRect frame = self.tableView.tableHeaderView.frame;
frame.size = size;
self.tableView.tableHeaderView.frame = frame;
self.tableViewHeaderSizeChanged = YES;
}
}
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
if (self.tableViewHeaderSizeChanged) {
self.tableViewHeaderSizeChanged = NO;
self.tableView.tableHeaderView = self.tableView.tableHeaderView;
}
}
This might work:
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
return 30;
}
Please do following might help to you.
Via programmatically
self.automaticallyAdjustsScrollViewInsets = NO;
EDITED
self.tableView.contentInset = UIEdgeInsetsMake(-36, 0, 0, 0);
Related
I have a question about UITableView.
I want to let the tableView height according to my cells content height.
So, I use the following code successfully.
DispatchQueue.main.async {
var frame = self.tableView.frame
frame.size.height = self.tableView.contentSize.height
self.tableView.frame = frame
}
But when I have much data to show, my contents will out of screen.
And the tableView also out of screen.
Have any ideas to set it's constrain and don't make it out of screen.
I want to set 15 between the tableView bottom layout and superView bottom layout.
I use SnapKit to set autolayout.
//I want to set this one is the biggest frame size. Other contents I can scroll the tableView to show the data.
tableView.snp.makeConstraints { (make) in
make.top.equalTo(self.topLayoutGuide.snp.bottom)
make.left.equalTo(10)
make.right.equalTo(-10)
make.bottom.equalTo(-15)
}
You could create a custom UITableView :
class AutomaticHeightTableView: UITableView {
override var contentSize: CGSize {
didSet {
self.invalidateIntrinsicContentSize()
}
}
override var intrinsicContentSize: CGSize {
self.layoutIfNeeded()
return CGSize(width: UIViewNoIntrinsicMetric, height: contentSize.height + 20)
}
}
And then set your UITableView Class to AutomaticHeightTableView.
This solution is inspired from an answer found on stackoverflow.
I have solved a similar problem using the following method. You only need few lines of code.
override func viewDidLoad() {
super.viewDidLoad()
setupTableView()
}
private func setupTableView() {
tableView.estimatedRowHeight = 44.0
tableView.rowHeight = UITableViewAutomaticDimension
}
Simply set an estimated row height for the UITableView and then set the rowHeight as UITableViewAutomaticDimension.
Maybe you can limit the height of the table's frame, making sure is not longer than its superView, something like this modifying your code:
DispatchQueue.main.async {
if let superViewHeight = self.tableView.superView?.bounds.maxY {
let maxHeight = superViewHeight - self.tableView.frame.minY
var frame = self.tableView.frame
frame.size.height = min(self.tableView.contentSize.height, maxHeight)
self.tableView.frame = frame
}
}
Code Work :
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tblHeightConstraint: NSLayoutConstraint! // tableView Height Constraint
#IBOutlet weak var tblView: UITableView!
var tblMaxHeight : CGFloat = 50
override func viewDidLoad() {
super.viewDidLoad()
let navHeight = (self.navigationController?.navigationBar.frame.size.height)! + UIApplication.shared.statusBarFrame.size.height
tblMaxHeight = self.view.frame.size.height - 40 - navHeight
}
override func viewDidLayoutSubviews(){
tblHeightConstraint.constant = min(tblMaxHeight, tblView.contentSize.height)
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 24
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell")
cell?.textLabel?.text = "Row: #\(indexPath.row)"
return cell!
}
}
Constraints to tableView :
Output :
Put your UITableView inside a UIScrollView and add constraints like this:
Constraints to Table View
Create an IBOutlet of the height constraint and then override viewWillLayoutSubviews in your UIViewController
override func viewWillLayoutSubviews() {
super.updateViewConstraints()
self.tableViewHeightConstraint.constant = tableView.contentSize.height
}
Have you tried
self.tableView.invalidateIntrinsicContentSize()
https://developer.apple.com/documentation/uikit/uiview/1622457-invalidateintrinsiccontentsize
I have tried the following code which gives the correct textview frame height
func textViewDidChange(_ textView: UITextView) {
myToDoList[keyArray[sect]]![row] = textView.text
var frame = textView.frame
frame.size.height = textView.contentSize.height
textView.frame = frame
print(frame)
let indexPath = self.tableView.indexPathForView(textView)
print(inputActive,indexPath)
self.tableView.indexPathForView(inputActive)
self.tableView.rowHeight = frame.size.height
}
The answer from "Krunal" is missing a piece or two...
Start with the cell layout / constraints:
And use this code:
import UIKit
class WithTextViewCell: UITableViewCell, UITextViewDelegate {
#IBOutlet var theTextView: UITextView!
var callBack: ((UITextView) -> ())?
override func awakeFromNib() {
super.awakeFromNib()
// in case these were not set in IB
theTextView.delegate = self
theTextView.isScrollEnabled = false
}
func textViewDidChange(_ textView: UITextView) {
// tell controller the text changed
callBack?(textView)
}
}
class TableWithTextViewTableViewController: UITableViewController {
var cellData = [
"UITableViewController implements the following behaviors:",
"If a nib file is specified via the init(nibName:bundle:) method (which is declared by the superclass UIViewController), UITableViewController loads the table view archived in the nib file. Otherwise, it creates an unconfigured UITableView object with the correct dimensions and autoresize mask. You can access this view through the tableView property.",
"If a nib file containing the table view is loaded, the data source and delegate become those objects defined in the nib file (if any). If no nib file is specified or if the nib file defines no data source or delegate, UITableViewController sets the data source and the delegate of the table view to self.",
"When the table view is about to appear the first time it’s loaded, the table-view controller reloads the table view’s data. It also clears its selection (with or without animation, depending on the request) every time the table view is displayed. The UITableViewController class implements this in the superclass method viewWillAppear(_:). You can disable this behavior by changing the value in the clearsSelectionOnViewWillAppear property.",
"When the table view has appeared, the controller flashes the table view’s scroll indicators. The UITableViewController class implements this in the superclass method viewDidAppear(_:).",
"It implements the superclass method setEditing(_:animated:) so that if a user taps an Edit|Done button in the navigation bar, the controller toggles the edit mode of the table.",
"You create a custom subclass of UITableViewController for each table view that you want to manage. When you initialize the controller in init(style:), you must specify the style of the table view (plain or grouped) that the controller is to manage. Because the initially created table view is without table dimensions (that is, number of sections and number of rows per section) or content, the table view’s data source and delegate—that is, the UITableViewController object itself—must provide the table dimensions, the cell content, and any desired configurations (as usual). You may override loadView() or any other superclass method, but if you do be sure to invoke the superclass implementation of the method, usually as the first method call.",
]
override func viewDidLoad() {
super.viewDidLoad()
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 100
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return cellData.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "WithTextViewCell", for: indexPath) as! WithTextViewCell
// Configure the cell...
cell.theTextView.text = cellData[indexPath.row]
cell.callBack = {
textView in
// update data source
self.cellData[indexPath.row] = textView.text
// tell table view we're starting layout updates
tableView.beginUpdates()
// get current content offset
var scOffset = tableView.contentOffset
// get current text view height
let tvHeight = textView.frame.size.height
// telll text view to size itself
textView.sizeToFit()
// get the difference between previous height and new height (if word-wrap or newline change)
let yDiff = textView.frame.size.height - tvHeight
// adjust content offset
scOffset.y += yDiff
// update table content offset so edit caret is not covered by keyboard
tableView.contentOffset = scOffset
// tell table view to apply layout updates
tableView.endUpdates()
}
return cell
}
}
The "key" parts:
Add a "call back" closure to your cell, so we can tell the controller when the text has changed.
When the call back occurs, have the table view controller: update the dataSource with the edited text; tell the text view to resize itself; and adjust the content offset to avoid having the caret (the text insertion point) disappear behind the keyboard.
Set UITextview height according to your content (text) size using sizeToFit and enable translatesAutoresizingMaskIntoConstraints in cellForRowAtIndexPath
Try this and see
class TextViewCell: UITableViewCell {
#IBOutlet weak var textview: UITextView!
func adjustTextViewHeight(textview : UITextView) {
textview.translatesAutoresizingMaskIntoConstraints = true
textview.sizeToFit()
textview.isScrollEnabled = false
}
}
class TableController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var table: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Don't forget to set dataSource and delegate for table
table.dataSource = self
table.delegate = self
// Set automatic dimensions for row height
table.rowHeight = UITableViewAutomaticDimension
table.estimatedRowHeight = UITableViewAutomaticDimension
}
// UITableViewAutomaticDimension calculates height of label contents/text
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "textview") as! TextViewCell
cell.adjustTextViewHeight(textview: cell.textview)
return cell
}
}
Here is Storyboard Layout:
And here is result:
Its very easy to implement dynamic cell height when you design cell with interfaceBuilder directly as prototype cell or xib, where you just need to set top an bottom constraints properly and rest of the thing is done by tableViewAutoDimension.
override func viewDidLoad() {
super.viewDidLoad()
tableView.estimatedRowHeight = 50
tableView.rowHeight = UITableViewAutomaticDimension
}
In my requirement currently,I wanted to develop a Pintrest layout view. For that I have 2 tableView's on same ScrollView.
mark: I didn't use collectionView as it was very difficult to customize the flow Layout for this purpose & I do not want to include a 3rd Party framework for the same.
Check the attached screenshot -
I am populating them with 2 arrays one for even & one for odd items.
I am making these tableView's non scrollable & increasing my scrollView's contentView's height as per the tallest tableView. Both the tableView's have custom cells with dynamically increasing contents i.e a label.
in my viewDidLoad()
self.tableViewCol1.estimatedRowHeight = 296
self.tableViewCol1.rowHeight = UITableViewAutomaticDimension
self.tableViewCol1.separatorStyle = UITableViewCellSeparatorStyle.None
self.tableViewCol2.estimatedRowHeight = 296
self.tableViewCol2.rowHeight = UITableViewAutomaticDimension
self.tableViewCol2.separatorStyle = UITableViewCellSeparatorStyle.None
in my DataSource method -
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
var cell2 = MostLikedTVCscnd()//custom Cell for 2nd Table
var cell1 = MostLikedTVC()//custom Cell for 1st Table
if tableView == tableViewCol1
{
let cell = tableViewCol1.dequeueReusableCellWithIdentifier("cell1", forIndexPath: indexPath) as! MostLikedTVC
cell.imageCol1.image = imageArr1[indexPath.row] as? UIImage
cell.aboutLblCol1.text = labelArr1[indexPath.row] as? String//dynamic increasing label
cell1=cell
}
else if tableView == tableViewCol2
{
let cell = tableViewCol2.dequeueReusableCellWithIdentifier("cell2", forIndexPath: indexPath) as! MostLikedTVCscnd
cell.imageCol2.image = imageArr2[indexPath.row] as? UIImage
cell.aboutLblCol2.text = labelArr2[indexPath.row] as? String
cell2 = cell
}
//changing the height constraint of table's to make the table view non scrollable
tableView1HghtCnstrnt.constant = tableViewCol1.contentSize.height
tableView2HghtCnstrnt.constant = tableViewCol2.contentSize.height
//comparing the content size of table's to check which table is the tallest & adjust the height of the main ScrollView's content
if tableViewCol1.contentSize.height>tableViewCol2.contentSize.height
{
mainViewHghtCnstrnt.constant = tableViewCol1.contentSize.height+35//mainViewHghtCnstrnt :- mainScrollView's content height constraint & 35 is the padding
}
else if tableViewCol1.contentSize.height<tableViewCol2.contentSize.height
{
mainViewHghtCnstrnt.constant = tableViewCol2.contentSize.height+35
}
else if tableViewCol1.contentSize.height==tableViewCol2.contentSize.height
{
mainViewHghtCnstrnt.constant = tableViewCol2.contentSize.height+35
}
//returning the cell
if tableView == tableViewCol1
{
return cell1
}
else
{
return cell2
}
}
}
But my problem is that the table's are not properly calculating the size of their content's. I did some search and here an answer to a question says - contentSize will be messed up when you give estimatedRowHeight
So what options do I have? What can be done to realise the same properly?
You can use tableView's contentSize property to get the height required for the tableView.
I have done a demo to test this, and it is working as you are expecting.
My constraint are as follows:
ScrollView:
LeadingSpace to SuperView, TrailingSpace to SuperView, TopSpace to
SuperView, BottomSpace to SuperView
ContainerView (UIView inside scrollView):
LeadingSpace to SuperView, TrailingSpace to SuperView, TopSpace to
SuperView, BottomSpace to SuperView, EqualWidth to SuperView (i.e
scrollView)
ATableView
LeadingSpace to SuperView, TrailingSpace to BTableView, TopSpace to
SuperView, Width fixed points, HeightFixed points (Created outlet of
this constraint), BottomSpace to SuperView with StandardSpacing and
LowPriority (250)
BTableView
LeadingSpace to ATableView, TrailingSpace to SuperView, TopSpace to
SuperView, Width fixed points, HeightFixed points (Created outlet of
this constraint), BottomSpace to SuperView with StandardSpacing and
LowPriority (250)
And here is my full code,
class ViewController: UIViewController {
#IBOutlet weak var tableViewA: UITableView!
#IBOutlet weak var tableViewB: UITableView!
#IBOutlet weak var heightConstraintBTableView: NSLayoutConstraint!
#IBOutlet weak var heightConstraintATableView: NSLayoutConstraint!
var tableViewACellCount = 50
var tableViewBCellCound = 20
override func viewDidLoad() {
super.viewDidLoad()
//Dont set estimatedRowHeight as it is displaying empty cell at the bottom of tableView (try playing with this uncomment estimatedRowHeight)
//tableViewA.estimatedRowHeight = 44.0
tableViewA.rowHeight = UITableViewAutomaticDimension
//tableViewB.estimatedRowHeight = 44.0
tableViewB.rowHeight = UITableViewAutomaticDimension
//Try both methods
self.performSelector("adjustTableViewHeight", withObject: [], afterDelay: 0.0)
//self.adjustTableViewHeight()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
if(tableView.isEqual(tableViewA)) { //A TableView
return tableViewACellCount
} else { //B TableView
return tableViewBCellCound
}
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCellWithIdentifier("cell")
return cell!
}
func adjustTableViewHeight() {
dispatch_async(dispatch_get_main_queue()) { () -> Void in
self.tableViewA.layoutIfNeeded()
self.tableViewB.layoutIfNeeded()
}
heightConstraintATableView.constant = tableViewA.contentSize.height
heightConstraintBTableView.constant = tableViewB.contentSize.height
}
}
I would like to set the UITableView to match the height for all the contents in the table view.
This is my storyboard
The problem with this is the top and bottom ImageView is always static on the screen.
The there are suppose to be 10 items on the table view but only 7 shows up due to screen size limitation. I would like to show all 10 before user is able to see the bottom ImageView. (btw, all 3 of the views ie. both the image views and tableview is in a uiscrollview)
IDEAL
Some of the other limitations that i have to work with is that the number of items in the table view is dynamic meaning it can be in any amount of usually less than 10 that i will later retrieve from an api. And the cell height is also dynamic depending on the contents.
I have only just started with some simple code
class ExampleViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
var items: [String] = [
"Item 01", "Item 02", "Item 03", "Item 04", "Item 05",
"Item 06", "Item 07", "Item 08", "Item 09", "Item 10"]
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "cell")
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.items.count;
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell:UITableViewCell = self.tableView.dequeueReusableCellWithIdentifier("cell")! as UITableViewCell
cell.textLabel?.text = self.items[indexPath.row]
return cell
}
}
Subclass your UITableView to override the intrinsicContentSize to be its contentSize, like this:
override var intrinsicContentSize: CGSize {
return contentSize
}
Then use automatic row heights for your table, so your exampleViewController's viewDidLoad would have:
tableView.estimatedRowHeight = 44
And the UITableViewDelegate function:
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
When you receive data from your API and reload your table, just call:
tableView.invalidateIntrinsicContentSize()
This will tell your table to resize itself to the same size as its contents (because of the override), and move your bottom image as needed.
If your storyboard throws an error saying that your UIScrollView has an ambiguous height because there's no height constraint on the UITableView, select your UITableView and give it a placeholder intrinsic size in the Size Inspector.
The answers using the subclassing technique are incomplete. You should also override layoutSubviews() like this.
public class DynamicSizeTableView: UITableView
{
override public func layoutSubviews() {
super.layoutSubviews()
if bounds.size != intrinsicContentSize {
invalidateIntrinsicContentSize()
}
}
override public var intrinsicContentSize: CGSize {
return contentSize
}
}
This is what I utilize in production apps:
Swift 5, 2021
import UIKit
class DynamicTableView: UITableView {
/// Will assign automatic dimension to the rowHeight variable
/// Will asign the value of this variable to estimated row height.
var dynamicRowHeight: CGFloat = UITableView.automaticDimension {
didSet {
rowHeight = UITableView.automaticDimension
estimatedRowHeight = dynamicRowHeight
}
}
public override var intrinsicContentSize: CGSize { contentSize }
public override func layoutSubviews() {
super.layoutSubviews()
if !bounds.size.equalTo(intrinsicContentSize) {
invalidateIntrinsicContentSize()
}
}
}
You need to set an IBOutlet to the NSLayoutConstraint that sets the tableView height (first you need create the height constraint with any value, doesn't matter) and then ctrl drag it to your class file
Then in your viewWillAppear you have to calculate the tableView height and set it. Like this:
var tableViewHeight:CGFloat = 0;
for (var i = tableView(self.tableView , numberOfRowsInSection: 0) - 1; i>0; i-=1 ){
tableViewHeight = height + tableView(self.tableView, heightForRowAtIndexPath: NSIndexPath(forRow: i, inSection: 0) )
}
tableViewHeightLayout.constant = tableViewHeight
And that's pretty much it. That will give your scrollView content size and shouldn't raise any warnings.
Update Swift 4
this code working be good
self.scrollView.layoutIfNeeded()
self.view.layoutIfNeeded()
self.tableViewHeightConstraint.constant = CGFloat(self.tableView.contentSize.height)
You probably have to implement the table view intrinsic content size. Please check this answer to see if it helps.
I remember having this problem and even created a custom UITableView subclass.
#import "IntrinsicTableView.h"
#implementation IntrinsicTableView
#pragma mark - UIView
- (CGSize)intrinsicContentSize
{
return CGSizeMake(UIViewNoIntrinsicMetric, self.contentSize.height);
}
#pragma mark - UITableView
- (void)endUpdates
{
[super endUpdates];
[self invalidateIntrinsicContentSize];
}
- (void)reloadData
{
[super reloadData];
[self invalidateIntrinsicContentSize];
}
- (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation
{
[super reloadRowsAtIndexPaths:indexPaths withRowAnimation:animation];
[self invalidateIntrinsicContentSize];
}
- (void)reloadSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation
{
[super reloadSections:sections withRowAnimation:animation];
[self invalidateIntrinsicContentSize];
}
- (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation
{
[super insertRowsAtIndexPaths:indexPaths withRowAnimation:animation];
[self invalidateIntrinsicContentSize];
}
- (void)insertSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation
{
[super insertSections:sections withRowAnimation:animation];
[self invalidateIntrinsicContentSize];
}
- (void)deleteRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation
{
[super deleteRowsAtIndexPaths:indexPaths withRowAnimation:animation];
[self invalidateIntrinsicContentSize];
}
- (void)deleteSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation
{
[super deleteSections:sections withRowAnimation:animation];
[self invalidateIntrinsicContentSize];
}
#end
Update for Swift 5. Adding maxHeight so that you can specify how tall you want your tableView to be
class SelfSizingTableView: UITableView {
var maxHeight = CGFloat.infinity
override var contentSize: CGSize {
didSet {
invalidateIntrinsicContentSize()
setNeedsLayout()
}
}
override var intrinsicContentSize: CGSize {
let height = min(maxHeight, contentSize.height)
return CGSize(width: contentSize.width, height: height)
}
}
In that case, don't make your bottom cell static, make it a part of table view and insert this bottom image in last row using table view delegate method - insertRowAtIndexPath
In this type of case add your bottom imageView(red) in a table footer view.
To add footer view in UITableView you can use:
tableViewObj.tableFooterView = footerViewObj;
Try this also
in ViewDidLoad
self.table.estimatedRowHeight = 44.0 ;
self.table.rowHeight = UITableViewAutomaticDimension;
Height for row at index path
-(float)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
return UITableViewAutomaticDimension;}
Easy way here.
Step 1: Set a height constraint for the table view
Step 2: Control drag the constraint
Step 3: Before you return the count of the rows. In numberOfRowsInSection method, do
tableViewHeightConstraint.constant = tableView.rowHeight * CGFloat(someArray.count)
Of course you can edit the height anchor programmatically, the logic here is to adjust the table view height according to the cell height and cell number.
Based on solution #nikans, written in Xamarin
[Register(nameof(DynamicSizeTableView)), DesignTimeVisible(true)]
public class DynamicSizeTableView : UITableView
{
public override void LayoutSubviews()
{
base.LayoutSubviews();
if (Bounds.Size != IntrinsicContentSize)
InvalidateIntrinsicContentSize();
}
public override CGSize IntrinsicContentSize => ContentSize;
public DynamicSizeTableView(CGRect frame) : base(frame) { }
public DynamicSizeTableView(IntPtr handle) : base(handle) { }
}
Here is the simplest Solution
First Give a height to the tableView.
Create outlet of that height in view Controller. let's say tableViewHeight
Then do this in viewDidLoad or where you populate the data after calling tableView.reloadData()
var height = 0.0
for i in 0..<items.count {
let frame = tableView.rectForRow(at: IndexPath(row: i, section: 0))
height += frame.size.height
}
tableViewHeight.constant = height
This Also Works with tableViews that have dynamic cell heights
Based on solution of #rr1g0
Updated for Swift 5 in 2020, and works with TableViews with sections too.
Create height constraint for tableView and create an outlet to it. And in viewDidLayoutSubviews() use the code below:
var tableViewHeight: CGFloat = 0
for section in 0..<tableView.numberOfSections {
for row in 0..<tableView.numberOfRows(inSection: section) {
tableViewHeight += tableView(tableView, heightForRowAt: IndexPath(row: row, section: section))
}
}
tableViewHeightConstraint.constant = tableViewHeight
I have a tableview with 7 custom cells. The cells are big enough that only 5 of them fit on screen at any one time. The different cells are user-configurable in terms of content, which is how I noticed that there is something strange going on. When the table first loads, and you scroll down to view all the cells for the first time, the cell contents are all correct. What is odd, however, is that once you scroll up and down a few times such that the top cells and the bottom cells disappear off screen a couple of times, the content of the bottom cell will pick up properties of the first cell.
Note that there is no code at all in willDisplayCell, so there is no reason for the content to change just due to scrolling. Also, all of the scrolling is without registering a touch in any of the cells. I have a strong feeling this is a dequeuing problem of some sort. What do I need to do to make sure the content of the cells remains stable regardless of the amount of scrolling?
Here is the TVC code:
import Foundation
import UIKit
class adventureTVC: UITableViewController {
var focusArray: [String] = []
override func viewDidLoad() {
super.viewDidLoad()
// General Setup
self.title = selectedGenre
self.tableView.rowHeight = 98.0
self.tableView.separatorStyle = UITableViewCellSeparatorStyle.None
self.view.backgroundColor = UIColor(red: 0.8, green: 0.8, blue: 0.8, alpha: 1.0)
self.tableView.contentInset.bottom = 49
// Registering the custom cell
let nib = UINib(nibName: "StarsAcrossCell", bundle: nil)
tableView.registerNib(nib, forCellReuseIdentifier: "starsCell")
// Data used by this TVC. This should be the Genre for which ever cell was touched to get here
focusArray = bookage.focusArray()
}
override func viewWillAppear(animated: Bool) {
tableView.reloadData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func numberOfSectionsInTableView(tableView: UITableView?) -> Int {
return 1
}
override func tableView(tableView: UITableView?, numberOfRowsInSection section: Int) -> Int {
return focusArray.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: StarsAcrossCell = self.tableView.dequeueReusableCellWithIdentifier("starsCell") as! StarsAcrossCell
cell.selectionStyle = .None
cell.groupTypeLabel.text = focusArray[indexPath.row]
cell.cellBorder.backgroundColor = getCellColor(indexPath.row)
let statusArray = bookage.getReadStatusArrayForFocus(selectedCategory, genre: selectedGenre, focus: focusArray[indexPath.row])
setStarImages(statusArray, cell: cell)
return cell
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
}
}
It seems cell reuse issue. You need to implement -prepareForReuse method in your custom cell class and set all cell properties to default value.
- (void)prepareForReuse
If a UITableViewCell object is reusable—that is, it has a reuse
identifier—this method is invoked just before the object is returned
from the UITableView method dequeueReusableCellWithIdentifier:. For
performance reasons, you should only reset attributes of the cell that
are not related to content, for example, alpha, editing, and selection
state. The table view's delegate in tableView:cellForRowAtIndexPath:
should always reset all content when reusing a cell. If the cell
object does not have an associated reuse identifier, this method is
not called. If you override this method, you must be sure to invoke
the superclass implementation.
Refer here for more, https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableViewCell_Class/#//apple_ref/occ/instm/UITableViewCell/prepareForReuse
First of all create customtableviewcell (if you haven't created it)
RatingViewCell.h
#interface RatingViewCell : UITableViewCell<RatingViewDelegate>
#property (strong, nonatomic) IBOutlet UILabel *lblTitle;
#property (strong, nonatomic) RatingView *ratingView;
#end
RatingViewCell.m
#implementation RatingViewCell
#synthesize lblTitle,ratingView2;
- (void)awakeFromNib
{
// Initialization code
ratingView2 = [[RatingView alloc] initWithFrame:CGRectMake(-10,22,130,15)
selectedImageName:#"RatingStartBig"
unSelectedImage:#"RatingStartBigBlank"
minValue:0
maxValue:5
intervalValue:0.5
stepByStep:NO];
ratingView2.userInteractionEnabled = NO;
ratingView2.delegate = self;
[self.contentView addSubview:ratingView2];
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
[super setSelected:selected animated:animated];
// Configure the view for the selected state
}
Then use this customCell in tableview's CellforRow method as
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"CellID";
RatingViewCell *cell = (RatingViewCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(cell == nil){
cell = [[RatingViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier];
}
cell.lblTitle.text = #"Title"; // use array to pass values dynamically
cell.ratingView2.value = 2.0; // use array to pass values dynamically
return cell;
}