UITableViewCell subclassed in swift 3 has contentView always of 320 points - ios

I am trying to create a UILabel in my subclassed UITableViewCell to make it the full width of the cell. But the contentView is always 320pt if in iPhone 7 or 7+.
The code is:
import UIKit
import SwipeCellKit
class ReuseableCell: SwipeTableViewCell
{
public var test = UILabel();
var animator: Any?
override init(style: UITableViewCellStyle, reuseIdentifier: String!)
{
super.init(style: style, reuseIdentifier: reuseIdentifier);
// Always 320pt and I want full width of the table cell
var width = self.contentView.frame.width;
test = UILabel(frame: CGRect(x: 0, y: 0, width: width, height: 100));
test.text = "Lorem ipsum...";
...
I have been stuck on this for 2 nights. Thanks.

You can try auto-layout
override init(style: UITableViewCellStyle, reuseIdentifier: String!)
{
super.init(style: style, reuseIdentifier: reuseIdentifier);
test = UILabel(frame: CGRect.zero);
self.contentView.addSubview(test)
test.translatesAutoresizingMaskIntoConstraints = false
test.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true
test.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true
test.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
test.heightAnchor.constraint(equalToConstant: 200).isActive = true
test.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
}

You have to add Your label inside override init(style: UITableViewCellStyle, reuseIdentifier: String!) but to read correct frame you have override override func layoutSubviews() and read correct frame

May be you forgot to set constrains to your 'UITableView'. Try set tableview width constrain equal to superview width.
In ViewController:
self.view.addConstraint(NSLayoutConstraint(item: self.tableView, attribute: .width, relatedBy: .equal, toItem: self.view, attribute: .width, multiplier: 1.0, constant: 0))

Related

Self sizing cell with multiple Stack Views

I've looked all over the forum and attempted all the solutions and thus far nothing has worked. I noticed my UIImageView was overlaying multiple cells, meaning the celll did not automatically adjust its height. Here is the constraint i found in the console it complained about.
"<NSLayoutConstraint:0x600001970f50 'UIView-Encapsulated-Layout-Height' UITableViewCellContentView:0x7f86a4813dd0.height == 44 (active)>"
In my tableViewController I have the follow
tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = 300
Here is my entire cell that should self size.
import UIKit
class UserConnectionCell: UITableViewCell {
fileprivate let leftImageView: UIImageView = {
let uiImageView = UIImageView()
uiImageView.translatesAutoresizingMaskIntoConstraints = false
return uiImageView
}()
fileprivate let leftLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
fileprivate let middleLabel: UILabel = {
let label = UILabel()
label.numberOfLines = 0
label.font = UIFont(name: "Ariel", size: 10)
label.textAlignment = .center
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
fileprivate let rightImageView: UIImageView = {
let uiImageView = UIImageView()
uiImageView.translatesAutoresizingMaskIntoConstraints = false
return uiImageView
}()
fileprivate let rightLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
fileprivate let stackViewLeft: UIStackView = {
let stackView = UIStackView()
stackView.axis = .vertical
stackView.translatesAutoresizingMaskIntoConstraints = false
return stackView
}()
fileprivate let stackViewRight: UIStackView = {
let stackView = UIStackView()
stackView.axis = .vertical
stackView.translatesAutoresizingMaskIntoConstraints = false
return stackView
}()
fileprivate let stackViewMain: UIStackView = {
let stackView = UIStackView()
stackView.axis = .horizontal
stackView.alignment = .fill
stackView.spacing = 0
stackView.translatesAutoresizingMaskIntoConstraints = false
return stackView
}()
//
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier:reuseIdentifier)
stackViewLeft.addArrangedSubview(leftImageView)
stackViewLeft.addArrangedSubview(leftLabel)
stackViewRight.addArrangedSubview(rightImageView)
stackViewRight.addArrangedSubview(rightLabel)
stackViewMain.addArrangedSubview(stackViewLeft)
stackViewMain.addArrangedSubview(middleLabel)
stackViewMain.addArrangedSubview(stackViewRight)
contentView.addSubview(stackViewMain)
}
// called when trying to layout subviews.
override func layoutSubviews() {
super.layoutSubviews()
stackViewLeft.addConstraint(NSLayoutConstraint(item: leftImageView, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .width, multiplier: 1, constant: 100))
stackViewLeft.addConstraint(NSLayoutConstraint(item: leftImageView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .height, multiplier: 1, constant: 100))
stackViewRight.addConstraint(NSLayoutConstraint(item: rightImageView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .height, multiplier: 1, constant: 100))
stackViewRight.addConstraint(NSLayoutConstraint(item: rightImageView, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .width, multiplier: 1, constant: 100))
NSLayoutConstraint.activate(
[stackViewMain.topAnchor.constraint(equalTo: contentView.topAnchor,constant: 0),
stackViewMain.leadingAnchor.constraint(equalTo: contentView.leadingAnchor,constant: 0),
stackViewMain.trailingAnchor.constraint(equalTo: contentView.trailingAnchor,constant: 0),
stackViewMain.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: 0)
])
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
var viewModel : UserConnectionViewModel? {
didSet {
// move this to the view model
if let profileUrl = viewModel?.leftImageUrl {
leftImageView.loadImageFromURL(url: profileUrl)
} else {
leftImageView.image = UIImage(named: "defaultprofile")
}
if let profileUrl = viewModel?.rightImageUrl {
rightImageView.loadImageFromURL(url: profileUrl)
} else {
rightImageView.image = UIImage(named: "defaultprofile")
}
leftLabel.text = viewModel?.leftLabel
middleLabel.text = viewModel?.middleLabel
rightLabel.text = viewModel?.rightlabel
}
}
override func awakeFromNib() {
super.awakeFromNib()
self.contentView.autoresizingMask = .flexibleHeight
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
Any ideas for why the cell is not self sizing?
First, a cell's contentView is a "special" view with properties integral to the table view's operation.
So, do not do this:
self.contentView.autoresizingMask = .flexibleHeight
Second, layoutSubviews() can be (and usually is) called multiple times during the lifecycle of a cell / view. Your constraint setup should be done in init:
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier:reuseIdentifier)
stackViewLeft.addArrangedSubview(leftImageView)
stackViewLeft.addArrangedSubview(leftLabel)
stackViewRight.addArrangedSubview(rightImageView)
stackViewRight.addArrangedSubview(rightLabel)
stackViewMain.addArrangedSubview(stackViewLeft)
stackViewMain.addArrangedSubview(middleLabel)
stackViewMain.addArrangedSubview(stackViewRight)
contentView.addSubview(stackViewMain)
NSLayoutConstraint.activate([
// constrain main stack view to all 4 sides of contentView
stackViewMain.topAnchor.constraint(equalTo: contentView.topAnchor,constant: 0),
stackViewMain.leadingAnchor.constraint(equalTo: contentView.leadingAnchor,constant: 0),
stackViewMain.trailingAnchor.constraint(equalTo: contentView.trailingAnchor,constant: 0),
stackViewMain.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: 0),
// constrain left image view Width: 100-pts,
// Height equal to Width (1:1 ratio)
leftImageView.widthAnchor.constraint(equalToConstant: 100.0),
leftImageView.heightAnchor.constraint(equalTo: leftImageView.widthAnchor),
// constrain right image view Width: 100-pts,
// Height equal to Width (1:1 ratio)
rightImageView.widthAnchor.constraint(equalToConstant: 100.0),
rightImageView.heightAnchor.constraint(equalTo: rightImageView.widthAnchor),
])
}
So... replace your init with the above code and completely remove both your awakeFromNib() and layoutSubviews() funcs.
You should get this:

Creating Custom UITableViewCell in Swift Programmatically

I need a cell with a label that takes up the entire contentView. I've done this a bunch of times with a nib but this time I decided to avoid the creating a nib. Below is my code.
class StackLegendCell: UITableViewCell {
var title = UILabel()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
contentView.addSubview(title)
title.numberOfLines = 1
title.textAlignment = .center
title.setContentCompressionResistancePriority(1000, for: .horizontal)
title.setContentHuggingPriority(1000, for: .horizontal)
title.text = "??"
//contentView.translatesAutoresizingMaskIntoConstraints = false
title.widthAnchor.constraint(equalTo: contentView.widthAnchor, multiplier: 1.0).isActive = true
title.heightAnchor.constraint(equalTo: contentView.heightAnchor, multiplier: 1.0).isActive = true
}
The problem is that the label doesn't show anything. A check into view debugger shows that width = height = 0. But I'm setting height and width anchors which should give a size to be the same as the contentView.
What am I missing?
Thanks.
Finally, I found the solution. You missed setting the frame of the titleLabel
title.frame = contentView.frame
add this line of code and build. This should work.

row height is not automatically updated

EDITED:
This is my custom cell class. It has a TextField and a TextView. Whatever I do I can't get the row height updated automatically. I know I can do it manually using heightForRowAt but I don't want to do that.
class customCell: UITableViewCell, UITextViewDelegate{
var didSetupConstraints = false
var titleField : UITextField = {
var textField = UITextField()
textField.placeholder = " Subject (optional)"
textField.backgroundColor = UIColor.lightGray
textField.translatesAutoresizingMaskIntoConstraints = false
textField.layer.cornerRadius = 3
textField.clipsToBounds = true
return textField
}()
var messageView : UITextView = {
var textView = UITextView()
textView.text = "Add your email here"
textView.translatesAutoresizingMaskIntoConstraints = false
textView.backgroundColor = UIColor.red
return UITextView()
}()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.contentView.addSubview(titleField)
self.contentView.addSubview(messageView)
messageView.delegate = self
addConstraints()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func addConstraints(){
contentView.addConstraints([titleField.topAnchor.constraint(equalTo: self.contentView.topAnchor, constant: 23),titleField.trailingAnchor.constraint(equalTo: self.contentView.trailingAnchor, constant: -18),titleField.leadingAnchor.constraint(equalTo: self.contentView.leadingAnchor, constant: 18) ])
titleField.addConstraint(NSLayoutConstraint(item: titleField, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 50))
contentView.addConstraints([messageView.topAnchor.constraint(equalTo: titleField.bottomAnchor, constant: 11),messageView.trailingAnchor.constraint(equalTo: self.contentView.trailingAnchor, constant: -18),messageView.leadingAnchor.constraint(equalTo: self.contentView.leadingAnchor, constant: 18), messageView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -5)])
messageView.addConstraint(NSLayoutConstraint(item: messageView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 100))
}
override func layoutSubviews() {
super.layoutSubviews()
contentView.setNeedsLayout()
contentView.layoutIfNeeded()
}
override func updateConstraints() {
if !didSetupConstraints {
addConstraints()
didSetupConstraints = true
}
super.updateConstraints()
}
func textViewDidBeginEditing(_ textView: UITextView) {
if textView.textColor == UIColor.lightGray {
textView.text = nil
textView.textColor = UIColor.black
}
}
func textViewDidEndEditing(_ textView: UITextView) {
if textView.text.isEmpty {
textView.text = "Add your email here"
textView.textColor = UIColor.lightGray
}
}
}
I have already seen this question and from what I have understood the things I need to do are:
Add tableView.estimatedRowHeight = 44.0 tableView.rowHeight = UITableViewAutomaticDimension which I have done in tableViewController
Add a bottom and top constraint: I have added a topAnchor to my TextField + a constraint between my TextField and TextView + a constraint between my TextView's bottomAnchor and the contentView bottomAnchor
I have added my constraints code into my updateConstraints() method.
Not sure if I need to do anything else, but I've done all three but it still doesn't work. I'm guessing that maybe my bottom/top constraints are not set up correctly. The current result that I get is (The textView isn't visible at all :(( )
yet what I expect to get is:
EDIT 2
See image:
After all the fixes, the only problem I have now is that the empty cells don't have the default size of 44, is it that the tableView is trying to be smart and adjusts the row height based on the last cell height?
A few things:
updateConstraints can be called multiple times by the system, so use a flag to only add your constraints the first time.
messageView.topAnchor.constraint(equalTo: titleField.topAnchor, constant: 11) should be messageView.topAnchor.constraint(equalTo: titleField.bottomAnchor, constant: 11)
Try giving your messageView a height.
As #Honey pointed out, textView was not returned in the initialization of messageView.
About empty cell heights, if you don't want empty cells at all, just do tableView.tableFooterView = UIView() to get rid of them. It's probably the table view being smart about cell heights, like you said.

Custom cell with dynamic subviews stack

I have a tableView populated by an array of 'Post'. On this tableView I register a class 'PostCell'. This PostCell isn't supported by any nib since I want to dynamically compose it with subviews.
Suppose two subviews, a top yellowView and a bottom redView, each subclass of UIView, are stacked on a PostCell.
My issue is that the below code returns this:
ViewController
override func viewDidLoad(){
super.viewDidLoad()
tableView.delegate = self
tableView.datasource = self
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 80
tableView.registerClass(PostCell.self, forCellReuseIdentifier: "PostCell")
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let post = posts[indexPath.row]
let cell = tableView.dequeueReusableCellWithIdentifier("PostCell") as! PostCell
cell.configureForData(post)
return cell
}
PostCell
class PostCell: UITableViewCell {
override init(style: UITableViewCellStyle, reuseIdentifier: String?){
super.init(style: style, reuseIdentifier: reuseIdentifier)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
fun configureForData(post: Post){
let yellowView: UIView = UIView(frame: CGRect(x: 0, y: 0, width: contentView.frame.width, height: 40))
yellowView.backgroundColor = UIColor.yellowColor()
yellowView.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(yellowView)
let redView: UIView = UIView(frame: CGRect(x: 0, y: 40, width: contentView.frame.width, height: 40))
redView.backgroundColor = UIColor.redColor()
redView.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(redView)
}
override func layoutSubviews() {
super.layoutSubviews()
let height = NSLayoutConstraint(item: contentView, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: 80)
contentView.translatesAutoresizingMaskIntoConstraints = false
contentView.addConstraint(height)
}
}
EDIT 1: This fixes the layout
class PostCell: UITableViewCell {
let yellowView = UIView()
let redView = UIView()
override init(style: UITableViewCellStyle, reuseIdentifier: String?){
super.init(style: style, reuseIdentifier: reuseIdentifier)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
fun configureForData(post: Post){
yellowView.backgroundColor = UIColor.yellowColor()
yellowView.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(yellowView)
redView.backgroundColor = UIColor.redColor()
redView.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(redView)
layoutIfNeeded
}
override func layoutSubviews() {
super.layoutSubviews()
let height = NSLayoutConstraint(item: contentView, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: 80)
contentView.translatesAutoresizingMaskIntoConstraints = false
contentView.addConstraint(height)
yellowView.frame = UIView(frame: CGRect(x: 0, y: 0, width: contentView.frame.width, height: 40))
redView.frame = UIView(frame: CGRect(x: 0, y: 40, width: contentView.frame.width, height: 40))
}
Try to use cell.layoutIfNeeded() after cell.configureForData(post) in your tableView:cellForRowAtIndexPath function. But I think it's better to calculate cell height and return it in tableView:heightForRowAtIndexPath function.
Also you have problem with cell width:
It's because you got contentView.frame.width before id correctly laid out. You need to update frames in each layoutSubviews call or use constraints.

Mimic UITableViewCellStyleValue1 accessoryType-detailTextLabel spacing with Autolayout?

When using a default UITableViewCell with UITableViewCellStyleValue1 the detailTextLabel is aware of the accessoryType. The distance between the label and the edge of the cell is different from the distance between the label and the accessoryType view. When I use a custom UITableViewCell subclass the two distances are equal. Which doesn't look as nice as the default implementation.
It's a bit hard to explain, so here is a screenshot:
The first two cells are default UITableViewCell with UITableViewCellStyleValue1.
Cell 3 and 4 are custom cells that were setup with a H:[label]-16-| constraint.
Cell 5 and 6 are custom cells that were setup with a Trailing-0-TrailingMargin constraint.
I want to get the behavior of the default cell for my own cells. How can I achieve that with autolayout?
iOS actually moves the contentView around, so the solution is to simply adjust the distance between the contentView and the label:
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
/* ... */
trailingDetailConstraint = NSLayoutConstraint(item: contentView, attribute: .Right, relatedBy: .Equal, toItem: rightLabel, attribute: .Right, multiplier: 1, constant: 15)
contentView.addConstraint(trailingDetailConstraint)
}
override var accessoryType: UITableViewCellAccessoryType {
didSet {
if accessoryType == .None {
trailingDetailConstraint.constant = 15
}
else {
trailingDetailConstraint.constant = 0
}
layoutIfNeeded()
}
}
override var editingAccessoryType: UITableViewCellAccessoryType {
didSet {
if accessoryType == .None {
trailingDetailConstraint.constant = 15
}
else {
trailingDetailConstraint.constant = 0
}
layoutIfNeeded()
}
}

Resources