UITableViewCell style goes away after scrolling down - swift

I am trying to figure out why my custom styling for table cells disappears after scrolling down in a table view and them back up. What do I need to do to have the style persist?
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell : CustomTrendingCell = trendingTableView.dequeueReusableCell(withIdentifier: "cell") as! CustomTrendingCell
cell.selectionStyle = .none
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(searchForTrendingTerm(sender:)))
cell.iconView_search.isUserInteractionEnabled = true
cell.iconView_search.tag = indexPath.row
cell.iconView_search.addGestureRecognizer(tapGesture)
cell.trendingLabel.text = trendingSearchTerms[indexPath.row]
cell.elevate(elevation: 4.0) //where my style is being set
return cell
}
extension UIView {
func elevate(elevation: Double) {
self.layer.masksToBounds = false
self.layer.shadowColor = UIColor.black.cgColor
self.layer.shadowOffset = CGSize(width: 0, height: elevation)
self.layer.shadowRadius = abs(CGFloat(elevation))
self.layer.shadowOpacity = 0.4
}
}
The top 2 items in the screenshot below have been scrolled down and up. The drop shadow styling has been removed. The bottom 2 have the correct styling and have not been scrolled down.
Screenshot example

One possible solution here is to explicitly specify the zPosition of each cell's layer.
You would want to ensure that the upper cell has the higher position so that it's content (shadow) lies over the lower cell.
In your cell for row at function add:
cell.layer.zPosition = CGFloat(numberOfRows - indexPath.row)

Related

Setting up different layouts for one cell, depending on its content

In an UITableViewCell, I have an UIView container, which contains a UIImageView on top and an UILabel on the bottom. When the UILabel does not contain text, I want to remove the UILabel and resize the UImageView to the bottom of my UIView container.
In my TableViewCell, I have this:
let x = imageViewPostPreview.bottomAnchor.constraint(equalTo: viewPreviewOverlay.topAnchor, constant: 0)
if postText == "" {
x.isActive = true
imageViewPostPreview.layer.maskedCorners = [.layerMaxXMinYCorner, .layerMinXMinYCorner, .layerMaxXMaxYCorner, .layerMinXMaxYCorner]
self.layoutSubviews()
self.layoutIfNeeded()
} else if postText != "" {
x.isActive = false
imageViewPostPreview.layer.maskedCorners = [.layerMaxXMinYCorner, .layerMinXMinYCorner]
self.layoutSubviews()
self.layoutIfNeeded()
}
}
This piece of code works on when the initial UITableViewCell is loaded, but when I scroll my UITableView, the cell loses the initial layout.
How do I setup the layout correctly, so the layout is kept on scroll?
Edit:
Initial cell, which is correct:
After scrolling, incorrect layout:
Add this to prepareToReuse method
override func prepareForReuse() {
super.prepareForReuse()
let x = imageViewPostPreview.bottomAnchor.constraint(equalTo: viewPreviewOverlay.topAnchor, constant: 0)
if postText.isEmpty {
x.isActive = true
imageViewPostPreview.layer.maskedCorners = [.layerMaxXMinYCorner, .layerMinXMinYCorner, .layerMaxXMaxYCorner, .layerMinXMaxYCorner]
} else {
x.isActive = false
imageViewPostPreview.layer.maskedCorners = [.layerMaxXMinYCorner, .layerMinXMinYCorner]
}
self.layoutIfNeeded()
}
Note: Dont call layoutSubview directly ... always try to call layoutifNeeded or setNeeedsLayout
A way you can have the cell calculate its size automatically, is to have constraints set inside the cell view for all directions on vertical axis. So, in your case the image view will have top, leading and trailing set to superview, and bottom to the label. The label will have leading and bottom to superview, and the top will be to the image view you set. It's important to set for vertical axis, since for table view we need it to calculate the height. This way the table view will know what to expect and can set height automatically.
After that you set tableView.rowHeight = UITableView.automaticDimension. Or you can also set in the delegate method.
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
After all this is in place, you will only need to give the right values to the cell subviews(in this case set image and text). You can do this inside cell for row function. A sample code will look something like this.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "identifier", for: indexPath)
let model = self.models[indexPath.row]
cell.imageViewPostPreview.image = model.image ?? nil
cell.postText.text = model.text ?? nil
return cell
}
This should work without any further need to calculate constraints and calling layouts to update. Hope this helps! You can also check this question.

Is there a callback for ViewCell fully laid out in swift?

I have a UITableView with cells, the relevant part of the layout looks like this:
everything is fine until i try to add shadows to that button(orange) and view ($-), this happens:
The problem is that the view is not fully set in awakeFromNib, and it also not fully set during
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
So when can i add the shadow to those views without having to worry about the size not being set correctly yet?
Aditional Info: if i set it during the cell setup, it does not work at first, but after scrolling it gets fixed for as long as the view is open.
Edit 1: how do i add the shadows? I call a func for the cell that calls the next method passing the view as parameter, i think that #DonMag suggestion should work, subclassing should work, but is there no way to know when all the view is laid out in a TableView?
static func addShadowToView(view: UIView, CornerRadius: CGFloat){
view.layer.cornerRadius = CornerRadius
view.clipsToBounds = true
view.layer.masksToBounds = false
view.layer.shadowColor = UIColor.gray.cgColor
view.layer.shadowOffset = CGSize(width: 0.1, height: 0.1)
view.layer.shadowOpacity = 0.5
let shadowPath = UIBezierPath(roundedRect: view.bounds,
cornerRadius: view.layer.cornerRadius)
view.layer.shadowPath = shadowPath.cgPath
}

swift tableview cell change width frame not work

This is my code but not work!
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! MyChatCell
var frame = cell.frame
if(indexPath%2 == 0){
frame.size.width = CGFloat(200)
} else {
frame.size.width = CGFloat(100)
}
cell.frame = frame
return cell
}
I want change cell width to 100 or 200 per cell
But frame.size.width not work
You can't change that, because all cells in a table view must have the same width, but can have different heights if needed.
However, you can try to make the table view's .backgroundColor transparent, and maybe the content view of the cell, than add another "wrapper view" on the cell's content view and make it have different widths. It will create the "visual impression" that cells indeed have different widths.

Tint of the image does not want to change

I am trying to set a new color of the image while the cell of the UICollectionView was selected or deselected. Whenever i do not set a tint color of the image it is working, but i do not wanna have default blue color of it. So What I am doing is :
In func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell function, I am inicialazing my images with code :
let borderWidth = itemBorder.frame.width
let borderHeight = itemBorder.frame.height
myImage = UIImageView(frame: CGRect(x: 0, y: 0, width: borderWidth - 1/4 * borderWidth, height: borderHeight - 1/5 * borderHeight))
myImage.image = UIImage(named: myCollection[indexPath.row])?.withRenderingMode(.alwaysTemplate)
myImage.tintColor = UIColor.groupTableViewBackground
myImage.center = CGPoint(x: tmpCell.bounds.width/2, y: tmpCell.bounds.height/2)
myImage.contentMode = .scaleAspectFit
tmpCell.contentView.addSubview(myImage)
and so on in the didselected and deselected function :
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let currentCell: UICollectionViewCell?
switch collectionView {
case myCollectionView:
print("clicked")
currentCell = myCollectionView.cellForItem(at: indexPath)!
currentCell?.tintColor = UIColor.white
case colorsCollectionView:
break
default:
break
}
}
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
let currentCell: UICollectionViewCell?
switch collectionView {
case vehiclesCollectionView:
print("deselected")
currentCell = myCollectionView.cellForItem(at: indexPath)!
currentCell?.tintColor = UIColor.groupTableViewBackground
case colorsCollectionView:
break
default:
break
}
}
Could someone tell me whats wrong ?
Thanks in advance!
Solution
Solution
Whenever i wanted to update the tint of the color i was pointing on the cell not on the image so basiclly only need to add this into selected or deselected method :
currentCell = myCollectionView.cellForItem(at: indexPath)!
let image = currentCell?.contentView.subviews[1] as! UIImageView
image.tintColor = UIColor.white
I see that you are setting the currentCell?.tintColor but I suppose you will probably have to set the currentCell?.vehicleImageView.image.tintColor
Also I see your code as a bit confusing since you have vehicleImage (which should probably be named vehicleImageView) and myImage which is a UIImage which is being added as a subView to the contentView? I thought it was only possible to add subclasses of UIView as subviews.
I suggest you create an outlet called myImageView in your custom UICollectionViewCell to which you can set the image.tintColor and change cell.myImageView.tintColor in your didSelect and didDeselect
If you do not add them from the storyboard, you can still create a subclass of UICollectionViewCell that has a property called vehicleImageView. You can set the frame and image of this as required in your cellForRow. Now you will have a property which you can refer to in your didSelect and didDeselect as cell.vehicleImageView.image.tintColor.
If you do not want to create a subclass that has a property, you will basically have to loop through all your subviews and find the image view and set the image tintColor there. Setting the tintColor of the UICollectionViewCell WILL NOT solve the problem. You will have to set it to the imageView.image
Hope that helps!

How to keep tablecells from inheritiing in Swift

I'm trying to generate cells in my table view, each with dynamically allowed buttons on the right-hand side; some with 2 buttons, some with 4 buttons. The problem is, though the table view loads as I'd like, upon scrolling around, the cells end up all having 4 buttons - even the ones that only had two before hand.
What am I doing wrong?
Here is my code:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell:UITableViewCell = tableView.dequeueReusableCellWithIdentifier("cell")! as UITableViewCell
cell.textLabel?.text = self.items[indexPath.row][0]
cell.accessibilityLabel = self.items[indexPath.row][bigSort]
cell.selectionStyle = .None
for x in 0..<4
{
let button = UIButton()
let marginStart = w - tickWidthInCell * 4 - 9 + (tickWidthInCell + 2) * x
let margin = CGFloat(marginStart)
button.frame = CGRectMake(margin, CGFloat(cellHeightMargin), CGFloat(tickWidthInCell), CGFloat(tickHeightInCell))
button.layer.borderColor = GRAY.CGColor
if (self.items[indexPath.row][1] == "Special" && x % 2 != 0)
{
button.backgroundColor = UIColor.clearColor()
button.layer.borderColor = UIColor.clearColor().CGColor
//button.hidden = true
}
else
{
button.addTarget(self, action: #selector(cellButtonClicked), forControlEvents: .TouchUpInside)
button.backgroundColor = UIColor(netHex:0x222222)
button.layer.borderWidth = 1
}
cell.contentView.addSubview(button)
}
return cell
}
It's because tableview reuses the cells you define.
To get control over how the reusing should happen, in your custom UITableViewCell override prepareForReuse. And always reset them the state you want to be, like setting the cell to only have 2 buttons)
Or in cellForRowAtIndexPath you can handle both cases to have either 2 buttons or 4.
(when you are specifying that you want 2 buttons make sure that you hide the other 2)