Invalidate and Redraw CAShapeLayer inside collectionViewCell after device orientation change - swift

I have a custom collection view cell that is adding a dashed border to the cell layer with the following code:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = appDealCollectionView.dequeueReusableCell(withReuseIdentifier: "appDealCollectionCell", for: indexPath) as! AppDealCollectionViewCell
if let codes = discountCodes {
cell.discountCodeTitle.text = codes[indexPath.row].codeMessageOne
cell.discountCode.text = codes[indexPath.row].code
let yourViewBorder = CAShapeLayer()
yourViewBorder.strokeColor = UIColor.black.cgColor
yourViewBorder.lineWidth = 2
yourViewBorder.lineDashPattern = [10, 10]
yourViewBorder.frame = cell.bounds
yourViewBorder.fillColor = nil
yourViewBorder.path = UIBezierPath(roundedRect: cell.bounds, cornerRadius: 6).cgPath
cell.layer.addSublayer(yourViewBorder)
}
return cell
}
This code works perfectly fine on the initial loading of the view. However, when the orientation changes, the cell size changes. The above code does correctly draw the new border CAShapeLayer, but the previously drawn border layer is still present which were drawn based on the old size.
The result is two different border layers being present at the same time overlapping each other with different dimensions.
How do I invalidate any previously drawn CAShapeLayers? Where is the invalidation done? In cellForItemAt? Or possibly inside the custom "AppDealCollectionViewCell" itself?

Since cells are reusable, every call of cellForRowAtIndexPath will add another instance of CAShapeLayer onto cell. That is why you are having several borders overlapping each other. Also CALayer doest not support neither auto layout nor autoresizingMask, so you have to update size of your CAShapeLayer manually.
You should create subclass of UITableViewCell, then create instance of CAShapeLayer and store pointer to it in class property variable. Once layout cycle occurs, in layoutSubviews function you need to update frame of CAShapeLayer.
The final implementation looks like that:
class BorderedTableViewCell: UITableViewCell {
lazy var borderLayer = CAShapeLayer()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupBorderLayer()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupBorderLayer()
}
private func setupBorderLayer() {
borderLayer.strokeColor = UIColor.black.cgColor
borderLayer.lineWidth = 2
borderLayer.fillColor = nil
borderLayer.lineDashPattern = [10, 10]
layer.addSublayer(borderLayer)
}
private func updateBorderLayer() {
borderLayer.frame = bounds
borderLayer.path = UIBezierPath(roundedRect: bounds, cornerRadius: 6).cgPath
}
override func layoutSubviews() {
super.layoutSubviews()
updateBorderLayer()
}
}
I hope this helps.

Related

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
}

UITableViewCell style goes away after scrolling down

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)

ContentView for tableViewCell not auto-resizing correctly?

I have added 2 labels to my cell and setup these constraints with snapkit, issue is I cant get the cell to expand correctly, it stays at its default height:
titleLabel.snp.makeConstraints { (make) -> Void in
make.top.equalTo(contentView.snp.top)
make.bottom.equalTo(descriptionLabel.snp.top)
make.left.equalTo(contentView.snp.left)
make.right.equalTo(contentView.snp.right)
}
descriptionLabel.snp.makeConstraints { (make) -> Void in
make.top.equalTo(titleLabel.snp.bottom)
make.bottom.equalTo(contentView.snp.bottom)
make.left.equalTo(contentView.snp.left)
make.right.equalTo(contentView.snp.right)
}
I mapped the four edges as you can see, however I know height isnt implied by these, how can I apply a height when the content is by nature, dynamic, and could be various heights...
setup for the labels looks like this:
lazy var titleLabel: UILabel = {
let titleLabel = UILabel()
titleLabel.textColor = .green
titleLabel.textAlignment = .center
contentView.addSubview(titleLabel)
return titleLabel
}()
lazy var descriptionLabel: UILabel = {
let descriptionLabel = UILabel()
descriptionLabel.textColor = .dark
descriptionLabel.textAlignment = .center
descriptionLabel.numberOfLines = 0
contentView.addSubview(descriptionLabel)
return descriptionLabel
}()
Give the table view an estimatedRowHeight, and set its rowHeight to UITableViewAutomaticDimension. Now the cells will be self-sizing. Well, if a label is pinned by all four sides to the content view, and if the cell is self-sizing, then that's all you have to do: the label will automatically change its height to accommodate its text, and the cell will automatically change size to accommodate the label.
First of all I think that you should add subviews to contentView in subclassed UITableViewCell class initializer method.
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.contentView.addSubview(titleLabel)
self.contentView.addSubview(descriptionLabel)
}
Secondly, make sure that in your viewDidLoad method (probably in your ViewController) these two lines are added:
tableView.estimatedRowHeight = 44.0
tableView.rowHeight = UITableView.automaticDimension
Of course, you should change estimatedRowHeight to accommodate your needs.
One more thing worth mentioning - you can create these constraints easier (using power of SnapKit):
titleLabel.snp.makeConstraints { (make) -> Void in
make.top.left.right.equalTo(contentView)
}
descriptionLabel.snp.makeConstraints { (make) -> Void in
make.top.equalTo(titleLabel.snp.bottom)
make.bottom.left.right.equalTo(contentView)
}

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!

UICollectionView rounded imageView

I have a UIImageView inside a UICollectionView Cell.
I wanted there to be 2 cells per column in the uicollectionview so I used this code....
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let padding: CGFloat = 25
let collectionCellSize = collectionView.frame.size.width - padding
return CGSize(width: collectionCellSize/2, height: collectionCellSize/2)
}
For the image view I wanted it to be round, and this code usually works elsewhere..
self.accountImageView.layer.cornerRadius = frame.size.width/2
self.accountImageView.clipsToBounds = true
I have tried putting that in the cellForItemAt, with no luck
Now inside the CollectionView Cell Class I added it like this
override func layoutSubviews() {
super.layoutSubviews()
self.accountImageView.layer.cornerRadius = frame.size.width/2
self.accountImageView.clipsToBounds = true
}
The image looks like a deflated football.
Is the padding code messing up the rounded image view code?
You need to add self.accountImageView.layoutIfNeeded().
And make sure height and width of your imageview is equal
override func layoutSubviews()
{
super.layoutSubviews()
self.accountImageView.layoutIfNeeded() // add this
self.accountImageView.layer.cornerRadius = frame.size.width/2
self.accountImageView.clipsToBounds = true
}
If you are using constraint then try this -
override func layoutSubviews() {
super.layoutSubviews()
self.accountImageView.layoutIfNeeded() // Add this line
self.accountImageView.layer.cornerRadius = frame.size.width/2
self.accountImageView.clipsToBounds = true
}