I would like to have a similar behavior than the <Table> HTML tag, in the sense where the frame is sized according to its content.
In my very context, I use an UIStackView as the content view of a UITableViewCell. As items in the cell are various information, the resulting height of the cell should be variable.
My strategy is to programmatically build a cell as a UIStackView with a .Vertical axis, as in the code snippet as follows:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
let sv = UIStackView(frame: tableview.frame)
sv.axis = .Vertical
cell.addSubview(sv)
for i in information {
let l = UILabel()
l.text = i
sv.addSubViewAndArrange(l)
}
return cell
}
Unfortunately, the cell size does not adjust to the content, and as a result I have to set the cell height myself, as follows:
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return cellHeight // a constant
}
How could I fix that?
UIStackView is designed to grow its size according to the content. In order for that to work, you need to set up the constraints between the UIStackView and the UITableViewCell. For example, if UIStackView is first-item and UITableViewCell is it's super-view, then this is how the constraints look like in interface builder:
If you like setting up constraints in code, that should work too.
For example, assuming stackView and cellView are the names, then above constraints' Swift-code would look like:
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.topAnchor.constraint(equalTo: cellView.topAnchor, constant: 0).isActive = true
stackView.bottomAnchor.constraint(equalTo: cellView.bottomAnchor, constant: 0).isActive = true
stackView.leadingAnchor.constraint(equalTo: cellView.leadingAnchor, constant: 0).isActive = true
stackView.trailingAnchor.constraint(equalTo: cellView.trailingAnchor, constant: 0).isActive = true
To demonstrate that this will work, I have this for the cellForRowAt function. Basically, it puts a number of UILabel inside the UIStackView and the label count is depending on the row number.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TableviewCell", for: indexPath) as! TableviewCell
for i in 1...indexPath.row + 1 {
let label = UILabel()
label.text = "Row \(indexPath.row), Label \(i)"
cell.stackView.addArrangedSubview(label)
}
return cell
}
Here is the final result:
https://github.com/yzhong52/AutosizeStackview
I built this example I hope it helps, I've created a tableView which use a cell that contains a stackView and the views loaded in the stackView are gotten from a nib file
https://github.com/Joule87/stackView-within-TableViewCell
Related
How can I make the tableview cell height dynamic,
I have 1 label and 1 image in the cell, My image height is constant of 70, label height depends on the api text, if my label text I large then the image view height so table view cell should adapt label height else image View.
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
below image just picking height of image
Making a UITableViewCells height dynamic based on its content is obtained by doing the following:
Make sure the content of your UITableViewCell is constrained such the dynamic content is pinned to both the top and bottom of the cell.
A contrived example cell:
class Cell: UITableViewCell {
let label = UILabel()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
// Allows your text to expand multiple lines
label.numberOfLines = 0
label.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(label)
// Constrains a UILabel to the edges of the UITableViewCells content view
label.topAnchor.constraint(equalTo: commentBox.topAnchor).isActive = true
label.bottomAnchor.constraint(equalTo: commentBox.bottomAnchor).isActive = true
label.leadingAnchor.constraint(equalTo: commentBox.leadingAnchor).isActive = true
label.trailingAnchor.constraint(equalTo: commentBox.trailingAnchor).isActive = true
}
}
As you have above, return UITableViewAutomaticDimension from func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat
To help your UITableView compute the dynamic height, its recommended to return an estimated size you think your cell is going to be.
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat
I have a table view and collection view to display image. Table view for vertical scroll and collection view for horizontal scroll. The image is displayed as full screen for the device. But when I scroll vertically half of previous cell is displayed and half of next cell is displayed. I used Table view constant is 0 to superview on all 4 constraints.
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return myTableView.frame.size.height;
}
To return the height of the cell but even this is causing same issue.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = self.myTableView.dequeueReusableCell(withIdentifier: "TableViewCell", for: indexPath) as! TableViewCell
return cell
}
This table view cell contains collection view
class TableViewCell: UITableViewCell,UICollectionViewDataSource,UICollectionViewDelegate,UICollectionViewDelegateFlowLayout{
func setUpCollectionView(){
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
collectionView = UICollectionView(frame: .zero,collectionViewLayout: layout)
collectionView?.register(VideoCollectionViewCell.self, forCellWithReuseIdentifier: VideoCollectionViewCell.identifier)
collectionView?.isPagingEnabled = true
collectionView?.dataSource = self
collectionView?.delegate = self
self.contentView.addSubview(collectionView!)
collectionView?.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
collectionView!.topAnchor.constraint(equalTo: contentView.topAnchor),
collectionView!.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
collectionView!.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
collectionView!.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
])
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: self.frame.width, height: self.frame.height)
}
}
This is my implementation, I don't want images to be displayed on half/half portion. I am trying to show it on full screen. Can anyone tell me why this is happening?
you need only UICollectionView display list images and set property scroll horizontal in UICollectionViewFlowLayout
myCollectionView.dataSource = self
myCollectionView.delegate = self
myCollectionView.contentMode = .scaleAspectFill
// scroll the images page by page, set property isPagingEnabled is true
myCollectionView.isPagingEnabled = true
let layout = UICollectionViewFlowLayout()
// set sroll horizontal here
layout.scrollDirection = .horizontal
myCollectionView.collectionViewLayout = layout
I've a tableview and I'd like to make the cells rounded. Every cell has a textfield inside.
On "cellForRowAtIndexPath" I wrote:
cell.layer.cornerRadius = cell.frame.size.height/2;
cell.layer.masksToBounds = true;
cell.clipsToBounds = true;
but I can't understand why, but on one line cells sometimes the corner radius doesn't work causing to be like "pointed".
P.s. the content mode is set to be Aspect fit
Can someone help me?
I advise you to add a comtainer view to your cell controller, you will be able to manage the cell padding better with Auto Layout:
let containerView = UIView()
set property of it:
containerView.backgroundColor = .red
containerView.clipsToBounds = true
containerView.translatesAutoresizingMaskIntoConstraints = false
Now add UIView and set constraints:
addSubview(containerView)
containerView.topAnchor.constraint(equalTo: topAnchor, constant: 0).isActive = true //change the constant to add padding (right and bottom are negative value)
containerView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 0).isActive = true
containerView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 0).isActive = true
containerView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: 0).isActive = true
After that in cellForRowAt set yuor cell backgroundColor to clear
cell.backgoundColor = .clear
cell.containerView.layer.cornerRadius = cell.bounds.size.height/2;
This is the result with your corner radius applied to containerView:
Or set your tableView and cell like this:
class DummyViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
let tableView = UITableView()
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.register(MyCell.self, forCellReuseIdentifier: "cellId")
view.addSubview(tableView)
tableView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
10
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
300
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellId", for: indexPath) as! MyCell
cell.layer.cornerRadius = cell.frame.size.height / 2;
return cell
}
}
Your cell:
class MyCell: UITableViewCell {
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
backgroundColor = .blue
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
This is the result with your corner radius applied:
You are relying on the frame property of the cell, which is actually being managed by UIKit and the table view. In the place you refer to the frame, it might not actually be what you expect because it hasn’t been displayed yet. (For example if your cells have dynamic heights and you are recycling an old cell with a different height then it will still have the old frame.)
You could try rounding inside the cell’s implementation code itself. If you override the bounds property and use a didSet property observer you can set the corner radius there (use bounds instead of frame). This way even if the bounds changes 100 times you will always have the corner radius you expect.
As andym said, I'm relying on the frame property that haven't been defined yet.
The best way to accomplish my goal is to put my code inside willDisplayCell method like this:
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath{
cell.layer.cornerRadius = cell.layer.bounds.size.height/2;
}
clipsToBounds needs to be set to true on the superview, which is a tableview in your case.
You should configure the content view instead a cell it self
cell.contentView.layer.cornerRadius = 5 // or the value that you want
cell.contentView.layer.masksToBounds = true
I have a viewController with the following (static) tableView:
class viewController: UIViewController, UITableViewDelegate {
private let tableView: UITableView = {
let tv = UITableView()
tv.separatorStyle = .singleLine
tv.allowsSelection = true
tv.isScrollEnabled = false
return tv
}()
private let tableData = ["row1", "row2", "row3", "row4"]
override func viewDidLoad() {
super.viewDidLoad()
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.delegate = self
tableView.tableFooterView = UIView()
view.addSubview(tableView)
NSLayoutConstraints.activate([
tableView.centerXAnchor.constraints(equalTo: view.safeAreaLayoutGuide.centerXAnchor),
tableView.centerYAnchor.constraints(equalTo: view.safeAreaLayoutGuide.centerYAnchor)
)]
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .subtitle, reuseIdentifier: "cell")
cell.textLabel!.text = tableData[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return tableData.count
}
}
When I run the app, this viewController shows a blank screen. I know that the way I am setting up the tableview's constraints is the problem because when I set up the tableView using topAnchor, bottomAnchor, leftAnchor, and rightAnchor (and with some other tweaking) the tableview appears. Any idea why the app is behaving this way?
Your table view is probably there, and centered, but you didn't define a size, so it's probably being set to zero width and height, that's why you don't see it.
You can fix this by setting a constraint on it's width and height, either to a constant or related to it's superview, depending on what you want.
The problem is this is NOT a static table view. If it were, you would not have implemented cellForRowAt. It is a normal table view and it needs a data source and delegate. Plus it needs a height and a width.
I have a UITableView that UITableViewCell has a dynamic UILabel which will be added based on data i get from the database.
I'm using something like
let testing = ["A", "B", "C", "D"] // This array is dynamic data
self.dictionaryTableView.estimatedRowHeight = 44
self.dictionaryTableView.rowHeight = UITableViewAutomaticDimension
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "vocabCell", for: indexPath)
var y = 0
for var i in 0..<self.testing.count {
let lbl = UILabel(frame: CGRect(x: 0, y: y, width: 50, height: 25))
lbl.text = self.testing[i]
lbl.numberOfLines = 0
cell.addSubview(lbl)
y += 20
}
return cell
}
But UITableViewCell height does not stretch automatically to display all content cell. Please help
Here is the result
EDIT I added constraint for the uilabel in UITableView cellForRowAtIndexPath, the constraints are working (I knew it when I expend the cell height), but cell height not automaticly strech out
var bottomAnchor: NSLayoutYAxisAnchor = NSLayoutYAxisAnchor()
for var i in 0..<self.testing.count {
let lbl = UILabel()
lbl.text = self.testing[i]
lbl.numberOfLines = 0
lbl.translatesAutoresizingMaskIntoConstraints = false
cell.addSubview(lbl)
lbl.leftAnchor.constraint(equalTo: cell.leftAnchor, constant: 16).isActive = true
lbl.widthAnchor.constraint(equalTo: cell.widthAnchor).isActive = true
lbl.heightAnchor.constraint(equalToConstant: 25).isActive = true
if i == 0 {
lbl.topAnchor.constraint(equalTo: cell.topAnchor).isActive = true
} else {
lbl.topAnchor.constraint(equalTo: bottomAnchor).isActive = true
}
bottomAnchor = lbl.bottomAnchor
}
return cell
Many thanks
There are two things that you need to fix in your implementation:
Create the layout of your cell before dequeueing it in tableView(_:, cellForRowAt:).
For efficiency reasons, table views reuse the same cells over and over again when the user scrolls. That's why you use this weird "dequeuing" function rather than simply instantiating a new cell.
let cell = tableView.dequeueReusableCell(withIdentifier: "vocabCell", for: indexPath)
will always try to return a used cell that just scrolled out of the view. Only if there are no recycled cells available (for example when dequeueing the first couple of cells) the table view will create new instances of a cell.
The recycling of cells is the very reason why you should never create your cell's layout inside tableView(_:, cellForRowAt:), for example by adding a label. When the cell is being reused, another label will be added on top of the label that you added before and when it's being reused a second time, you'll end up with three overlapping labels etc.
The same applies to constraints. When you add constraints to a cell in tableView(_:, cellForRowAt:) without removing existing ones, more and more constraints will be added while the user is scrolling, most likely resulting in serious constraint conflicts.
Instead, setup your cell's layout before dequeueing it. There are several ways to achieve this:
If you use a UITableViewController inside a storyboard, you can create dynamic prototypes and lay out the cells directly in the storyboard. You could, for example, drag a label to a prototype cell there and create an outlet for it in a custom UITableViewCell subclass.
You can create a XIB file for your cell, open it in Interface Builder and create your layout there. Again, you need to create a UITableViewCell subclass with the appropriate outlets and associate it with your XIB file.
You can create a UITableViewCell subclass and set up your layout purely in code, for example inside the cell's initializer.
You need to use Auto Layout.
If you create your layout in Interface Builder, you just need to add the necessary constraints and you're good to go. If you create your layout in code, you need to set the translatesAutoresizingMaskIntoConstraints property to false for all views that you wish to constrain, for example:
lbl.translatesAutoresizingMaskIntoConstraints = false
In your particular layout, you need to constrain the label at the left, right, top and bottom with the corresponding edges of your cell's contentView.
If you don't know how to do that, please read Apple's Auto Layout Guide. (It's usually a better idea to do this in Interface Builder rather than in code.)
A very detailed description of how to use "Auto Layout in UITableView for dynamic cell layouts & variable row heights" can be found here.
used this code.
func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
You can use UITableView section to render the data instead of adding the view programmatically. Here's the example:
class ViewController: UITableViewController {
private var dataSectionOne = ["Data 1", "Data 2"]
// This can be your dynamic data.
// Once the data changed, called tableView.reloadData() to update the view.
private var dataSectionTwo = ["A", "B", "C"]
override func viewDidLoad() {
super.viewDidLoad()
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 44
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 2
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0 {
return dataSectionOne.count
} else if section == 1 {
return dataSectionTwo.count
}
return 0
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
if indexPath.section == 0 {
cell.textLabel?.text = dataSectionOne[indexPath.row]
} else if indexPath.section == 1 {
cell.textLabel?.text = dataSectionTwo[indexPath.row]
}
return cell
}
}
The results:
I found the answers here
I added a constraint to my UITableViewCell bottom with the following code and it's working, but I don't know what exactly this line doing (I don't know the parameters too)
Could anyone help me explain this code
let bottomSpaceConstraint: NSLayoutConstraint = NSLayoutConstraint(item: lbl, attribute: NSLayoutAttribute.bottomMargin, relatedBy: NSLayoutRelation.equal, toItem: cell.contentView, attribute: NSLayoutAttribute.bottomMargin, multiplier: 1, constant: -8)
cell.contentView.addConstraint(bottomSpaceConstraint)