UITableViewCell contentView has autoResizingMask constraint 'UIView-Encapsulated-Layout-Height' clashing with autolayout for self resizing - ios

I'm trying to create a table view whose cells expand when the cell is selected and remain expanded until selected again.
I'm trying to get the height of the cells to be determined by their content. I already know of setting a tableView's rowHeight and estimagedRowHeight to get self sizing working, so that's not the issue.
ViewController.swift:
import UIKit
class ViewController: UIViewController {
lazy var tableView: UITableView = {
let tv = UITableView(frame: .zero, style: .plain)
tv.register(ExpandingCell.self, forCellReuseIdentifier: ExpandingCell.reuseIdentifier)
tv.estimatedRowHeight = 85.0
tv.rowHeight = UITableViewAutomaticDimension
return tv
}()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.view.addSubview(tableView)
tableView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
tableView.topAnchor.constraint(equalTo: self.view.topAnchor),
tableView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),
tableView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
tableView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor)])
tableView.dataSource = self
tableView.delegate = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 50
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: ExpandingCell.reuseIdentifier, for: indexPath) as! ExpandingCell
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return 50.0
}
}
extension ViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
}
}
ExpandingCell.swift
import UIKit
class ExpandingCell: UITableViewCell {
static let reuseIdentifier = "dafasdfadfagr"
let minimumDisplayedView = UIView()
let extraContentView = UIView()
var expandingConstraint: NSLayoutConstraint?
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
private func commonInit() {
self.setupViews()
self.contentView.clipsToBounds = true
}
private func setupViews() {
self.addSubviews()
self.customizeSubviews()
self.constrainSubviews()
}
private func addSubviews() {
self.contentView.addSubview(minimumDisplayedView)
self.contentView.addSubview(extraContentView)
}
private func customizeSubviews() {
self.customizeMinimumDisplayedView()
self.customizeExtraContentView()
}
private func constrainSubviews() {
self.constrainMinimumDisplayedView()
self.constrainExtraContentView()
}
// MARK: - MinimumDisplayView
private func customizeMinimumDisplayedView() {
minimumDisplayedView.backgroundColor = .blue
}
private func constrainMinimumDisplayedView() {
minimumDisplayedView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
minimumDisplayedView.heightAnchor.constraint(equalToConstant: 50),
minimumDisplayedView.topAnchor.constraint(equalTo: self.contentView.topAnchor),
minimumDisplayedView.leadingAnchor.constraint(equalTo: self.contentView.leadingAnchor),
minimumDisplayedView.trailingAnchor.constraint(equalTo: self.contentView.trailingAnchor)
])
expandingConstraint = minimumDisplayedView.bottomAnchor.constraint(equalTo: self.contentView.bottomAnchor)
expandingConstraint?.isActive = true
}
// MARK: - ExtraContentView
private func customizeExtraContentView() {
extraContentView.backgroundColor = .red
}
private func constrainExtraContentView() {
extraContentView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
extraContentView.heightAnchor.constraint(equalToConstant: 200),
extraContentView.topAnchor.constraint(equalTo: minimumDisplayedView.bottomAnchor),
extraContentView.leadingAnchor.constraint(equalTo: self.contentView.leadingAnchor),
extraContentView.trailingAnchor.constraint(equalTo: self.contentView.trailingAnchor)
])
}
// MARK: - Animate Expansion
// override func setSelected(_ selected: Bool, animated: Bool) {
// super.setSelected(selected, animated: animated)
// print("selected: \(selected)")
// resize(selected)
// }
private func resize(_ expand: Bool) {
expandingConstraint?.isActive = false
if expand {
expandingConstraint = extraContentView.bottomAnchor.constraint(equalTo: self.contentView.bottomAnchor)
expandingConstraint?.isActive = true
} else {
expandingConstraint = minimumDisplayedView.bottomAnchor.constraint(equalTo: self.contentView.bottomAnchor)
expandingConstraint?.isActive = true
}
UIView.animate(withDuration: 0.3) {
self.layoutIfNeeded()
}
}
}
I have 2 views being added to the contentView and pinning the edges of the contentView to only the first view when initializing the cell. The first view is blue and short, while the second is red and tall.
I would expect the tableView to look all blue initially, but it instead looks like this:
I suspect it has something to do with the way cells are laid out in a view lifecycle, so I also came across this answer: reload tableview after first load as a workaround
When I run the code I get these constraint errors
2018-05-05 18:00:15.108634-0400 TestExpandingCells[1022:13932317] [LayoutConstraints] Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list is one you don't want.
Try this:
(1) look at each constraint and try to figure out which you don't expect;
(2) find the code that added the unwanted constraint or constraints and fix it.
(
"<NSLayoutConstraint:0x6000000961c0 UIView:0x7fe583700df0.height == 50 (active)>",
"<NSLayoutConstraint:0x600000096580 V:|-(0)-[UIView:0x7fe583700df0] (active, names: '|':UITableViewCellContentView:0x7fe583704850 )>",
"<NSLayoutConstraint:0x600000096760 UIView:0x7fe583700df0.bottom == UITableViewCellContentView:0x7fe583704850.bottom (active)>",
"<NSLayoutConstraint:0x600000096bc0 'UIView-Encapsulated-Layout-Height' UITableViewCellContentView:0x7fe583704850.height == 50 (active)>"
)
The last constraint on that list, 'UIView-Encapsulated-Layout-Height', seems to come from some autoresizingmask-to-constraint thing UIKit creates for laying out cell.
So my question is, how can I remove this constraint to allow AutoLayout to completely handle the making of my cells, without a workaround.
I would also prefer to have it all contained in the ExpansionCell class, rather than have it in the tableview's delegate methods.
Bonus points if you also find a way to trigger the expansion of the cell using AutoLayout.

First to get rid of the constraints's conflict set the priority of the bottom constraint of any view connected to contentView to 999 , second to trigger expanding hook the height constraint and change it , Also in your model declare any array of bool values to determine whether a cell is expanded or not to restore cell state after scroll and
in cellForRow
cell.viewHeighcon.constant = isExpanded ? 200 : 50

You can do 2 things:
Lower the priority of any of vertical constraints to less than required.
verticalAnchor.priority = .defaultHigh
tableView should have rowHeight and estimatedRowHeight:
tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = xx.xx (any value)

Related

UITableViewCell incorrect dynamic height calculation with SwiftUI view inside on iOS 15

I am trying to use SwiftUI view inside UITableViewCell that is working fine until iOS 15. On iOS 15 when I scroll table view and once initial visible cells go off screen and new cells being reused those are having extra height added.
Following is the source code.
import UIKit
import SwiftUI
class ViewController: UIViewController {
#IBOutlet weak var tableView: UITableView?
override func viewDidLoad() {
super.viewDidLoad()
tableView?.register(TableViewCell.self, forCellReuseIdentifier: "cell")
tableView?.rowHeight = UITableView.automaticDimension
tableView?.estimatedRowHeight = 100
tableView?.separatorColor = .white
tableView?.dataSource = self
}
}
extension ViewController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
20
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as? TableViewCell
return cell ?? UITableViewCell()
}
}
class TableViewCell: UITableViewCell {
var stackView: UIStackView = {
let stack = UIStackView(frame: .zero)
stack.axis = .vertical
return stack
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupView()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setupView()
}
func setupView() {
contentView.clipsToBounds = true
contentView.addSubview(stackView)
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true
stackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true
stackView.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
stackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
let hosting = UIHostingController(rootView: TestView())
stackView.addArrangedSubview(hosting.view)
hosting.view.backgroundColor = .red
}
}
struct TestView: View {
var body: some View {
Rectangle()
.fill(Color.green)
.frame(height: 100)
}
}
Any help will be highly appreciated. Thanks

Dynamic section header height on runtime

I have UITableView in view controller with a section header and in the header, I have one UITextView with scroll disabled and pinned all UITextView edges to its super view.
Here is the code for automatic height change
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
guard let view = tableView.dequeueReusableHeaderFooterView(withIdentifier: "CreatePostHeaderView") as? CreatePostHeaderView else {
return nil
}
return view
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return UITableView.automaticDimension
}
And also set the estimated Height with this code
tableView.estimatedSectionHeaderHeight = 75
But on runtime when the text of UITextView exceeds height of 75, it doesn't change after that regardless of UITextView content. So, Do I need to add anything to make sure the table section header height changed according to UITextView content? Am I missing anything here?
When performing some action that changes the height of a cell (including header / footer cells), you have to inform the table view that the height has changed.
This is commonly done with either:
tableView.beginUpdates()
tableView.endUpdates()
or:
tableView.performBatchUpdates(_:completion:)
In this case, you want to call this when the text in your text view changes - easily done with a "callback" closure.
Here is an example of using a UITextView in a reusable UITableViewHeaderFooterView.
This will apply to loading a complex view from a XIB, but since this view is simple (only contains a UITextView), we'll do it all from code. This example uses 3 sections, each with 12 rows (default table view cells).
First, the table view controller class - no #IBOutlet or #IBAction connections, so just create a new UITableViewController and set its custom class to MyTestSectionHeaderTableViewController:
class MyTestSectionHeaderTableViewController: UITableViewController {
var myHeaderData: [String] = [
"Section 0",
"Section 1",
"Section 2",
]
override func viewDidLoad() {
super.viewDidLoad()
tableView.rowHeight = 50
tableView.keyboardDismissMode = .onDrag
tableView.sectionHeaderHeight = UITableView.automaticDimension
tableView.estimatedSectionHeaderHeight = 75
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "defCell")
tableView.register(MySectionHeaderView.self, forHeaderFooterViewReuseIdentifier: MySectionHeaderView.reuseIdentifier)
}
override func numberOfSections(in tableView: UITableView) -> Int {
return myHeaderData.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 12
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let c = tableView.dequeueReusableCell(withIdentifier: "defCell", for: indexPath)
c.textLabel?.text = "\(indexPath)"
return c
}
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let v = tableView.dequeueReusableHeaderFooterView(withIdentifier: MySectionHeaderView.reuseIdentifier) as! MySectionHeaderView
v.myTextView.text = myHeaderData[section]
v.textChangedCallback = { txt in
self.myHeaderData[section] = txt
tableView.performBatchUpdates(nil, completion: nil)
}
return v
}
}
and this is the UITableViewHeaderFooterView class. Note that it needs to conform to UITextViewDelegate so we can tell the controller the text has changed (so it can update the height when needed), and we pass back the newly edited text to update our data source:
class MySectionHeaderView: UITableViewHeaderFooterView, UITextViewDelegate {
static let reuseIdentifier: String = String(describing: self)
var myTextView: UITextView = {
let v = UITextView()
v.isScrollEnabled = false
return v
}()
var textChangedCallback: ((String) -> ())?
override init(reuseIdentifier: String?) {
super.init(reuseIdentifier: reuseIdentifier)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
func commonInit() -> Void {
contentView.addSubview(myTextView)
myTextView.translatesAutoresizingMaskIntoConstraints = false
let g = contentView.layoutMarginsGuide
NSLayoutConstraint.activate([
myTextView.topAnchor.constraint(equalTo: g.topAnchor),
myTextView.leadingAnchor.constraint(equalTo: g.leadingAnchor),
myTextView.trailingAnchor.constraint(equalTo: g.trailingAnchor),
myTextView.bottomAnchor.constraint(equalTo: g.bottomAnchor)
])
myTextView.delegate = self
}
func textViewDidChange(_ textView: UITextView) {
guard let str = textView.text else {
return
}
textChangedCallback?(str)
}
}
The result:

UIKit - Swift - Allow tableHeaderView to scroll up, but not down

I have a UIViewController with a navigation bar and a tab bar. Other than that, the whole screen is made up of a UITableView.
I have a large tableHeaderView that has the same background color as the navbar.
When I drag the content up (scrolling down) everything looks fine.
But if I drag it up, there is an ugly disconnection between the navigation bar and the header view.
Is there any way I could anchor it to the top when dragging down, while allowing it to scroll when dragging up?
You can try creating a view and placing it behind the tableView, as the table view scrolls, the height of the view is updated.
import UIKit
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
lazy var tableView : UITableView = {
let tableView = UITableView(frame: .zero, style: .plain)
tableView.dataSource = self
tableView.delegate = self
return tableView
}()
let backView : UIView = {
let view = UIView()
view.backgroundColor = .red
return view
}()
var backViewHeight : NSLayoutConstraint?
override func viewDidLoad() {
super.viewDidLoad()
self.title = "ViewController"
self.view.addSubview(backView)
backView.translatesAutoresizingMaskIntoConstraints = false
backView.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor).isActive = true
backView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true
backView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true
backViewHeight = backView.heightAnchor.constraint(equalToConstant: 0)
backViewHeight?.isActive = true
self.view.addSubview(tableView)
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor).isActive = true
tableView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
tableView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true
tableView.register(Cell.self, forCellReuseIdentifier: "cell")
tableView.register(Header.self, forHeaderFooterViewReuseIdentifier: "header")
tableView.backgroundColor = .clear
self.navigationController?.navigationBar.barTintColor = .red
self.navigationController?.navigationBar.isTranslucent = false
self.navigationController?.navigationBar.setValue(true, forKey: "hidesShadow")
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 3
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView.contentOffset.y < 0 {
backViewHeight?.constant = -scrollView.contentOffset.y
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
return cell
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let header = tableView.dequeueReusableHeaderFooterView(withIdentifier: "header")
header?.contentView.backgroundColor = .red
let headerLabel = UILabel(frame: CGRect(x: 0, y: 0, width: tableView.bounds.size.width, height: 100))
headerLabel.textAlignment = .center
headerLabel.text = "Header"
header?.addSubview(headerLabel)
return header
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 100
}
func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
let view = UIView()
view.backgroundColor = .white
return view
}
}
class Cell: UITableViewCell {
let label : UILabel = {
let label = UILabel()
label.text = "One Label"
return label
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.backgroundColor = .clear
setupViews()
}
func setupViews() {
self.backgroundColor = .white
self.addSubview(label)
label.frame = self.frame
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class Header : UITableViewHeaderFooterView {
override init(reuseIdentifier: String?) {
super.init(reuseIdentifier: reuseIdentifier)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
If you copy paste this code in an empty project you can have a look at the behavior. Don't forget to embed the ViewController in a NavigationController. Hope it helps
1) If an unwanted white space on the top of tableview is permanent and the constraints are correct this is the solution.
the scroll view insets adjusted automatically if you disable it should remove it
if #available(iOS 11.0, *) {
tableView.contentInsetAdjustmentBehavior = .never
} else {
automaticallyAdjustsScrollViewInsets = false
}
2) if u just have it when u pull down and it goes back to its normal state. It means is that the tableview bouncing is enabled and that is normal behaviour according to iOS documentation:
If the value of this property is true, the scroll view bounces when it encounters a boundary of the content. Bouncing visually indicates that scrolling has reached an edge of the content. If the value is false, scrolling stops immediately at the content boundary without bouncing. The default value is true.
you can uncheck the bouncing from the tableview in your storyboard or xib file. Or u can use this snippet:
tableView.bounces = false
tableView.alwaysBounceVertical = false
Note: that is not recommended to disable the scroll bouncing since it would make things feel very unnatural for iOS.
and also if you want to use pull to refresh it will not work.
So finally if u choose to not disable it you will have to change the background color of the parent of your tableview and it will solve it.
I hope that makes sense!

Custom TableViewCell triggers NSLayoutConstraint error 'UIView-Encapsulated-Layout-Height'

I use Automatic Height for cells.
And each time I want to update my cell with new data (model variable here) I updated my autolayout constraints and I get an error.
Here just to show the issue, I don't even change the constraints.
I simply ask to recalculate the layout.
At the first init of the cell : No warning, no problem.
The error:
<NSLayoutConstraint:0x174285d70 'UIView-Encapsulated-Layout-Height'
UITableViewCellContentView:0x1030f8a50.height == 500 (active)>
The code:
tableView.rowHeight = UITableViewAutomaticDimension
override func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return 500
}
class TestTableViewCell: UITableViewCell {
var model: X? {
didSet {
setNeedsLayout()
layoutIfNeeded()
}
}
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
let viewtop = UIView()
let viewbottom = UIView()
viewtop.backgroundColor = UIColor.yellow
viewbottom.backgroundColor = UIColor.red
contentView.backgroundColor = UIColor.blue
contentView.addSubview(viewtop)
contentView.addSubview(viewbottom)
viewtop.snp.makeConstraints { (make) in
make.top.equalTo(contentView)
make.left.right.equalTo(contentView)
make.height.equalTo(50)
}
viewbottom.snp.makeConstraints { (make) in
make.top.equalTo(viewtop.snp.bottom)
make.left.right.equalTo(contentView)
make.bottom.equalTo(contentView)
make.height.equalTo(120)
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
The question:
Why, after asking a re-layout of the same constraints, do I get an error ?
EDIT: another example for better comprehension here.
var botViewHeightConstraint:Constraint!
class TestTableViewCell: UITableViewCell {
var model: Int? {
didSet {
if model == 1 {
botViewHeightConstraint.update(offset:200)
}else{
botViewHeightConstraint.update(offset:120)
}
}
}
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
let viewtop = UIView()
let viewbottom = UIView()
viewtop.backgroundColor = UIColor.yellow
viewbottom.backgroundColor = UIColor.red
contentView.backgroundColor = UIColor.blue
contentView.addSubview(viewtop)
contentView.addSubview(viewbottom)
viewtop.snp.makeConstraints { (make) in
make.top.equalTo(contentView)
make.left.right.equalTo(contentView)
make.height.equalTo(50)
}
viewbottom.snp.makeConstraints { (make) in
make.top.equalTo(viewtop.snp.bottom)
make.left.right.equalTo(contentView)
make.bottom.equalTo(contentView)
botViewHeightConstraint = make.height.equalTo(120).constraint
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
CellForRow code:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let post = fetchedResultsController?.fetchedObjects?[(indexPath as NSIndexPath).section] {
let cell = tableView.dequeueReusableCell(withIdentifier: imagePostCellId) as! TestTableViewCell!
cell.model = 1
return cell
}
}
First, you keep neglecting to set your subviews' translatesAutoresizingMaskIntoConstraints to false. That's important. (Perhaps snapkit does that for you, however. I don't know.)
Second — and this is the big point — what you're doing is not how you size a variable height cell from the inside out. You do not change an absolute height constraint, as your code is doing. The sizing is based ultimately on the intrinsic content size of the subviews. Some subviews might have absolute heights, certainly, but ultimately there must be at least one with an intrinsic content size. That is the size that you are able to change dynamically in order to determine the height of a cell.
That is why, for example, a cell containing a UILabel is so easy to use with dynamic row heights. It has an intrinsic content size.
The intrinsic content size does not conflict with the built-in height of the cell (the UIView-Encapsulated-Layout-Height in your console dump); it supplements it (when the runtime calls systemLayoutSizeFitting(UILayoutFittingCompressedSize) behind the scenes, which is how automatic variable row heights works).
If you use custom subviews with an implementation of intrinsicContentSize, and if setting your model value in the cell triggers a call to invalidateIntrinsicContentSize, your example will work perfectly with no complaints in the console.
Here is an example of such a custom view:
class MyView : UIView {
var h : CGFloat = 200 {
didSet {
self.invalidateIntrinsicContentSize()
}
}
override var intrinsicContentSize: CGSize {
return CGSize(width:300, height:self.h)
}
}
When this is a subview of your cell's content view, setting this view's h in cellForRow sizes the cell's height correctly.
For example, let's suppose our cell's content view has just one subview, v, which is a MyView. Then:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! MyCell
let even = indexPath.row % 2 == 0
cell.v.backgroundColor = even ? .red : .green
cell.v.h = even ? 40 : 80 // triggers layout!
return cell
}
Why are you adding "make.height.equalTo(120)" when you've already set top bottom left and right constrain relative to content and top view
As per your code it seems the cell height is always 50+120.
Also see if overriding heightForRow and return UITableViewAutomaticDimension works .

How to Custom UITableViewCell in swift

I'm a newbie in swift, and i try to custom UITableViewCell, i see many tutorial in youtube and internet. But i can't do it, i tried to fix a lot of way but nothing is change.
here is my code :
class movieViewController: UIViewController,UITableViewDelegate,UITableViewDataSource {
var categories = ["Action", "Drama", "Science Fiction", "Kids", "Horror"]
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var btnMenu: UIBarButtonItem!
override func viewDidLoad() {
super.viewDidLoad()
btnMenu.target = self.revealViewController()
btnMenu.action = Selector("revealToggle:")
self.view.addGestureRecognizer(self.revealViewController().panGestureRecognizer())
self.revealViewController().rearViewRevealWidth = 200
self.tableView.registerClass(CategoryRow.self, forCellReuseIdentifier: "Cell")
// Do any additional setup after loading the view.
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return categories[section]
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return categories.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! CategoryRow
cell.labelA.text = self.categories[indexPath.row]
cell.labelB.text = self.categories[indexPath.row]
return cell
}
CategoryRow.swift:
class CategoryRow: UITableViewCell {
var labelA: UILabel!
var labelB: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
and my bug :
It seem that you don't have xib. And you just declare your label but you don't init it. So labelA and labelB will nil. it make crash.
If you don't want xib, You must implement two function in CategoryRow like code below:
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.labelA = UILabel(frame: CGRectMake(0,0, self.contentView.frame.size.width, self.contentView.frame.size.height))
//Same here for labelB
self.contentView.addSubview(labelA)
}
Another way is you create xib UITableCiewCell with name same with your class. And set this cell is your class. You design labelA and labelB in this xib and drage outlet into class. And you modify in viewDidLoad
self.tableView.registerNib(UINib(nibName: "nameYourxib"), forCellReuseIdentifier: "Cell")
You haven't linked labelA inStoryboard.
Creating an Outlet Connection
You have not allocate the labelA or labelB. Thats why show the error. Connect your label with you UI like this:
#vien vu answer is right but what to do without XIB ?? Here is the complete solution for Custom cell in swift
You need to add delegate a datasource in viewDidLoad
viewDidLoad() {
self.tableView.delegate = self
self.tableView.datasource = self
}
and you need to create the labelA and labelB by outlets not variables. Hold control and drag from Storyboard to the corisponding file and let go, choose outlet and give it the name labelA and labelB

Resources