Cached shadow path for UITableView - swift

I want to cache a shadow path to make the performance of UITableView better.
I've read https://yalantis.com/blog/mastering-uikit-performance/ that
if let rect = cell.imageView?.bounds {
cell.imageView?.layer.shadowPath = UIBezierPath(rect: rect).cgPath
}
would stop offscreen shadows, however using the following function
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = "Cell data: \(data[indexPath.row])"
if let rect = cell.imageView?.bounds {
cell.imageView?.layer.shadowPath = UIBezierPath(rect: rect).cgPath
}
cell.imageView?.layer.shadowRadius = 8
cell.imageView?.layer.shadowOffset = CGSize(width: 3, height: 3)
cell.imageView?.layer.shadowOpacity = 0.5
cell.imageView?.layer.cornerRadius = 20
cell.imageView?.image = UIImage(named: "PlaceholderImage")
return cell
}
produces a result where some cells have the shadow and some don't.
How can I implement caching of shadow paths to all cells in a simple UITableView

The problem is these lines:
if let rect = cell.imageView?.bounds {
cell.imageView?.layer.shadowPath = UIBezierPath(rect: rect).cgPath
}
If the cell's imageView has never been assigned an image, its frame is .zero and so its bounds is .zero. So the shadowPath ends up with a cgPath that is just a point, and no shadow appears.
The way I would solve this is that I would not use the built-in imageView property at all. I'd use a custom cell class with my own image view whose frame I can control, instead of the built-in imageView which plays these sorts of tricks.

if let shadowPath = self.shadowPathCache {
cell.imageView?.layer.shadowPath = shadowPath
} else if let rect = cell.imageView?.bounds {
self.shadowPathCache = UIBezierPath(rect: rect).cgPath
cell.imageView?.layer.shadowPath = self.shadowPathCache
}

Related

TableView Cell view shadow does not coming properly in Swift

I have added view as a background for tableview cell and I am giving shadow for view. Here when I run tableview shadow does not coming properly, once I scroll down and up then shadow coming properly.
I have given shadow according to this answer answer
Code:
extension UIView {
func dropShadow(scale: Bool = true) {
layer.masksToBounds = false
layer.shadowColor = UIColor.black.cgColor
layer.shadowOpacity = 0.5
layer.shadowOffset = CGSize(width: -1, height: 1)
layer.shadowRadius = 1
layer.shadowPath = UIBezierPath(rect: bounds).cgPath
layer.shouldRasterize = true
layer.rasterizationScale = scale ? UIScreen.main.scale : 1
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! PlansTableViewCell
cell.containerView.dropShadow()
return cell
}
Before scrolling shadow coming like this:
after scrolling coming like below:
After running(before scrolling) also i need second image kind of output, Here Help me with the code.
Problem is here
layer.shadowPath = UIBezierPath(rect: bounds).cgPath
bounds isn't correct at that time , try
call it from
override func layoutSubviews() {
super.layoutSubviews()
self.containerViewdropShadow()
}
or
cell.layoutIfNeeded()
cell.containerView.dropShadow()

Adding images to table view cell problem using stack views

in my prototype cell I have a horizontal stack view and I connected that to my UITableViewCell and I defined an update function In my UITableViewCell that adds multiple images to the stack view and I called the function In TableViewController cellForRowAt but nothing nothing happens.
//inside UITableViewCell
#IBOutlet weak var lineStack: UIStackView!
func update(_ imageName: String){
let numberOfCopies = Int(deviceWidth/50)
let startX = (Int(deviceWidth) - (numberOfCopies*50))/2
xValue = xValue + startX
let image = UIImage(named: imageName)
for _ in 1...Int(numberOfCopies) {
xValue = xValue + heightValue
let imageView = UIImageView(image: image)
imageView.frame = CGRect(x: xValue , y: 0, width: widthValue, height: heightValue)
lineStack.addSubview(imageView)
}
}
//inside TableViewController
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Line", for: indexPath) as! LineTableViewCell
let imageName = imagesName[indexPath.row]
cell.update(imageName)
return cell
}
Your post indicates you want your images to be 50 x 50 points... use auto-layout constraints with .addArrangedSubview():
class TestCell: UITableViewCell {
#IBOutlet var lineStack: UIStackView!
func update(_ imageName: String) {
let numberOfCopies = Int(deviceWidth/50)
let image = UIImage(named: imageName)
for _ in 1...Int(numberOfCopies) {
let imageView = UIImageView(image: image)
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.widthAnchor.constraint(equalToConstant: 50).isActive = true
imageView.heightAnchor.constraint(equalTo: imageView.widthAnchor).isActive = true
lineStack.addArrangedSubview(imageView)
}
}
}
EDIT Sample result, with stack view centered horizontally:
Stack view is set to:
Axis: Horizontal
Alignment: Fill
Distribution: Fill
Spacing: 0
and this is the layout with constraints:
Note that the stack view has a Width constraint of 10, but its Priority is 250 ... that allows the stack view to stretch horizontally, while keeping IB satisfied with the constraints.
For the line:
lineStack.addSubview(imageView)
Instead of addSubview() you need to do it with addArrangedSubview(). The former is the regular way of adding a subview to a view, whereas the latter is specifically for UIStackView and tells the view to insert it properly.

Changing the UIImage position programmarically

I have a UITableView with sections. I added a UImage in each cell, but it is on the left side, how can I change it to make it go to the right side?
this is my code for each cell:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cells")
cell?.textLabel?.text = data[indexPath.section].subType[indexPath.row]
cell?.textLabel?.textAlignment = .right
cell?.textLabel?.font = UIFont(name: "GESSTwoLight-Light", size: 15)
cell!.isUserInteractionEnabled = false;
cell!.imageView?.image = UIImage(named: "username.png")
return cell!
}
This is my UITableView
you simply have to do the following in the tableView(_:cellForRowAt:) method:
cell.transform = CGAffineTransform(scaleX: -1.0, y: 1.0)
cell.textLabel?.transform = CGAffineTransform(scaleX: -1.0, y: 1.0)
cell.imageView?.transform = CGAffineTransform(scaleX: -1.0, y: 1.0)
This will create the following
Credits goes to liau-jian-jie for sharing this solution, which also can be applied to UIButtons.

How to make a Progress View in a TableView?

I'm trying to make a Progress View on a TableView.
I succeeeded to create progressview, but if animation is true bar disappears, and if is false bar is full... i can't understand why.
This is my code:
here is my appending method:
if formazione == formazione {
guestInfoList.append("Formazione: \(formazione)%")
}
and here the full dequeue function:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var countdowncell = tableView.dequeueReusableCell(withIdentifier: "countdowncell", for: indexPath)
//countdown
if indexPath.row == 9{
countdowncell.textLabel?.text = calcolaCountdown()
}else{
countdowncell.textLabel?.text = guestInfoList[indexPath.row]
}
//Creazione Progress View
if indexPath.row == 12 {
cell = UITableViewCell(style: .default, reuseIdentifier: "Cell")
let progressView = UIProgressView(progressViewStyle: .default)
//setting height of progressView
let transform = CGAffineTransform(scaleX: cell.frame.size.width, y: cell.frame.size.height)
progressView.transform = transform
cell.contentView.addSubview(progressView)
//**//inserisci valore formazione
progressView.setProgress(Float(formazione), animated: false)
}
return countdowncell
}
Ps: and if i want to put the progress View on the right Side of the cell?
Edit
Finally i made it! Thank you matt!!
if indexPath.row == 12 {
var cell = tableView.dequeueReusableCell(withIdentifier: "formprogresscell", for: indexPath)
let progressView = UIProgressView(progressViewStyle: .default)
//setting height of progressView
progressView.frame = CGRect(x: 230, y: 20, width: 130, height: 130)
progressView.progress += 0.5
progressView.rightAnchor.accessibilityActivate()
//**//inserisci valore formazione
progressView.setProgress(Float(formazione), animated: true)
progressView.progressTintColor = UIColor.red
cell.textLabel?.text = "Formazione: \(formvalue)%"
cell.contentView.addSubview(progressView)
}
The problem is what you do with the progress view after creating it:
let progressView = UIProgressView(progressViewStyle: .default)
You are failing to give the progressView any frame. Therefore it doesn't have one. In particular, it has no width. Therefore it is invisible.
Giving it a frame will solve both your problems: you'll give the view a width, and you'll give it an origin so you can put it where you want inside the content view. (But in fact you should be using autolayout, not the frame.)

Adding shadow to tableview cell cause laggy scroll and duplicate shadow

I have added white space around my tableview cell and every time I scroll this shadows getting bigger and bigger when I scroll, and its get lag when I scroll for the second and third time with the bigger shadow
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! CustomCell
cell.backgroundColor = UIColor.clear
cell.contentView.backgroundColor = UIColor.clear
let whiteRoundedView : UIView = UIView(frame: CGRect(x:10,y: 5,width: self.view.frame.size.width - 20,height: 117))
whiteRoundedView.layer.backgroundColor = CGColor(colorSpace: CGColorSpaceCreateDeviceRGB(), components: [1.0, 1.0, 1.0, 0.9])
//whiteRoundedView.layer.masksToBounds = true
whiteRoundedView.layer.cornerRadius = 5.0
whiteRoundedView.layer.shadowOffset = CGSize(width: -1,height: 1)
whiteRoundedView.layer.shadowOpacity = 0.2
let shadowPath = UIBezierPath(rect: whiteRoundedView.layer.bounds)
whiteRoundedView.layer.shouldRasterize = true
whiteRoundedView.layer.shadowPath = shadowPath.cgPath
cell.contentView.addSubview(whiteRoundedView)
cell.contentView.sendSubview(toBack: whiteRoundedView)
return cell
}
Just put code inside the awakefrom nib
class CustomCell: UITableViewCell {
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
self.backgroundColor = UIColor.clear
self.contentView.backgroundColor = UIColor.clear
let whiteRoundedView : UIView = UIView(frame: CGRect(x:10,y: 5,width: self.contentView.frame.size.width - 20,height: 117))
whiteRoundedView.layer.backgroundColor = CGColor(colorSpace: CGColorSpaceCreateDeviceRGB(), components: [1.0, 1.0, 1.0, 0.9])
//whiteRoundedView.layer.masksToBounds = true
whiteRoundedView.layer.cornerRadius = 5.0
whiteRoundedView.layer.shadowOffset = CGSize(width: -1,height: 1)
whiteRoundedView.layer.shadowOpacity = 0.2
let shadowPath = UIBezierPath(rect: whiteRoundedView.layer.bounds)
whiteRoundedView.layer.shouldRasterize = true
whiteRoundedView.layer.shadowPath = shadowPath.cgPath
self.contentView.addSubview(whiteRoundedView)
self.contentView.sendSubview(toBack: whiteRoundedView)
}
}
You keep adding shadow views on top of each other without ever removing them. If all your cells will need the shadow you can just add a tag and check if a view with that tag already exists like so:
whiteRoundedView.tag = 12345
if cell.contentView.viewWithTag(12345) == nil {
cell.contentView.addSubview(whiteRoundedView)
}
If some cells have the shadow but some cells don't you could do it like this:
if let shadowView = cell.contentView.viewWithTag(12345) && noShadow {
shadowView.removeFromSuperview
} else if !noShadow {
cell.contentView.addSubview(whiteRoundedView)
}
Alternatively like mentioned in the comments of the question you would add it to your custom cell class:
override func awakeFromNib() {
super.awakeFromNib()
let whiteRoundedView : UIView = UIView(frame: CGRect(x:10,y: 5,width: self.contentView.frame.size.width - 20,height: 117))
whiteRoundedView.layer.backgroundColor = CGColor(colorSpace: CGColorSpaceCreateDeviceRGB(), components: [1.0, 1.0, 1.0, 0.9])
whiteRoundedView.layer.cornerRadius = 5.0
whiteRoundedView.layer.shadowOffset = CGSize(width: -1,height: 1)
whiteRoundedView.layer.shadowOpacity = 0.2
let shadowPath = UIBezierPath(rect: whiteRoundedView.layer.bounds)
whiteRoundedView.layer.shouldRasterize = true
whiteRoundedView.layer.shadowPath = shadowPath.cgPath
self.contentView.addSubview(whiteRoundedView)
}
You are adding whiteRoundedView every time. If you are using a storyboard or Nib for designing your Cell UI then you can add whiteRoundedView there Or you can add a tag in whiteRoundedView and check if there are already any view added with that tag before adding the whiteRoundedView as subview.