Async height change for UITableViewCell - ios

In one of my projects, I need to change the height of UIImageView in UITableViewCell according to image size, but the problem is that sometimes I have to do this after the cell is already shown.
So, my current solution works like a charm if I know all the image sizes beforehand, but if I'm trying to calculate this with some delay – it's completely broken (especially with scrolling but it's broken even without it).
I made the example project to illustrate this. There is no async downloading, but I'm trying to dynamically change the height of UIImageView after some delay (1s). The height depends on UIImageView, so every next UIImageView should be slightly higher (10 pixels) than previous one. Also, I have a UILabel, constrained to UIImageView.
It looks like that (UIImageViews are the red ones)
If I'm trying to do this async, it looks like this, all the UILabels are really broken here.
and this is one after the scroll (async too):
What am I doing wrong here? I've read several threads about dynamic heights, but none of the solutions worked for me yet.
My code is fairly simple:
func addTableView() {
tableView = UITableView()
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.dataSource = self
tableView.delegate = self
tableView.separatorStyle = .none
tableView.estimatedRowHeight = 100
tableView.rowHeight = UITableView.automaticDimension
tableView.backgroundColor = .black
tableView.register(DynamicCell.self, forCellReuseIdentifier: "dynamicCell")
view.addSubview(tableView)
tableView.topAnchor.constraint(equalTo: view.topAnchor, constant: 0).isActive = true
tableView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 0).isActive = true
tableView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: 0).isActive = true
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0).isActive = true
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "dynamicCell", for: indexPath) as! DynamicCell
cell.message = messageArray[indexPath.row]
cell.backgroundColor = .clear
cell.selectionStyle = .none
cell.buildCell()
return cell
}
DynamicCell.swift (delegate is doing nothing right now):
var backView: UIView!
var label: UILabel!
var picView: UIImageView!
var message: DMessage?
var picViewHeight: NSLayoutConstraint!
var delegate: RefreshCellDelegate?
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
backView = UIView()
backView.translatesAutoresizingMaskIntoConstraints = false
backView.backgroundColor = .white
backView.clipsToBounds = true
backView.layer.cornerRadius = 8.0
self.addSubview(backView)
label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.textAlignment = .left
label.textColor = .black
label.numberOfLines = 0
backView.addSubview(label)
picView = UIImageView()
picView.translatesAutoresizingMaskIntoConstraints = false
picView.clipsToBounds = true
picView.backgroundColor = .red
backView.addSubview(picView)
addMainConstraints()
}
func addMainConstraints() {
backView.leftAnchor.constraint(equalTo: self.leftAnchor, constant: 8).isActive = true
backView.rightAnchor.constraint(equalTo: self.rightAnchor, constant: -32).isActive = true
backView.topAnchor.constraint(equalTo: self.topAnchor, constant: 4).isActive = true
backView.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -4).isActive = true
picView.topAnchor.constraint(equalTo: backView.topAnchor, constant: 0).isActive = true
picView.leftAnchor.constraint(equalTo: backView.leftAnchor, constant: 0).isActive = true
picView.rightAnchor.constraint(equalTo: backView.rightAnchor, constant: 0).isActive = true
label.topAnchor.constraint(equalTo: picView.bottomAnchor, constant: 0).isActive = true
label.leftAnchor.constraint(equalTo: backView.leftAnchor, constant: 8).isActive = true
label.rightAnchor.constraint(equalTo: backView.rightAnchor, constant: -8).isActive = true
label.bottomAnchor.constraint(equalTo: backView.bottomAnchor, constant: -4).isActive = true
picViewHeight = NSLayoutConstraint(item: picView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 100)
picViewHeight.priority = UILayoutPriority(999)
picViewHeight.isActive = true
}
override func prepareForReuse() {
picViewHeight.constant = 0
//picViewHeight.constant = 0
}
func buildCell() {
guard let message = message else {return}
label.attributedText = NSAttributedString(string: message.text)
changeHeightWithDelay()
//changeHeightWithoutDelay()
}
func changeHeightWithoutDelay() {
if let nh = self.message?.imageHeight {
self.picViewHeight.constant = nh
self.delegate?.refreshCell(cell: self)
}
}
func changeHeightWithDelay() {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
if let nh = self.message?.imageHeight {
self.picViewHeight.constant = nh
self.delegate?.refreshCell(cell: self)
}
}
}

putting this as an answer.
one thing I noticed, when you are playing around with cell, it's always better to use the contentView instead of directly using self. ie self.contentView.addSubview(). what does refreshcell function do? have you tried marking it as needsSetDisplay so in the next draw cycle it will be updated? have you tried calling layoutIfNeeded?
To explain a bit further, your view has already been 'rendered' the moment you want to change the height/width of your view you need to inform it that there's an update. this happens when you mark the view as setNeedsDisplay and in the next render cycle it will be updated
more info on apple's documentation here
You can use this method or the setNeedsDisplay(_:) to notify the system that your view’s contents need to be redrawn. This method makes a note of the request and returns immediately. The view is not actually redrawn until the next drawing cycle, at which point all invalidated views are updated.

Related

rowHeight of cell not adjusting to content increase

I use below code to add constraints programatically, no story board used
cell.descriptionDetail.leadingAnchor.constraint(equalTo: cell.contentView.leadingAnchor, constant: 15).isActive = true
cell.descriptionDetail.topAnchor.constraint(greaterThanOrEqualTo: cell.contentView.topAnchor, constant: 20).isActive = true
cell.descriptionDetail.trailingAnchor.constraint(equalTo: cell.contentView.trailingAnchor, constant: -20).isActive = true
cell.descriptionDetail.bottomAnchor.constraint(greaterThanOrEqualTo: cell.contentView.bottomAnchor, constant: 10).isActive = true
Now i use this in viewDidLaod
detailTableView.rowHeight = UITableView.automaticDimension
detailTableView.rowHeight = 40
even if i remove height constraints on all cell the row height does. not adjust, if i remove
detailTableView.rowHeight = 40
and add detailTableView.estimatedRowHeight = 40, i get error, currently this is what happens , content overlapping cells
UPDATE MY ENTIRE CODE OF FILE WHERE THE CELL IS BEING CREATED AND CONSTRAINED
let cell = detailTableView.dequeueReusableCell(withIdentifier: String(describing: TextOnlyCell.self), for: indexPath) as! TextOnlyCell
view.addSubview(cell.descriptionDetail)
view.addSubview(cell)
cell.descriptionDetail.numberOfLines = 0
cell.descriptionDetail.lineBreakMode = .byWordWrapping
cell.descriptionDetail.translatesAutoresizingMaskIntoConstraints = false
cell.descriptionDetail.widthAnchor.constraint(greaterThanOrEqualToConstant: 100).isActive = true
cell.descriptionDetail.heightAnchor.constraint(greaterThanOrEqualToConstant: 20).isActive = true
///Constraints
cell.descriptionDetail.leadingAnchor.constraint(equalTo: cell.leadingAnchor, constant: 15).isActive = true
cell.descriptionDetail.topAnchor.constraint(greaterThanOrEqualTo: cell.topAnchor, constant: 10).isActive = true
cell.descriptionDetail.trailingAnchor.constraint(equalTo: cell.trailingAnchor, constant: -5).isActive = true
cell.descriptionDetail.bottomAnchor.constraint(greaterThanOrEqualTo: cell.bottomAnchor, constant: 10).isActive = true
cell.descriptionDetail.text = restaurant.description
return cell
Add this method in your class... and be sure that your constraints are attached with top and bottom ... to help automaticDimension to calculate height
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UITableView.automaticDimension
}

Dynamic Constraint for UIView & UICollection

On the top of my screen will show one of two UIViews.
One is the minimized version and the other is the maximized verison.
The minimized view is 30 in height while the maximized version is 250.
Underneath this there is a UICollectionView.
I want it so that when the minimized version of the UIView is showing, the UICollectionView's topAnchor will be connected to the UIViews bottomAnchor.
When I click on each of the UIViews they will become hidden and make the other one visible.
Here are some screenshots to help visualize:
Default
Minimized
Attempt to maximize
So when I show the maximized UIView I want the UICollectionViews topAnchor to be connected to that ones bottomAnchor and so forth.
Currently everything is working except the proper resizing of the UICollectionView.
It will resize up when I minimize, but will not resize down when I maximize.
My viewDidLoad calls both of these functions:
private func configureMaxView() {
view.addSubview(maxView)
maxView.layer.cornerRadius = 18
maxView.backgroundColor = .secondarySystemBackground
maxView.translatesAutoresizingMaskIntoConstraints = false
maxView.isHidden = isMaxViewHidden
let gesture = UITapGestureRecognizer(target: self, action: #selector (self.minimizeAction (_:)))
self.maxView.addGestureRecognizer(gesture)
let padding: CGFloat = 20
NSLayoutConstraint.activate([
maxView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
maxView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: padding),
maxView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -padding),
maxView.heightAnchor.constraint(equalToConstant: 250),
])
}
private func configureMinView() {
view.addSubview(minView)
minView.layer.cornerRadius = 9
minView.backgroundColor = .secondarySystemBackground
minView.translatesAutoresizingMaskIntoConstraints = false
minView.isHidden = !isMaxViewHidden
let gesture = UITapGestureRecognizer(target: self, action: #selector (self.expandAction (_:)))
self.minView.addGestureRecognizer(gesture)
let padding: CGFloat = 20
NSLayoutConstraint.activate([
minView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
minView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: padding),
minView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -padding),
minView.heightAnchor.constraint(equalToConstant: 30),
])
}
Here are the functions that are called when you click on one of the UIViews:
#objc func minimizeAction(_ sender:UITapGestureRecognizer){
minView.isHidden = false
maxView.isHidden = true
// without this call the hiding and unhiding works fine - But the collectionview won't move
resizeCollectionView()
isMaxViewHidden = !isMaxViewHidden
}
#objc func expandAction(_ sender:UITapGestureRecognizer){
minView.isHidden = true
maxView.isHidden = false
// without this call the hiding and unhiding works fine - But the collectionview won't move
resizeCollectionView()
isMaxViewHidden = !isMaxViewHidden
}
My viewDidLoad will also call this after setting up the uiviews in order to set up the collectionView:
private func configureCollectionView() {
collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: UIHelper.createThreeColumnFlowLayout(in: view))
view.addSubview(collectionView)
collectionView.delegate = self
collectionView.backgroundColor = .systemBackground
collectionView.register(CustomCell.self, forCellWithReuseIdentifier: CustomCell.resuseID)
collectionView.translatesAutoresizingMaskIntoConstraints = false
let bottomAnchor = isMaxViewHidden ? minView.bottomAnchor : maxView.bottomAnchor
NSLayoutConstraint.activate([
collectionView.topAnchor.constraint(equalTo: bottomAnchor),
collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
])
}
Here is the function I call from each uiview click handler in an attempt to update the constraint:
private func resizeCollectionView() {
let bottomAnchor = isMaxViewHidden ? maxView.bottomAnchor: minView.bottomAnchor
NSLayoutConstraint.activate([
collectionView.topAnchor.constraint(equalTo: bottomAnchor),
])
// This does move the collection view down, but seems hardcoded and bad
//collectionView.frame.origin.y = 650
}
Error:
"<NSLayoutConstraint:0x600000c4ed50 UIView:0x7ffd60c135e0.top == UILayoutGuide:0x6000016f89a0'UIViewSafeAreaLayoutGuide'.top (active)>",
"<NSLayoutConstraint:0x600000c4fa20 UIView:0x7ffd60c135e0.height == 250 (active)>",
"<NSLayoutConstraint:0x600000c4bed0 UIView:0x7ffd60c13750.top == UILayoutGuide:0x6000016f89a0'UIViewSafeAreaLayoutGuide'.top (active)>",
"<NSLayoutConstraint:0x600000c3c000 UIView:0x7ffd60c13750.height == 30 (active)>",
"<NSLayoutConstraint:0x600000c3dbd0 V:[UIView:0x7ffd60c135e0]-(0)-[UICollectionView:0x7ffd62819600] (active)>",
"<NSLayoutConstraint:0x600000c20cd0 V:[UIView:0x7ffd60c13750]-(0)-[UICollectionView:0x7ffd62819600] (active)>"
)
Will attempt to recover by breaking constraint
"<NSLayoutConstraint:0x600000c4ed50 UIView:0x7ffd60c135e0.top == UILayoutGuide:0x6000016f89a0'UIViewSafeAreaLayoutGuide'.top (active)>",
"<NSLayoutConstraint:0x600000c4fa20 UIView:0x7ffd60c135e0.height == 250 (active)>",
"<NSLayoutConstraint:0x600000c4bed0 UIView:0x7ffd60c13750.top == UILayoutGuide:0x6000016f89a0'UIViewSafeAreaLayoutGuide'.top (active)>",
"<NSLayoutConstraint:0x600000c3c000 UIView:0x7ffd60c13750.height == 30 (active)>",
What's happening in here is that, even though the view is being hidden, the constraints are still there, so assigning numerous top constraints on a single view will result to a conflict.
If you want to have a sort of dynamic constraint, just modify the constraint's constant or multiplier values. That way, you wont need to worry about constraint objects.
Wilson's solution is right. What I can just add from that is just have a single containerView containing your min and max views. and just toggle the visibility of your min and max view inside your containerView.
Here's my (simple) take on it:
everything is called in viewDidLoad() as you do, and
you also need to define a constraint object.
private var containerViewHeight: NSLayoutConstraint!
private func configureContainerView() {
containerView = UIView()
containerView.backgroundColor = .systemGreen
containerView.translatesAutoresizingMaskIntoConstraints = false
containerView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(containerViewTapped)))
view.addSubview(containerView)
containerViewHeight = containerView.heightAnchor.constraint(equalToConstant: 250)
containerViewHeight.isActive = true
NSLayoutConstraint.activate([
containerView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
containerView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
containerView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
])
}
#objc private func containerViewTapped() {
isViewTapped.toggle()
containerViewHeight.constant = isViewTapped ? 30 : 250
innerMinView.isHidden = !isViewTapped
innerMaxView.isHidden = isViewTapped
}
private func configureCollectionView() {
collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: UICollectionViewFlowLayout())
view.addSubview(collectionView)
collectionView.backgroundColor = .systemYellow
collectionView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
collectionView.topAnchor.constraint(equalTo: containerView.bottomAnchor),
collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
])
}
private func configureInnerMinView() {
innerMinView = UIView()
innerMinView.backgroundColor = .systemRed
innerMinView.translatesAutoresizingMaskIntoConstraints = false
containerView.addSubview(innerMinView)
NSLayoutConstraint.activate([
innerMinView.widthAnchor.constraint(equalTo: containerView.widthAnchor, multiplier: 0.8),
innerMinView.heightAnchor.constraint(equalTo: containerView.heightAnchor, multiplier: 0.8),
innerMinView.centerXAnchor.constraint(equalTo: containerView.centerXAnchor),
innerMinView.centerYAnchor.constraint(equalTo: containerView.centerYAnchor)
])
}
private func configureInnerMaxView() {
innerMaxView = UIView()
innerMaxView.backgroundColor = .systemBlue
innerMaxView.translatesAutoresizingMaskIntoConstraints = false
containerView.addSubview(innerMaxView)
NSLayoutConstraint.activate([
innerMaxView.widthAnchor.constraint(equalTo: containerView.widthAnchor, multiplier: 0.8),
innerMaxView.heightAnchor.constraint(equalTo: containerView.heightAnchor, multiplier: 0.8),
innerMaxView.centerXAnchor.constraint(equalTo: containerView.centerXAnchor),
innerMaxView.centerYAnchor.constraint(equalTo: containerView.centerYAnchor)
])
}
side note: btw. I think I know what project this is. :) SA-GH.F
You're activating conflicting constraints on top of each other. If you want to go between a minimized and maximized view, just change the constant value on the height constraint itself.
I whipped up a sample MyViewController in playgrounds with 2 views, the first of which's height and color changes when you tap it. myView is like your collapsable view, and myOtherView is like your collectionView.
Let me know if you have any questions!
class MyViewController : UIViewController {
private enum ViewState {
case min
case max
var height: CGFloat {
switch self {
case .min:
return 30
case .max:
return 250
}
}
var color: UIColor {
switch self {
case .min:
return .red
case .max:
return .green
}
}
}
private var myViewState: ViewState = .min {
didSet {
viewStateToggled()
}
}
private let myView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
private let myOtherView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .yellow
return view
}()
private lazy var myViewHeightConstraint = NSLayoutConstraint(
item: myView,
attribute: .height,
relatedBy: .equal,
toItem: nil,
attribute: .notAnAttribute,
multiplier: 1,
constant: myViewState.height
)
override func loadView() {
let view = UIView()
view.backgroundColor = .white
self.view = view
}
override func viewDidLoad() {
super.viewDidLoad()
configureMyViews()
}
private func configureMyViews() {
view.addSubview(myView)
myView.backgroundColor = myViewState.color
NSLayoutConstraint.activate([
myView.topAnchor.constraint(equalTo: view.topAnchor),
myView.leftAnchor.constraint(equalTo: view.leftAnchor),
myView.rightAnchor.constraint(equalTo: view.rightAnchor),
myViewHeightConstraint
])
let tap = UITapGestureRecognizer(target: self, action: #selector(self.handleTap(_:)))
myView.addGestureRecognizer(tap)
view.addSubview(myOtherView)
NSLayoutConstraint.activate([
myOtherView.topAnchor.constraint(equalTo: myView.bottomAnchor),
myOtherView.leftAnchor.constraint(equalTo: view.leftAnchor),
myOtherView.rightAnchor.constraint(equalTo: view.rightAnchor),
myOtherView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
])
}
private func viewStateToggled() {
myViewHeightConstraint.constant = myViewState.height
UIView.animate(withDuration: 0.4) {
self.myView.backgroundColor = self.myViewState.color
self.view.layoutSubviews()
}
}
#objc func handleTap(_ sender: UITapGestureRecognizer? = nil) {
myViewState = myViewState == .min ? .max : .min
}
}
I was wondering if you need a call to update your layout. Maybe called from the resizeCollectionView()?
let bottomAnchorMinContraint: NSConstraint = collectionView.topAnchor.constraint(equalTo: minView.bottomAnchor)
let bottomAnchorMaxContraint: NSConstraint = collectionView.topAnchor.constraint(equalTo: maxView.bottomAnchor)
if isMaxViewHidden {
bottomAnchorMinContraint.isActive = true
bottomAnchorMaxContraint.isActive = false
}
if !isMaxViewHidden {
bottomAnchorMinContraint.isActive = false
bottomAnchorMaxContraint.isActive = true
}
view.layoutIfNeeded()

UITableView Cell Label truncates on scrolling

i have a tableview which displays a custom created cell. The issue is that the label in my cell gets truncated everytime i scroll. Even if i give my label a big width it still truncates.
This is my cellForRowAt:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: cellID, for: indexPath) as? CoronaStatisticsCell else { return UITableViewCell() }
let currentCountry = searchedCountry == nil ? countrySections[indexPath.section][indexPath.row] : searchedCountry![indexPath.row]
cell.configure(country: currentCountry)
cell.preservesSuperviewLayoutMargins = false
cell.separatorInset = UIEdgeInsets.zero
cell.layoutMargins = UIEdgeInsets.zero
return cell
}
This is my label which gets truncated in my cell:
private lazy var casesStaticticLbl: UILabel = {
var lbl = UILabel()
lbl.font = UIFont(name: "AvenirNext-Bold", size: 20)
lbl.textAlignment = .left
return lbl
}()
In the setSelected method i set my views:
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
layer.cornerRadius = 10
countryLabel.translatesAutoresizingMaskIntoConstraints = false
addSubview(countryLabel)
NSLayoutConstraint.activate([
countryLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 10),
countryLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -10),
countryLabel.topAnchor.constraint(equalTo: topAnchor, constant: 10),
countryLabel.heightAnchor.constraint(equalToConstant: 20)
])
var casesSV = UIStackView(arrangedSubviews: [casesLbl, casesStaticticLbl])
var deathsSV = UIStackView(arrangedSubviews: [deathLbl, deathStaticticLbl])
var recoveredSV = UIStackView(arrangedSubviews: [recoveredLbl, recoveredStaticticLbl])
for sv in [casesSV, deathsSV, recoveredSV] {
sv.axis = .vertical
sv.translatesAutoresizingMaskIntoConstraints = false
sv.spacing = 10
sv.alignment = .leading
addSubview(sv)
}
NSLayoutConstraint.activate([
casesSV.leadingAnchor.constraint(equalTo: countryLabel.leadingAnchor),
casesSV.topAnchor.constraint(equalTo: countryLabel.bottomAnchor, constant: 5),
casesSV.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -20),
deathsSV.centerXAnchor.constraint(equalTo: centerXAnchor),
deathsSV.topAnchor.constraint(equalTo: countryLabel.bottomAnchor, constant: 5),
deathsSV.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -20),
recoveredSV.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -30),
recoveredSV.topAnchor.constraint(equalTo: countryLabel.bottomAnchor, constant: 5),
recoveredSV.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -20),
])
}
This is the initial label
after scrolling:
I think theres an issue with my stackviews because if i only add the label to the cell nothing truncates.
Thanks in advance
I'm answering your question based on the given contexts.
First off, Abhishek's comment is kinda agreeable. I would say you can definitely play with the constraints in the setSelected method, BUT not adding subviews. This merely comes from my personal preferences.
The only time you add subviews and set their constraints is in your init method override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?), but to reiterate, you can definitely toggle constraints in your setSelected AND also in your cellForRow.
To address your problem, given your current contexts in your question, you can add an explicit width as constraint. If you are stacking labels in a stackView, you must provide at least one explicit height or width constraint.
Lastly, take heed ⚠️ that Apple (App Store) and Google Play Store won't accept your app about
COVID-19 🦠
UNLESS you are from health organization. This is to prevent misinformation.

how to get a round UIbutton in tableviewcell in swift?

class CustomTableViewCell: UITableViewCell {
let nameLbl: UILabel = UILabel()
let profileBtn: UIButton = UIButton()
let aboutLbl: UILabel = UILabel()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
contentView.addSubview(profileBtn)
contentView.addSubview(nameLbl)
contentView.addSubview(aboutLbl)
nameLbl.translatesAutoresizingMaskIntoConstraints = false
profileBtn.translatesAutoresizingMaskIntoConstraints = false
aboutLbl.translatesAutoresizingMaskIntoConstraints = false
profileBtn.backgroundColor = UIColor.red
nameLbl.font = UIFont(name: "Arial", size: 16)
aboutLbl.font = UIFont(name: "Arial", size: 16)
profileBtn.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
profileBtn.leftAnchor.constraint(equalTo: leftAnchor, constant: 20).isActive = true
profileBtn.widthAnchor.constraint(equalToConstant: 40).isActive = true
profileBtn.heightAnchor.constraint(equalToConstant: 40).isActive = true
self.profileBtn.layer.masksToBounds = true
self.profileBtn.layer.cornerRadius = CGFloat(roundf(Float(self.profileBtn.frame.size.width/2.0)))
nameLbl.topAnchor.constraint(equalTo: topAnchor, constant: 30).isActive = true
nameLbl.leftAnchor.constraint(equalTo: leftAnchor, constant: 70).isActive = true
nameLbl.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20).isActive = true
aboutLbl.topAnchor.constraint(equalTo: nameLbl.bottomAnchor, constant: 10).isActive = true
aboutLbl.leftAnchor.constraint(equalTo: leftAnchor, constant: 70).isActive = true
aboutLbl.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20).isActive = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
i want the profile button inside the cell to have a round design.but ebven setting the corener radius and marskstobounds to true i am getting a square button. what am i doing wrong any help is apreciated. thanks in advance.
You are calculating the corner radius when the profile button hasn't been laid out yet. This means the width of the profile button will be zero rendering the corner radius the same. Move the line that you set the corner radius to an overriding method of layoutSubviews – this will ensure the views and subsequent sizes have been laid out in order for you to set the appropriate corner radius.
override func layoutSubviews() {
super.layoutSubviews()
profileBtn.layer.cornerRadius = profileBtn.frame.width / 2
}
Here is my solution:
override func layoutSubviews() {
super.layoutSubviews()
self.makeItCircle()
}
func makeItCircle() {
self.yourbutton.layer.masksToBounds = true
self.yourbutton.layer.cornerRadius = CGFloat(roundf(Float(self.yourbutton.frame.size.width/2.0)))
}
self.imageView.layer.masksToBounds = true //- in main
When you initialise the cell, the button does not have any frame. So self.profileBtn.layer.cornerRadius = CGFloat(roundf(Float(self.profileBtn.frame.size.width/2.0))) results in cornerRadius to be 0.
Since you are giving 40 constant width and height to the button, you can simply do this:
self.profileBtn.layer.cornerRadius = 20.0
Also make sure to set the button to clip the bounds:
self.profileBtn.clipsToBounds = true

UIView clipsToBounds doesn't work

I have a view controller with XIB, with a view (contentView) inside. This view contains some buttons.
The content view has round corners and clips to bounds, but it doesn't respect the clipping rect. I set the corner radius and the clipsToBounds in the viewDidLoad of the view controller.
Here you can see the reveal screenshot that shows that the view is composed in the correct way, but on simulator and device clipping bounds are not respected.
Anybody can please help me to understand what happen.
The app is targeted to iOS 10 and 11, and both have the same issue.
I found a solution, I move the clipsToBound in the viewDidLayoutSubviews instead viewDidLoad and now works
override func viewDidLoad() {
super.viewDidLoad()
contentView.layer.cornerRadius = Dimensions.CornerRaius
contentView.dropShadow()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
contentView.clipsToBounds = true
}
I defined my view (UIView in my case) like that:
fileprivate let backView: UIView = {
let view = UIView()
view.clipsToBounds = true
view.layer.masksToBounds = false
view.layer.cornerRadius = 10
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
The image:
fileprivate let imgView: UIImageView = {
let iv = UIImageView()
iv.translatesAutoresizingMaskIntoConstraints = false
return iv
}()
In my case I defined these elements in a custom table view cell:
class customCell: UITableViewCell {
Although I set "clipsToBounds = true" in the definition of background view, not clip the image.
But, if I set "clipsToBounds = true" later, it clips the image.
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
backView.addSubview(imgView)
contentView.addSubview(backView)
imgView.topAnchor.constraint(equalTo: backView.topAnchor, constant: 0).isActive = true
imgView.leadingAnchor.constraint(equalTo: backView.leadingAnchor, constant: 0).isActive = true
imgView.trailingAnchor.constraint(equalTo: backView.trailingAnchor, constant: 0).isActive = true
imgView.heightAnchor.constraint(equalTo: imgView.widthAnchor, multiplier: 1/4).isActive = true
backView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 0).isActive = true
backView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 15).isActive = true
backView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -15).isActive = true
backView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: 0).isActive = true
And inside "init" method:
backView.clipsToBounds = true

Resources