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

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 .

Related

How to update height Constraints by SnapKit and update cell height?

I have a TableViewCell and two button to switch different constrain.
I want to update it's height constrain and cell height.
like following pic1
when I click buttonB, the view will change like pic2
Then I click buttonA, the view will back to pic1
I try to modify constrains, but I fail to update height.
Have any idea or answer to me?
Thanks
pic1
pic2
Here is code:
class CellContainView: UIView {
let buttonA: UIButton = { () -> UIButton in
let ui = UIButton()
ui.titleLabel?.numberOfLines = 0
ui.setTitle("Click\nA", for: .normal)
ui.backgroundColor = UIColor.blue
return ui
}()
let buttonB: UIButton = { () -> UIButton in
let ui = UIButton()
ui.titleLabel?.numberOfLines = 0
ui.setTitle("Click\nB", for: .normal)
ui.backgroundColor = UIColor.gray
return ui
}()
let buttonC: UIButton = { () -> UIButton in
let ui = UIButton()
ui.titleLabel?.numberOfLines = 0
ui.setTitle("Click\nC", for: .normal)
ui.backgroundColor = UIColor.brown
return ui
}()
let labelA: UILabel = { () -> UILabel in
let ui = UILabel()
ui.text = "Test"
return ui
}()
let viewA: UIView = { () -> UIView in
let ui = UIView()
ui.backgroundColor = UIColor.red
return ui
}()
override init(frame: CGRect) {
super.init(frame: frame)
addUI()
addConstrain()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func addUI() {
self.addSubview(buttonA)
self.addSubview(buttonB)
self.addSubview(buttonC)
self.addSubview(labelA)
self.addSubview(viewA)
}
func addConstrain() {
buttonA.snp.makeConstraints { (make) in
make.left.top.equalToSuperview()
make.height.equalTo(60)
}
buttonB.snp.makeConstraints { (make) in
make.left.equalTo(buttonA.snp.right)
make.top.right.equalToSuperview()
make.width.equalTo(buttonA.snp.width)
make.height.equalTo(buttonA.snp.height)
}
buttonC.snp.makeConstraints { (make) in
make.left.equalTo(15)
make.right.equalTo(-15)
make.bottom.equalTo(-15)
make.height.equalTo(50)
}
labelA.snp.makeConstraints { (make) in
make.left.equalTo(15)
make.top.equalTo(buttonA.snp.bottom).offset(15)
make.width.equalTo(195)
make.height.equalTo(50)
}
viewA.snp.makeConstraints { (make) in
make.left.equalTo(labelA.snp.right)
make.top.equalTo(buttonA.snp.bottom).offset(15)
make.right.equalTo(-15)
make.height.equalTo(50)
make.bottom.equalTo(buttonC.snp.top).offset(-10)
}
}
func updateConstrain(sender: UIButton) {
switch sender {
case buttonA:
viewA.snp.updateConstraints { (make) in
make.height.equalTo(50)
}
case buttonB:
viewA.snp.updateConstraints { (make) in
make.height.equalTo(150)
}
default:
break
}
}
}
class TestTableViewCell: UITableViewCell {
let cellContainView: CellContainView = { () -> CellContainView in
let ui = CellContainView()
ui.backgroundColor = UIColor.orange
return ui
}()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.addSubview(cellContainView)
cellContainView.snp.makeConstraints { (make) in
make.left.top.equalTo(15)
make.bottom.right.equalTo(-15)
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
let tableView: UITableView = { () -> UITableView in
let ui = UITableView()
return ui
}()
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 44
tableView.register(TestTableViewCell.self, forCellReuseIdentifier: "TestTableViewCell")
tableView.tableFooterView = UIView()
self.view.addSubview(tableView)
tableView.snp.makeConstraints { (make) in
make.top.left.right.bottom.equalToSuperview()
}
}
#objc func buttonAClicked(sender: UIButton) {
let index = IndexPath(row: 0, section: 0)
let cell = tableView.cellForRow(at: index) as! TestTableViewCell
cell.cellContainView.updateConstrain(sender: sender)
tableview.reloadData()
}
#objc func buttonBClicked(sender: UIButton) {
let index = IndexPath(row: 0, section: 0)
let cell = tableView.cellForRow(at: index) as! TestTableViewCell
cell.cellContainView.updateConstrain(sender: sender)
tableview.reloadData()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TestTableViewCell", for: indexPath) as! TestTableViewCell
cell.cellContainView.buttonA.addTarget(self, action: #selector(buttonAClicked), for: .touchUpInside)
cell.cellContainView.buttonB.addTarget(self, action: #selector(buttonBClicked), for: .touchUpInside)
return cell
}
}
Update constrain problem when I click butttonB (Add update viewA heightConstrain & tableview.reloadData()):
TestCellUpdateHeight[29975:5195310] [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.
(
"<SnapKit.LayoutConstraint:0x6040000a4320#CellContainView.swift#75 UIButton:0x7fa1d0d0d680.height == 60.0>",
"<SnapKit.LayoutConstraint:0x6040000a49e0#CellContainView.swift#89 UIButton:0x7fa1d0d10610.height == 50.0>",
"<SnapKit.LayoutConstraint:0x6040000a51c0#CellContainView.swift#104 UIView:0x7fa1d0d11130.height == 150.0>",
"<SnapKit.LayoutConstraint:0x6040000a4800#CellContainView.swift#80 UIButton:0x7fa1d0d0f7b0.top == TestCellUpdateHeight.CellContainView:0x7fa1d0d0d470.top>",
"<SnapKit.LayoutConstraint:0x6040000a4a40#CellContainView.swift#82 UIButton:0x7fa1d0d0f7b0.height == UIButton:0x7fa1d0d0d680.height>",
"<SnapKit.LayoutConstraint:0x6040000a4ec0#CellContainView.swift#88 UIButton:0x7fa1d0d10610.bottom == TestCellUpdateHeight.CellContainView:0x7fa1d0d0d470.bottom - 15.0>",
"<SnapKit.LayoutConstraint:0x6040000a5100#CellContainView.swift#102 UIView:0x7fa1d0d11130.top == UIButton:0x7fa1d0d0f7b0.bottom + 15.0>",
"<SnapKit.LayoutConstraint:0x6040000a5220#CellContainView.swift#105 UIView:0x7fa1d0d11130.bottom == UIButton:0x7fa1d0d10610.top - 10.0>",
"<SnapKit.LayoutConstraint:0x6040000a52e0#TestTableViewCell.swift#26 TestCellUpdateHeight.CellContainView:0x7fa1d0d0d470.top == TestCellUpdateHeight.TestTableViewCell:0x7fa1d208a000.top + 15.0>",
"<SnapKit.LayoutConstraint:0x6040000a53a0#TestTableViewCell.swift#27 TestCellUpdateHeight.CellContainView:0x7fa1d0d0d470.bottom == TestCellUpdateHeight.TestTableViewCell:0x7fa1d208a000.bottom - 15.0>",
"<NSLayoutConstraint:0x6080002819f0 'UIView-Encapsulated-Layout-Height' TestCellUpdateHeight.TestTableViewCell:0x7fa1d208a000'TestTableViewCell'.height == 230 (active)>"
)
Will attempt to recover by breaking constraint
In general, to update the height constraint of a uiview:UIView you should save it first:
var viewHeightConstraint: Constraint!
uiview.snp.makeConstraints { (make) in
viewHeightConstraint = make.height.equalTo(50).constraint
}
If you simply use updateConstraints function, it will add a new height constraint that causes the issue Unable to simultaneously satisfy constraints. So you have to remove the last one:
viewHeightConstraint.deactivate()
Then make constraint again:
uiview.snp.makeConstraints { (make) in
viewHeightConstraint = make.height.equalTo(100).constraint
}
You cannot update cell's height just by changing its constraint.
Table can only update the height of its cells as a result of reloadData() call, after which it will ask its delegate for cell's height (or calculate automatically, if your cells are self-sizing).
So, to get the effect you want you might do something like the following.
In buttonBClicked method remember the state you want to get (using some variable maybe).
In table delegate's method tableView(_:, heightForRowAt:) return the correct height for the cell.
This is not ideal and serves just as a direction for considering your solution. My main point is that you need to reload table view to change cell's height.
What you currently do
labelA.snp.updateConstraints { (make) in
make.bottom.equalTo(buttonC.snp.top).offset(-100)
}
will increase the bottom distance of the labelA , but will leave it's top constant the same you need to either
1- Update the height of the labelA
or
2- Update the height of the redView in front of it
Also don't forget to call
cell.cellContainView.updateConstrain(sender: sender)
cell.layoutIfNeeded()
note also because of cell reusing you may find other cell stretched , so you need to keep track of stretched cell indices and apply that inside cellForRowAt
You can also do this
func tableView(_ tableView: UITableView,
heightForRowAt indexPath: IndexPath) -> CGFloat
return expandArr[indexPath.row] ? 300 : 100
}
where
var expandArr = [Bool]()
indicating whether the cell is expanded or not , also if you have a model you may add a bool value to it instead of this separate arr ( it's for illustration )
If you need recalculate height cell (without recreate cells / reload cells) just call:
tableView.beginUpdates()
tableView.endUpdates()
i think you don’t need to change Bottom Constraint.what you required is Apply height Constraint to redView.and change it according to your button Selection.
func updateConstrain(sender: UIButton) {
switch sender {
case buttonA:
redView.snp.updateConstraints { (make) in
make.heigh.equalTo(10)
}
case buttonB:
redView.snp.updateConstraints { (make) in
make.heigh.equalTo(100)
}
default:
break
}
}
}
And don’t forget to reload Specific Row after updating Constraint
#objc func buttonAClicked(sender: UIButton) {
let index = IndexPath(row: 0, section: 0)
let cell = tableView.cellForRow(at: index) as! TestTableViewCell
cell.cellContainView.updateConstrain(sender: sender)
yourtableview.reloadRows(at: [IndexPath(row: 0, section: 0)], with: UITableViewRowAnimation.none)
}
#objc func buttonBClicked(sender: UIButton) {
let index = IndexPath(row: 0, section: 0)
let cell = tableView.cellForRow(at: index) as! TestTableViewCell
cell.cellContainView.updateConstrain(sender: sender)
yourtableview.reloadRows(at: [IndexPath(row: 0, section: 0)], with: UITableViewRowAnimation.none)
}

SnapKit - UITableView height is not change with dynamical content of cell

I'm a beginner in SnapKit, I want to implement a UITableViewController with SnapKit, each row have two UILabel, one of them is Title and another one is Value.
My issue is the height of the row in UITableView not changed according to the content of each row.
here is my code:
class ViewController:
import UIKit
import SnapKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
// MARK: Property List
var list: NSMutableArray?
let myTableView: UITableView = {
let table = UITableView()
return table
}()
//MARK: Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
self.myTableView.rowHeight = UITableView.automaticDimension
self.myTableView.estimatedRowHeight = 100
title = "TableView Page"
setup()
setupViews()
}
// MAKR: Setup View
func setup() {
self.view.backgroundColor = .white
navigationController?.navigationBar.prefersLargeTitles = true
}
func setupViews () {
self.view.addSubview(myTableView)
myTableView.snp.makeConstraints { make in
make.top.left.bottom.right.equalTo(10)
}
myTableView.register(CustomCell.self, forCellReuseIdentifier: CustomCell.customCell)
myTableView.delegate = self
myTableView.dataSource = self
}
// MARK: TableView DataSource
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: CustomCell.customCell, for: indexPath) as! CustomCell
cell.title.text = "title is lognest??"
cell.value.text = (indexPath.row % 2 == 0) ?
"longest value longest value longest value longest value longest value longest value longest value for text"
: "short value"
cell.title.snp.makeConstraints { (make) in
make.top.equalTo(cell.value.snp.top)
make.left.equalTo(20)
make.trailing.equalTo(cell.value.snp.leading)
}
cell.value.snp.makeConstraints { (make) in
make.right.equalTo(-20)
make.top.equalTo(cell.title.snp.top)
make.bottom.equalTo(-10)
}
NSLog("value height is: \(cell.value.frame.height)")
NSLog("cell height is: \(cell.frame.height)")
return cell;
}
}
class CustomCell:
// MARK: custom cell
class CustomCell: UITableViewCell {
// MARK: Property
static var customCell = "cell"
public var title:UILabel = {
let tit = UILabel()
tit.setContentHuggingPriority(.defaultLow, for: .horizontal)
tit.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal)
tit.textColor = .black
tit.alpha = 0.6
return tit
}()
public var value:UILabel = {
let val = UILabel()
val.textColor = .black
val.setContentHuggingPriority(.defaultHigh, for: .horizontal)
val.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
val.lineBreakMode = NSLineBreakMode.byWordWrapping
val.numberOfLines = 0
val.alpha = 0.75
return val
}()
// MARK: initializer
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.addSubview(title)
self.addSubview(value)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init?(coder aDecoder: NSCoder)")
}
}
And this is console logs:
snapkitTest[26345:1387159] cell height is: 44.0
snapkitTest[26345:1387159] value height is: 0.0
1- You have to add the label to
self.contentView.addSubview(title)
2- You need to set bottom constraint to contentView
title.snp.makeConstraints { (make) -> Void in
make.trailing.equalTo(value.snp.leading)
make.top.equalTo(self.contentView.snp.top).inset(10)
make.left.equalTo(20)
}
value.snp.makeConstraints { (make) -> Void in
make.right.equalTo(-40)
make.top.equalTo(self.contentView.snp.top).inset(10)
make.bottom.equalTo(-10)
}
Also transfer this constraints to init of cell custom class , as not to re-add constraints every scroll of tableView

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

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)

Empty Cells in TableView with Custom TableViewCell - Swift

I am currently learning Swift programatically. I am wanting to add a tableView to a viewController (for the purpose of being able to manipulate the constraints later) and customize the cells with a TableViewCell.
I can do this with my eyes closed when using the storyboard, but when I try to do it with just straight code I have empty cells.
My storyboard is comprised of one (1) empty viewController that has the custom class of ViewController
I have looked at others with similar issues but non of the solutions have worked. Would love to know what I am overlooking (probably something simple). Thanks in advance for the help!
ViewController.swift:
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource{
var tableView: UITableView = UITableView()
var items: [String] = ["Viper", "X", "Games"]
override func viewDidLoad() {
tableView.frame = CGRectMake(0, 0, view.frame.size.width, view.frame.size.height)
tableView.delegate = self
tableView.dataSource = self
tableView.registerClass(TableViewCell.self, forCellReuseIdentifier: "cell")
self.view.addSubview(tableView)
}
func tableView(tableView:UITableView, heightForRowAtIndexPath indexPath:NSIndexPath)->CGFloat
{
return 50
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return self.items.count
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 3
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! TableViewCell
//cell.textLabel?.text = self.items[indexPath.row]
cell.companyName.text = "name"
return cell
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
TableViewCell:
import UIKit
class TableViewCell: UITableViewCell {
var companyName = UILabel()
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
companyName.frame = CGRectMake(0, 0, 200, 20)
companyName.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(companyName)
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
Hello #Michael First thing is you should only use awakeFromNib when you are using a .xib(Nib) and in your case you are using custom class without such xib so, you should use
override init(style: UITableViewCellStyle, reuseIdentifier: String?){
super.init(style: style, reuseIdentifier: reuseIdentifier)
companyName = UILabel(frame: CGRectMake(0, 0, 200, 20))
contentView.addSubview(companyName)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
also you should initialise your label before using it.
this will solve your problem.
Read apple's documentation for subclassing UITableViewCell here.
If you want your custom cell to load from some custom xib you do sometimes like:
tableView.registerNib(UINib(nibName: "CustomTableViewCell", bundle: NSBundle.mainBundle()), forCellReuseIdentifier: "CustomTableViewCell")
And you should have CustomTableViewCell.xib file where you have table view cell with reuse identifier CustomTableViewCell
Checkout how your cell's companyLabel is laid out. Does it exist or no?
In your code, I replaced companyLabel with default textLabel and it worked for me.
cell.textLabel!.text = self.items[indexPath.row]
I think awakeFromNib is not called because you do not register a nib but a class. Try this instead:
class TableViewCell: UITableViewCell {
let companyName = UILabel()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
// Initialization code
companyName.frame = CGRectMake(0, 0, 200, 20)
companyName.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(companyName)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}

Assigning property of custom UITableViewCell

I am using the below custom UITableViewCell without nib file.
I have no problem in viewing it in my UITableViewController.
My problem is with assigning a text to "name" label cell.name.text = "a Name" .. noting is assigned
Can you help me?
import UIKit
class ACMenuCell: UITableViewCell {
#IBOutlet var name : UILabel!
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
name = UILabel(frame: CGRectMake(20, 10, self.bounds.size.width , 25))
name.backgroundColor = .whiteColor()
self.contentView.addSubview(name)
self.contentView.backgroundColor = .blueColor()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func layoutSubviews() {
super.layoutSubviews()
}
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
}
}
You might want to try this: Create a public function in a subclass of UITableViewCell, and inside this funxtion set the text to label.
(void)initializeCell
{
do whatever like setting the text for label which is a subview of tableViewCell
}
And from cellForRowAtIndexPAth, call this function on cell object:
// taking your code for demo
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let reuseIdentifierAC:NSString = "ACMenuCell"; var cell = tableView.dequeueReusableCellWithIdentifier(reuseIdentifierAC, forIndexPath:indexPath) as ACMenuCell cell = ACMenuCell(style: UITableViewCellStyle.Default, reuseIdentifier: reuseIdentifierAC) [cell initializeCell] return cell }
This approach has worked for me.

Resources