UIView in custom UITableViewCell redraws when scrolling - swift

I am adding an UIView inside my custom UITableView to draw more or less like an availability bar. I have two problems with it:
1.- The view only gets drawn when the new cells appear on screen not at launch time as you can see in the GIF below.
2.- When scrolling tableView several times the UIView on each cell is redrawn so they overlap with each other
GIF
Code of UITableViewCell :
import UIKit
class CustomTableViewcell: UITableViewCell {
#IBOutlet weak var bikeStationLabel: UILabel!
#IBOutlet weak var progressView: UIView!
#IBOutlet weak var distanceLabel: UILabel!
override internal func awakeFromNib() {
}
}
I think nothing is wrong there, it's simple. My problem I think comes with the func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell method. What I have so far is:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: CustomTableViewcell! = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! CustomTableViewcell
cell.backgroundColor = UIColor(red: 120/255, green: 120/255, blue: 120/255, alpha: 0.5)
// Dump the data into a single variable to shorten the code
let stations = parsedData[indexPath.row]
// Draw a rectangle on each progress bar depending on the availability of the bikes
let availabilityGraph = UIBezierPath(roundedRect: CGRect(x: cell.progressView.frame.minX, y: cell.progressView.frame.minY, width: CGFloat(Float(stations.freeBikes!) / (Float(stations.freeBikes!) + Float(stations.freeDocks!))) * cell.progressView.frame.width, height: cell.progressView.frame.height), cornerRadius: 0)
let shapeLayer = CAShapeLayer()
shapeLayer.path = availabilityGraph.cgPath
//change the fill color
shapeLayer.fillColor = UIColor.clear().cgColor
//you can change the line width
shapeLayer.lineWidth = cell.frame.height
shapeLayer.strokeColor = UIColor(red: 96/255, green: 96/255, blue: 96/255, alpha: 0.8).cgColor
cell.progressView.layer.addSublayer(shapeLayer)
cell.bikeStationLabel.textColor = UIColor.white()
cell.bikeStationLabel.text = stations.stationName!
cell.distanceLabel.text = "100 m"
return cell
}

First of all give your layer specific name like this
shapeLayer.name = "ShapeLayer"
Now before adding it the progressView check that layer is not already added in the progressView, if added then just remove it.
if let sublayers = cell.progressView.layer.sublayers {
for layer: CALayer in sublayers {
if (layer.name == "ShapeLayer") {
layer.removeFromSuperlayer()
break
}
}
}
//Now add the layer to `progressView`
cell.progressView.layer.addSublayer(shapeLayer)

Related

UICollectionView: Resizing cells with animations and custom layouts

I have a collection view with a custom layout and a diffable datasource. I would like to resize the cells with an animation.
With a custom layout: it looks like the content of the cells scaled to the new size during the animation. Only when the cells reaches its final size, it is finally redrawn (see animation below)
With a stock flow layout the subviews are laid out correctly during the animation: the label remains at the top, no resizing and same for the switch.
So I would assume that the issue is in the layout implementation, what could be added to the layout allow a correct layout during the animation?
Description
The custom layout used in this example is FSQCollectionViewAlignedLayout, I also got it with the custom I used in my code. I noticed that for some cases, it is important to always return the same layout attributes objects, which FSQCollectionViewAlignedLayout doesn't do. I modified it to not return copies, and the result is the same.
For the cells (defined in the xib), the contentMode of the cell and its contentView are set to .top. The cell's contentView contains 2 subviews: a label (laid out in layoutSubviews) and a switch (laid out with constraints, but its presence has no effect on the animation).
The code of the controller is:
class ViewController: UIViewController, UICollectionViewDelegateFlowLayout {
enum Section {
case main
}
#IBOutlet weak var collectionView : UICollectionView!
var layout = FSQCollectionViewAlignedLayout()
var dataSource : UICollectionViewDiffableDataSource<Section, String>!
var cellHeight : CGFloat = 100
override func loadView() {
super.loadView()
layout.defaultCellSize = CGSize(width: 150, height: 100)
collectionView.setCollectionViewLayout(layout, animated: false) // uncommented to use the stock flow layout
dataSource = UICollectionViewDiffableDataSource(collectionView: collectionView, cellProvider: { cv, indexPath, item in
guard let cell = cv.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as? Cell else { return nil }
cell.label.text = item
return cell
})
var snapshot = NSDiffableDataSourceSnapshot<Section, String>()
snapshot.appendSections([.main])
snapshot.appendItems(["1", "2"], toSection: .main)
dataSource.apply(snapshot)
}
override func viewDidLoad() {
super.viewDidLoad()
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 150, height: cellHeight)
}
#IBAction func resizeAction(_ sender: Any) {
if let layout = collectionView.collectionViewLayout as? FSQCollectionViewAlignedLayout {
UIView.animate(withDuration: 0.25) {
layout.defaultCellSize.height += 50
let invalidation = FSQCollectionViewAlignedLayoutInvalidationContext()
invalidation.invalidateItems(at: [[0, 0], [0, 1]])
layout.invalidateLayout(with: invalidation)
self.view.layoutIfNeeded()
}
}
if let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout {
UIView.animate(withDuration: 0.25) {
self.cellHeight += 50
let invalidation = UICollectionViewFlowLayoutInvalidationContext()
invalidation.invalidateItems(at: [[0, 0], [0, 1]])
layout.invalidateLayout(with: invalidation)
self.view.layoutIfNeeded()
}
}
}
}
class Cell : UICollectionViewCell {
#IBOutlet weak var label: UILabel!
override func layoutSubviews() {
super.layoutSubviews()
label.frame = CGRect(x: 0, y: 0, width: bounds.width, height: 30)
}
}
I spent a developer support ticket on this question. The answer was that custom animations when reloading cells are not supported.
So as a workaround, I ended up creating new cells instances that I add to the view hierarchy, then add autolayout constraints so that they overlap exactly the cells being resized. The animation is then run on those newly added cells. At the end of the animation, these cells are removed.

Access CollectionView Cell Content (change width of inner view)

I want to have a uiview as cell background with changing width. This should be used as a progress-/ statusbar.
The problem:
I can’t change the width of the statusbar from my collectionViewController (only from the CollectionViewCellController)....
I manually set the width to 5 in layoutSubviews(). Then I changed it to 80 in setStatusBar(...) which I called in the other class. The width stays at 5 :(
Here is my code
import UIKit
class MyCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var myLabel: UILabel!
#IBOutlet weak var statusbar: UIView!
override func layoutSubviews() {
// cell rounded section
self.layer.cornerRadius = 15.0
self.layer.masksToBounds = true
super.layoutSubviews()
statusbar.frame = CGRect(x: 0, y: 0, width: 5, height: self.frame.height)
}
}
extension MyCollectionViewCell{
func setStatusbar(completedTasks: Int, totalTasks: Int){
print(self.frame.width)
let newWidth = 80 //(self.frame.width * (CGFloat(completedTasks / totalTasks)))
statusbar.backgroundColor = .orange
statusbar.frame = CGRect(x: 0, y: 0, width: newWidth, height: Int(self.frame.height))
reloadInputViews()
}
}
And this is where I call the function setStatusbar(...) - where I want to change the width, but DOESNT work:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
// get a reference to our storyboard cell
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath as IndexPath) as! MyCollectionViewCell
// Use the outlet in our custom class to get a reference to the UILabel in the cell
cell.myLabel.text = self.items[indexPath.row] // The row value is the same as the index of the desired text within the array.
cell.backgroundColor = UIColor(rgb: 0xF444444)
cell.setStatusbar(completedTasks: 5, totalTasks: 25) //!!! HERE !!!
return cell
}
Can someone help me find the problem why the statusbar uiview doesn’t change its with from 5 to 80?
Thank you!

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.

I cannot able to add subview to UItableviewcell

Code
import UIKit
class ViewController:
UIViewController,UITableViewDelegate,UITableViewDataSource{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 3
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var suma=UITableViewCell()
var sampleviewpurple=UIView(frame: CGRect(x: suma.frame.width/0, y: 0, width: suma.frame.width/2, height: suma.frame.height))
sampleviewpurple.backgroundColor=#colorLiteral(red: 0.2196078449, green: 0.007843137719, blue: 0.8549019694, alpha: 1)
var sampleviewred=UIView(frame: CGRect(x: suma.frame.width/1, y: 0, width: suma.frame.width/2, height: suma.frame.height))
sampleviewred.backgroundColor=#colorLiteral(red: 0.8078431487, green: 0.02745098062, blue: 0.3333333433, alpha: 1)
suma.contentView.addSubview(sampleviewpurple)
suma.contentView.addSubview(sampleviewred)
return suma
}
#IBOutlet weak var tab: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tab.dataSource=self
tab.delegate=self
}
}
when I try to add a subview to the suma and return to the delegate, My table view is still blank, I am returning valid data source to the data source when and also noticed the reference to the table view all is fine, But still how I can not able to add a subview to the uitableviewcell
In the cellForRowAt function, you must dequeue your table cell, not instantiate UITableViewCell:
var suma = tableView.dequeueReusableCell(withIdentifier: "YOUR_CELL_IDENTIFIER")
For setting your view's frame you are using the cell's frame which is not set yet. You should use such a view which frame is already set and on which bases you want to calculate the frame of your new views.
Try this:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var sumaCell:UITableViewCell?
sumaCell = tableView.dequeueReusableCell(withIdentifier: "sumaCell")
if sumaCell == nil {
sumaCell = UITableViewCell(style: .default, reuseIdentifier: "sumaCell")
}
var sampleviewpurple=UIView(frame: CGRect(x: 0, y: 0, width: ((sumaCell?.frame.width)!/2), height: (sumaCell?.frame.height)!))
sampleviewpurple.backgroundColor=#colorLiteral(red: 0.2196078449, green: 0.007843137719, blue: 0.8549019694, alpha: 1)
var sampleviewred=UIView(frame: CGRect(x: (sumaCell?.frame.width)!/2, y: 0, width: ((sumaCell?.frame.width)!/2), height: (sumaCell?.frame.height)!))
sampleviewred.backgroundColor=#colorLiteral(red: 0.8078431487, green: 0.02745098062, blue: 0.3333333433, alpha: 1)
sumaCell?.contentView.addSubview(sampleviewpurple)
sumaCell?.contentView.addSubview(sampleviewred)
return sumaCell!
}
You are creating the cell in code. First of all, you have to register the UITableViewCell in viewDidLoad method.
tab.register(UITableViewCell.self, forCellReuseIdentifier: "DefaultCell")
Then you have to use the dequeue cell in cellForRowAt method.
var suma = tab.dequeueReusableCell(withIdentifier: "DefaultCell")
If you are instantiating the cell from storyboard, you just have to use dequeue cell. No need to register.
I made three change.
Dequeue the cell for reusability.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var suma = UITableViewCell()
suma = tab.dequeueReusableCell(withIdentifier: "DefaultCell")!`
Changed the sampleviewpurple and sampleviewred x position
let sampleviewpurple = UIView(frame: CGRect(x: 0, y: 0, width: suma.frame.width/2, height: suma.frame.height))
let sampleviewred = UIView(frame: CGRect(x: suma.frame.width/2, y: 0, width: suma.frame.width/2, height: suma.frame.height))
Registered the cell which is created programmatically to create a connection between UITableView and UITableViewCell in viewDidLoad function
tab.register(UITableViewCell.self, forCellReuseIdentifier: "DefaultCell")