UICollectionViewCell UIImage Lag even with dispatch_async - ios

Symptom lag appears upon rendering a cell in collection view cell, I've recorded it here https://youtu.be/2utKOpHMshs
I'm not quite sure how the lag is happening and starting to suspect it has something to do with the recipe name element else rather than the UIImage since it's on another thread.
Async function
func asyncLoadImage(data: NSData, imageView: UIImageView) {
let priority = DISPATCH_QUEUE_PRIORITY_DEFAULT
dispatch_async(dispatch_get_global_queue(priority, 0)) {
var image: UIImage!
NSLog("loading image")
image = UIImage(data: data)
dispatch_async(dispatch_get_main_queue()) {
imageView.image = image
NSLog("render image")
}
}
}
RecipeListViewController
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(Storyboard.CellIdentifier, forIndexPath: indexPath) as!RecipeListCollectionViewCell
let recipe = recipes[indexPath.item] as! Recipe
cell.recipeName?.text = recipe.name?.uppercaseString
cell.layer.shouldRasterize = true
cell.layer.rasterizationScale = UIScreen.mainScreen().scale
if (recipe.photo != nil) {
asyncLoadImage(recipe.photo!, imageView: cell.recipeImageView)
}
return cell
}
UICollectionViewCell:init
recipeImageView = UIImageView(frame: CGRect(x: 0, y: 0, width: frame.size.width, height: frame.size.height-100))
recipeImageView.contentMode = UIViewContentMode.ScaleAspectFill
recipeImageView.clipsToBounds = true
self.addSubview(recipeImageView)
recipeName = UILabel(frame: CGRect(x: 0, y: frame.size.height-100, width: frame.size.width, height: 50))
recipeName.backgroundColor = UIColor(red: 255, green: 255, blue: 255, alpha: 1)
recipeName.textColor = UIColor.blackColor()
recipeName.textAlignment = NSTextAlignment.Center
recipeName.font = UIFont.boldSystemFontOfSize(18.00)
self.addSubview(recipeName)

The issue resolved by removing the following code
cell.layer.shouldRasterize = true
cell.layer.rasterizationScale = UIScreen.mainScreen().scale

Related

Animating CALayer is not working on iOS 14

I have a problem with animating CALayer after updating to Xcode12 and iOS14 - animations that used to work on iOS13 are not working on iOS14. Here is the code for animations:
private func animateCellPress() {
let lightShadowAnimation = CABasicAnimation(keyPath: "shadowOffset")
lightShadowAnimation.fromValue = CGSize(width: -self.shadowRadius, height: -self.shadowRadius)
lightShadowAnimation.toValue = CGSize(width: self.shadowRadius, height: self.shadowRadius)
lightShadowAnimation.fillMode = .forwards;
lightShadowAnimation.isRemovedOnCompletion = false;
lightShadowAnimation.duration = self.animationDuration
self.lightShadow.add(lightShadowAnimation, forKey: lightShadowAnimation.keyPath)
let darkShadowAnimation = CABasicAnimation(keyPath: "shadowOffset")
darkShadowAnimation.fromValue = CGSize(width: self.shadowRadius, height: self.shadowRadius)
darkShadowAnimation.toValue = CGSize(width: -self.shadowRadius, height: -self.shadowRadius)
darkShadowAnimation.fillMode = .forwards;
darkShadowAnimation.isRemovedOnCompletion = false;
darkShadowAnimation.duration = self.animationDuration
self.darkShadow.add(darkShadowAnimation, forKey: darkShadowAnimation.keyPath)
}
lightShadow and darkShadow are stored as a properties and here is the initialisation code for them:
private func initializeDarkShadow() {
darkShadow = CALayer()
darkShadow.frame = bounds
darkShadow.backgroundColor = backgroundColor?.cgColor
darkShadow.cornerRadius = cornerRadius
darkShadow.shadowOffset = CGSize(width: shadowRadius, height: shadowRadius)
darkShadow.shadowOpacity = 1
darkShadow.shadowRadius = shadowRadius
darkShadow.shadowColor = Asset.Colors.darkShadow.color.cgColor
let rect = CGRect(origin: CGPoint(x: 0, y: 0), size: bounds.size)
darkShadow.shadowPath = UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius).cgPath
layer.insertSublayer(darkShadow, at: 0)
}
private func initializeLightShadow() {
lightShadow = CALayer()
lightShadow.frame = bounds
lightShadow.backgroundColor = backgroundColor?.cgColor
lightShadow.cornerRadius = cornerRadius
lightShadow.shadowOffset = CGSize(width: -shadowRadius, height: -shadowRadius)
lightShadow.shadowOpacity = 1
lightShadow.shadowRadius = shadowRadius
lightShadow.shadowColor = Asset.Colors.lightShadow.color.cgColor
let rect = CGRect(origin: CGPoint(x: 0, y: 0), size: bounds.size)
lightShadow.shadowPath = UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius).cgPath
layer.insertSublayer(lightShadow, at: 0)
}
Any ideas on what's wrong and how can I fix it? Also I am not getting any compiler errors or warnings. Console log is empty as well.
This works. I think each time you are calling your animate layoutSubviews is being called thus removing the layer and the animation. You could either just update the frame there or check if the layer has already been added. If that is not the issue then there is something wrong with you press or touch function. Create a project with a single view and copy and paste this into your ViewController file.
import UIKit
class TableViewCell : UITableViewCell{
let darkShadowColor = UIColor.green
let lightShadowColor = UIColor.red
let animationDuration : Double = 2
var darkShadow = CALayer()
var lightShadow = CALayer()
let cornerRadius : CGFloat = 4
let shadowRadius : CGFloat = 3
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
initializeDarkShadow()
initializeLightShadow()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
initializeDarkShadow()
initializeLightShadow()
}
private func initializeDarkShadow() {
darkShadow = CALayer()
darkShadow.frame = bounds
darkShadow.backgroundColor = backgroundColor?.cgColor
darkShadow.cornerRadius = cornerRadius
darkShadow.shadowOffset = CGSize(width: shadowRadius, height: shadowRadius)
darkShadow.shadowOpacity = 1
darkShadow.shadowRadius = shadowRadius
darkShadow.shadowColor = darkShadowColor.cgColor
let rect = CGRect(origin: CGPoint(x: 0, y: 0), size: bounds.size)
darkShadow.shadowPath = UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius).cgPath
layer.insertSublayer(darkShadow, at: 0)
}
private func initializeLightShadow() {
lightShadow = CALayer()
lightShadow.frame = bounds
lightShadow.backgroundColor = backgroundColor?.cgColor
lightShadow.cornerRadius = cornerRadius
lightShadow.shadowOffset = CGSize(width: -shadowRadius, height: -shadowRadius)
lightShadow.shadowOpacity = 1
lightShadow.shadowRadius = shadowRadius
lightShadow.shadowColor = lightShadowColor.cgColor
let rect = CGRect(origin: CGPoint(x: 0, y: 0), size: bounds.size)
lightShadow.shadowPath = UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius).cgPath
layer.insertSublayer(lightShadow, at: 0)
}
func animateCellPress() {
print("called")
let lightShadowAnimation = CABasicAnimation(keyPath: "shadowOffset")
lightShadowAnimation.fromValue = CGSize(width: -self.shadowRadius, height: -self.shadowRadius)
lightShadowAnimation.toValue = CGSize(width: self.shadowRadius, height: self.shadowRadius)
lightShadowAnimation.beginTime = CACurrentMediaTime()
lightShadowAnimation.fillMode = .both;
lightShadowAnimation.isRemovedOnCompletion = false;
lightShadowAnimation.duration = self.animationDuration
self.lightShadow.add(lightShadowAnimation, forKey: nil)
let darkShadowAnimation = CABasicAnimation(keyPath: "shadowOffset")
darkShadowAnimation.fromValue = CGSize(width: self.shadowRadius, height: self.shadowRadius)
darkShadowAnimation.toValue = CGSize(width: -self.shadowRadius, height: -self.shadowRadius)
darkShadowAnimation.beginTime = CACurrentMediaTime()
darkShadowAnimation.fillMode = .both;
darkShadowAnimation.isRemovedOnCompletion = false;
darkShadowAnimation.duration = self.animationDuration
self.darkShadow.add(darkShadowAnimation, forKey: nil)
}
override func layoutSubviews() {
super.layoutSubviews()
self.lightShadow.frame = self.bounds
self.darkShadow.frame = self.bounds
}
}
class ViewController: UIViewController {
let identifier = "someCellID"
lazy var tableView : UITableView = {
let tv = UITableView(frame: self.view.bounds)
tv.delegate = self
tv.dataSource = self
tv.autoresizingMask = [.flexibleWidth,.flexibleHeight]
tv.register(TableViewCell.self, forCellReuseIdentifier: identifier)
return tv
}()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.view.addSubview(tableView)
}
}
extension ViewController : UITableViewDelegate,UITableViewDataSource{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 20
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: identifier, for: indexPath) as? TableViewCell{
cell.selectionStyle = .none
return cell
}
print("error")
return UITableViewCell()
}
func tableView(_ tableView: UITableView, didHighlightRowAt indexPath: IndexPath) {
if let cell = tableView.cellForRow(at: indexPath) as? TableViewCell{
cell.animateCellPress()
}
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 50
}
}

Reuse Identifier on iCarousel

I am trying to build a carousel on my app based on this project, https://github.com/nicklockwood/iCarousel, only I am writing it in swift. I have it working correctly, where you can scroll through and tap on the item at index correctly here:
func carousel(_ carousel: iCarousel, viewForItemAt index: Int, reusing view: UIView?) -> UIView {
let tempView = UIView(frame: CGRect(x: 0, y: 130, width: 295, height: 300))
let object = self.imageFilesArray.object(at: index) as! PFObject
let imageView = UIImageView()
imageView.frame = CGRect(x: 0, y: 0 , width: 295, height: 295)
imageView.layer.cornerRadius = 15
imageView.layer.masksToBounds = true
let imageFile = object.object(forKey: "Image") as! PFFile
imageFile.getDataInBackground{
(data, error) -> Void in
if error == nil {
}
imageView.image = UIImage(data: data!)
tempView.addSubview(imageView)
}
return tempView
}
func carousel(_ carousel: iCarousel, didSelectItemAt index: Int) {
let cell = carouselView.itemView(at: index)
let object = self.imageFilesArray[index] as! PFObject
if cell?.tag == 0 {
print("open cell")
let moveImg = CGAffineTransform(translationX: 0, y: -100)
UIView.animate(withDuration: 0.4, delay: 0.1, options: [], animations: {
//cell.imageView.transform = moveImg
}, completion: nil)
cell?.tag = 1
} else if cell?.tag == 1 {
let storyBoard : UIStoryboard = UIStoryboard(name: "Main", bundle:nil)
let nextViewController = storyBoard.instantiateViewController(withIdentifier: "EventDetailViewController") as! EventDetailViewController
self.present(nextViewController, animated:false, completion:nil)
}
However, what I am trying to do is when I tap the item at index, the imageView transitions on the screen. My issue is that I cannot access that particular cells imageView in order to move it. Does anyone know what I need to change?
You could give your imageView a tag like this:
let imageView = UIImageView()
imageView.frame = CGRect(x: 0, y: 0 , width: 295, height: 295)
imageView.layer.cornerRadius = 15
imageView.layer.masksToBounds = true
imageView.tag = 2
Then you can retrieve the imageView from the cell like this:
if let imageView = cell.viewWithTag(2) as? UIImageView {
imageView.transform = moveImg
}
OR
You could filter through the subviews of the cell and find the first subview that can be cast to a UIImageView. Although this isn't very robust, consider creating a Class and XIB for this cell.
cell.subviews.flatMap { $0 as? UIImageView }.first?.transform = moveImg

UITableViewCell gets reused and shows wrong drawing in UIView

I've been having this problem for weeks and I think I am not able to solve it.
I have a tableView which has custom tableViewCells and they are filled with data. The tableViewCell has two UILabels and one UIView.
The problem appears when I scroll several times the drawings on the UIViews are overlapped one with another, I think they are redrawn. I know that this behavior is because of the reuse of the cells but I can't even locate the origin of the problem.
UIViews show perfectly when opening the app
UIViews get overlapped after scrolling
My UITableView's cellForRowAtIndexPath is:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let CellIdentifier = "Cell"
let cell: CustomTableViewcell = self.tableView.dequeueReusableCellWithIdentifier(CellIdentifier, forIndexPath: indexPath) as! CustomTableViewcell
//self.tableView.dequeueReusableCellWithIdentifier(CellIdentifier) as! CustomTableViewcell
cell.prepareForReuse()
self.createUIForCell(cell)
self.configureCell(cell, indexPath: indexPath)
print("\t\(parsedData[indexPath.row].stationName): \(parsedData[indexPath.row].freeBikes)")
return cell
}
func createUIForCell(cell: CustomTableViewcell) {
cell.distanceLabel.textColor = UIColor.whiteColor()
cell.distanceLabel.backgroundColor = UIColor.clearColor()
cell.bikeStationLabel.textColor = UIColor.whiteColor()
cell.bikeStationLabel.backgroundColor = UIColor.clearColor()
}
func configureCell(cell: CustomTableViewcell, indexPath: NSIndexPath) {
let stations = parsedData[indexPath.row]
if indexPath.row % 2 == 0 {
cell.stackView.arrangedSubviews.first!.backgroundColor = cellBackgroundColor1
cell.progressView.backgroundColor = cellBackgroundColor2
} else {
cell.stackView.arrangedSubviews.first!.backgroundColor = cellBackgroundColor2
cell.progressView.backgroundColor = cellBackgroundColor1
}
cell.progressView.getWidth(stations.freeBikes, freeDocks: stations.freeDocks)
cell.getLocation(stations.latitude, longitude: stations.longitude)
cell.getParameters(stations.freeBikes, freeDocks: stations.freeDocks)
cell.bikeStationLabel.text = stations.stationName
if stations.distanceToLocation != nil {
if stations.distanceToLocation! < 1000 {
cell.distanceLabel.text = String(format: "%.f m", stations.distanceToLocation!)
} else {
cell.distanceLabel.text = String(format: "%.1f km", stations.distanceToLocation! / 1000)
}
} else {
cell.distanceLabel.text = ""
}
}
For the before mentioned UIVIew inside my custom cell I have created a separated class for it to handle the drawing and it looks like this:
import UIKit
class ProgressViewBar: UIView {
var freeBikes = 0
var freeDocks = 0
var text: UILabel? = nil
var text2: UILabel? = nil
func getWidth(freeBikes: Int, freeDocks: Int) {
self.freeBikes = freeBikes
self.freeDocks = freeDocks
}
override func drawRect(rect: CGRect) {
let path = UIBezierPath(roundedRect: CGRect(x: 0, y: 0, width: rect.width * (CGFloat(freeBikes) / (CGFloat(freeBikes) + CGFloat(freeDocks))), height: frame.height), cornerRadius: 0)
let shapeLayer = CAShapeLayer()
shapeLayer.path = path.CGPath
shapeLayer.strokeColor = UIColor.whiteColor().CGColor
shapeLayer.fillColor = UIColor.whiteColor().CGColor
self.layer.addSublayer(shapeLayer)
drawText(rect)
}
func drawText(rect: CGRect) {
if freeBikes != 0 {
text?.removeFromSuperview()
text = UILabel(frame: CGRect(x: 2, y: 0, width: 20, height: 20))
text!.text = String("\(freeBikes)")
text!.textAlignment = NSTextAlignment.Left
text!.font = UIFont(name: "HelveticaNeue-Thin", size: 17)
text!.backgroundColor = UIColor.clearColor()
text!.textColor = UIColor(red: 1/255, green: 87/255, blue: 155/255, alpha: 1)
self.addSubview(text!)
}
text2?.removeFromSuperview()
text2 = UILabel(frame: CGRect(x: rect.width * (CGFloat(freeBikes) / (CGFloat(freeBikes) + CGFloat(freeDocks))) + 2, y: 0, width: 20, height: 20))
text2!.text = String("\(freeDocks)")
text2!.textAlignment = NSTextAlignment.Left
text2!.font = UIFont(name: "HelveticaNeue-Thin", size: 17)
text2!.backgroundColor = UIColor.clearColor()
text2!.textColor = UIColor.whiteColor()
self.addSubview(text2!)
}
}
The view needs two parameters to be drawn and I pass them using the func getWidth(freeBikes: Int, freeDocks: Int) method calling it from the `ViewController. If any other piece of code is needed you can look at the repo.
The problem is that when reusing the cell you call drawRect once again and don't clear the already drawn rect. Clear it before drawing another one.

UITableView with autolayout not smooth when scrolling

I'm using XIB files to design cells in my UITableView. I also use the dequeue mecanism, for instance : let cell = tableView.dequeueReusableCellWithIdentifier("articleCell", forIndexPath: indexPath) as! ArticleTableViewCell. I precalculate all my row height in the viewDidLoad of my ViewController so the method func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat returns instantly the right value. All of this works.
In my UITableViewCell, I'm using many dynamic height labels (lines = 0). The layout is like this :
I don't use transparent background, all my subviews are opaque with a specified background color. I checked with the Color Blended Layers (everything is green) and Color Misaligned Images (nothing is yellow).
Here is my cellForRowAtIndexPath method :
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let tableViewSection = tableViewSectionAtIndex(indexPath.section)
let tableViewRow = tableViewSection.rows.objectAtIndex(indexPath.row) as! TableViewRow
switch tableViewRow.type! {
case TableViewRowType.Article :
let article = tableViewRow.article!
if article.type == TypeArticle.Article {
let cell = tableView.dequeueReusableCellWithIdentifier("articleCell", forIndexPath: indexPath) as! ArticleTableViewCell
return cell
} else {
let cell = tableView.dequeueReusableCellWithIdentifier("chroniqueCell", forIndexPath: indexPath) as! ChroniqueTableViewCell
return cell
}
default:
return UITableViewCell()
}
}
And then, in the willDisplayCell method :
func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
let tableViewSection = tableViewSectionAtIndex(indexPath.section)
let tableViewRow = tableViewSection.rows.objectAtIndex(indexPath.row) as! TableViewRow
let article = tableViewRow.article!
if cell.isKindOfClass(ArticleTableViewCell) {
let cell = cell as! ArticleTableViewCell
cell.delegate = self
cell.article = article
if let imageView = articleImageCache[article.id] {
cell.articleImage.image = imageView
cell.shareControl.image = imageView
} else {
loadArticleImage(article, articleCell: cell)
}
} else {
let cell = cell as! ChroniqueTableViewCell
cell.delegate = self
cell.article = article
if let chroniqueur = article.getChroniqueur() {
if let imageView = chroniqueurImageCache[chroniqueur.id] {
cell.chroniqueurImage.image = imageView
} else {
loadChroniqueurImage(article, articleCell: cell)
}
}
}
}
All my images are downloaded in a background thread, so there is no image loading when scrolling.
The layout is modified in my ArticleTableViewCell when I set the "article" property : cell.article = article :
var article: Article? {
didSet {
updateUI()
}
}
And my updateUI function :
func updateUI() -> Void {
if let article = article {
if let surtitre = article.surtitre {
self.surtitre.text = surtitre.uppercaseString
self.surtitre.setLineHeight(3)
} else {
self.surtitre.hidden = true
}
self.titre.text = article.titre
self.titre.setLineHeight(3)
if let amorce = article.amorce {
self.amorce.text = amorce
self.amorce.setLineHeight(3)
} else {
self.amorce.hidden = true
}
if let section = article.sectionSource {
if section.couleurFoncee != "" {
self.bordureSection.backgroundColor = UIColor(hexString: section.couleurFoncee)
self.surtitre.textColor = UIColor(hexString: section.couleurFoncee)
}
}
}
}
The problem is when setting the label text, this causes the lag. The setLineHeight method transforms the text of the label into an NSAttributedString to specify the line height, but even when removing this code and simply setting the text label, a small lag occurs when displaying a new cell.
If I remove all the label setup code, the cells displays the default label text and the tableview scroll is perfectly smooth, and the heights are correct too. Whenever I set a label text, the lag occurs.
I'm running the app on my iPhone 6s. On the 6s simulator, there's absolutely no lag at all, perfectly smooth.
Any idead? Maybe it's because I'm using a UIStackView to embed my labels? I did it so because it's easier to hide labels when empty, so the other elements are moved up, to avoid having spacing where the empty label is.
I tried many thing but can't get the tableview to scroll smoothly, any help would be appreciated.
All my optimizations made it almost 100% fluid on a 6s device but on a 5s, it's really not smooth. I even don't wanna test on a 4s device! I reached the autolayout performance limit when using multiple multiline labels.
After a deep analysis of the Time Profiler, the result is that dynamic labels height (3 in my case) with constraint between them, and those label have attributed text (used to set the line height, but this is not the bottleneck), it seems like the lag is caused by the UIView::layoutSubviews which renders the labels, update the constraints, etc... This is why what when I don't change the label text, everything is smooth. The only solution here is not to use autolayout and layout de subviews programmatically in layoutSubviews method of a custom UITableViewCell subclass.
For those wondering how to do this, I achieved to make a 100% smooth scroll without autolayout and multiple lables with dynamic height (multiline). Here is my UITableView subclass (I use a base class because I have 2 similar cell types) :
//
// ArticleTableViewCell.swift
//
import UIKit
class ArticleTableViewCell: BaseArticleTableViewCell {
var articleImage = UIImageView()
var surtitre = UILabel()
var titre = UILabel()
var amorce = UILabel()
var bordureTop = UIView()
var bordureLeft = UIView()
var articleImageWidth = CGFloat(0)
var articleImageHeight = CGFloat(0)
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.articleImage.clipsToBounds = true
self.articleImage.contentMode = UIViewContentMode.ScaleAspectFill
self.bordureTop.backgroundColor = UIColor(colorLiteralRed: 219/255, green: 219/255, blue: 219/255, alpha: 1.0)
self.bordureLeft.backgroundColor = UIColor.blackColor()
self.surtitre.numberOfLines = 0
self.surtitre.font = UIFont(name: "Graphik-Bold", size: 11)
self.surtitre.textColor = UIColor.blackColor()
self.surtitre.backgroundColor = self.contentView.backgroundColor
self.titre.numberOfLines = 0
self.titre.font = UIFont(name: "PublicoHeadline-Extrabold", size: 22)
self.titre.textColor = UIColor(colorLiteralRed: 26/255, green: 26/255, blue: 26/255, alpha: 1.0)
self.titre.backgroundColor = self.contentView.backgroundColor
self.amorce.numberOfLines = 0
self.amorce.font = UIFont(name: "Graphik-Regular", size: 12)
self.amorce.textColor = UIColor.blackColor()
self.amorce.backgroundColor = self.contentView.backgroundColor
self.contentView.addSubview(articleImage)
self.contentView.addSubview(surtitre)
self.contentView.addSubview(titre)
self.contentView.addSubview(amorce)
self.contentView.addSubview(bordureTop)
self.contentView.addSubview(bordureLeft)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
if let article = article {
var currentY = CGFloat(0)
let labelX = CGFloat(18)
let labelWidth = fullWidth - 48
// Taille de l'image avec un ratio de 372/243
articleImageWidth = ceil(fullWidth - 3)
articleImageHeight = ceil((articleImageWidth * 243) / 372)
self.bordureTop.frame = CGRect(x: 3, y: 0, width: fullWidth - 3, height: 1)
// Image
if article.imagePrincipale == nil {
self.articleImage.frame = CGRect(x: 0, y: 0, width: 0, height: 0)
self.bordureTop.hidden = false
} else {
self.articleImage.frame = CGRect(x: 3, y: 0, width: self.articleImageWidth, height: self.articleImageHeight)
self.bordureTop.hidden = true
currentY += self.articleImageHeight
}
// Padding top
currentY += 15
// Surtitre
if let surtitre = article.surtitre {
self.surtitre.frame = CGRect(x: labelX, y: currentY, width: labelWidth, height: 0)
self.surtitre.preferredMaxLayoutWidth = self.surtitre.frame.width
self.surtitre.setTextWithLineHeight(surtitre.uppercaseString, lineHeight: 3)
self.surtitre.sizeToFit()
currentY += self.surtitre.frame.height
currentY += 15
} else {
self.surtitre.frame = CGRect(x: 0, y: 0, width: 0, height: 0)
}
// Titre
self.titre.frame = CGRect(x: labelX, y: currentY, width: labelWidth, height: 0)
self.titre.preferredMaxLayoutWidth = self.titre.frame.width
self.titre.setTextWithLineHeight(article.titre, lineHeight: 3)
self.titre.sizeToFit()
currentY += self.titre.frame.height
// Amorce
if let amorce = article.amorce {
currentY += 15
self.amorce.frame = CGRect(x: labelX, y: currentY, width: labelWidth, height: 0)
self.amorce.preferredMaxLayoutWidth = self.amorce.frame.width
self.amorce.setTextWithLineHeight(amorce, lineHeight: 3)
self.amorce.sizeToFit()
currentY += self.amorce.frame.height
} else {
self.amorce.frame = CGRect(x: 0, y: 0, width: 0, height: 0)
}
// Boutons
currentY += 9
self.updateButtonsPosition(currentY)
self.layoutUpdatedAt(currentY)
currentY += self.favorisButton.frame.height
// Padding bottom
currentY += 15
// Couleurs
self.bordureLeft.frame = CGRect(x: 0, y: 0, width: 3, height: currentY - 2)
if let section = article.sectionSource {
if let couleurFoncee = section.couleurFoncee {
self.bordureLeft.backgroundColor = couleurFoncee
self.surtitre.textColor = couleurFoncee
}
}
// Mettre à jour le frame du contentView avec la bonne hauteur totale
var frame = self.contentView.frame
frame.size.height = currentY
self.contentView.frame = frame
}
}
}
And the base class :
//
// BaseArticleTableViewCell.swift
//
import UIKit
class BaseArticleTableViewCell: UITableViewCell {
var backgroundThread: NSURLSessionDataTask?
var delegate: SectionViewController?
var favorisButton: FavorisButton!
var shareButton: ShareButton!
var updatedAt: UILabel!
var fullWidth = CGFloat(0)
var article: Article? {
didSet {
// Update du UI quand on set l'article
updateArticle()
}
}
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.selectionStyle = UITableViewCellSelectionStyle.None
self.contentView.backgroundColor = UIColor(colorLiteralRed: 248/255, green: 248/255, blue: 248/255, alpha: 1.0)
// Largeur de la cellule, qui est toujours plein écran dans notre cas
// self.contentView.frame.width ne donne pas la bonne valeur tant que le tableView n'a pas été layouté
fullWidth = UIScreen.mainScreen().bounds.width
self.favorisButton = FavorisButton(frame: CGRect(x: fullWidth - 40, y: 0, width: 28, height: 30))
self.shareButton = ShareButton(frame: CGRect(x: fullWidth - 73, y: 0, width: 28, height: 30))
self.updatedAt = UILabel(frame: CGRect(x: 18, y: 0, width: 0, height: 0))
self.updatedAt.font = UIFont(name: "Graphik-Regular", size: 10)
self.updatedAt.textColor = UIColor(colorLiteralRed: 138/255, green: 138/255, blue: 138/255, alpha: 1.0)
self.updatedAt.backgroundColor = self.contentView.backgroundColor
self.addSubview(self.favorisButton)
self.addSubview(self.shareButton)
self.addSubview(self.updatedAt)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// Avant qu'une cell soit réutilisée, faire un cleanup
override func prepareForReuse() {
super.prepareForReuse()
// Canceller un background thread si y'en a un actif
if let backgroundThread = self.backgroundThread {
backgroundThread.cancel()
self.backgroundThread = nil
}
resetUI()
}
// Updater le UI
func updateArticle() {
self.favorisButton.article = article
self.shareButton.article = article
if let delegate = self.delegate {
self.shareButton.delegate = delegate
}
}
// Faire un reset du UI avant de réutiliser une instance de Cell
func resetUI() {
}
// Mettre à jour la position des boutons
func updateButtonsPosition(currentY: CGFloat) {
// Déjà positionnés en X, width, height, reste le Y
var shareFrame = self.shareButton.frame
shareFrame.origin.y = currentY
self.shareButton.frame = shareFrame
var favorisFrame = self.favorisButton.frame
favorisFrame.origin.y = currentY + 1
self.favorisButton.frame = favorisFrame
}
// Mettre à jour la position du updatedAt et son texte
func layoutUpdatedAt(currentY: CGFloat) {
var frame = self.updatedAt.frame
frame.origin.y = currentY + 15
self.updatedAt.frame = frame
if let updatedAt = article?.updatedAtListe {
self.updatedAt.text = updatedAt
} else {
self.updatedAt.text = ""
}
self.updatedAt.sizeToFit()
}
}
On the viewDidLoad of my ViewController, i pre-calculate all the row height :
// Créer une cache des row height des articles
func calculRowHeight() {
self.articleRowHeights = [Int: CGFloat]()
// Utiliser une seule instance de chaque type de cell
let articleCell = tableView.dequeueReusableCellWithIdentifier("articleCell") as! BaseArticleTableViewCell
let chroniqueCell = tableView.dequeueReusableCellWithIdentifier("chroniqueCell") as! BaseArticleTableViewCell
var cell: BaseArticleTableViewCell!
for articleObj in section.articles {
let article = articleObj as! Article
// Utiliser le bon type de cell
if article.type == TypeArticle.Article {
cell = articleCell
} else {
cell = chroniqueCell
}
// Setter l'article et refaire le layout
cell.article = article
cell.layoutSubviews()
// Prendre la hauteur générée
self.articleRowHeights[article.id] = cell.contentView.frame.height
}
}
Set the row height for the requested cell :
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
let tableViewSection = tableViewSectionAtIndex(indexPath.section)
let tableViewRow = tableViewSection.rows.objectAtIndex(indexPath.row) as! TableViewRow
switch tableViewRow.type! {
case TableViewRowType.Article :
let article = tableViewRow.article!
return self.articleRowHeights[article.id]!
default:
return UITableViewAutomaticDimension
}
}
Return the cell in cellForRowAtIndexPath (I have multiple cell types in my tableView, so there's a couple of checks to do) :
// Cellule pour un section/row spécifique
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let tableViewSection = tableViewSectionAtIndex(indexPath.section)
let tableViewRow = tableViewSection.rows.objectAtIndex(indexPath.row) as! TableViewRow
switch tableViewRow.type! {
case TableViewRowType.Article :
let article = tableViewRow.article!
if article.type == TypeArticle.Article {
let cell = tableView.dequeueReusableCellWithIdentifier("articleCell", forIndexPath: indexPath) as! ArticleTableViewCell
cell.delegate = self
cell.article = article
if let imageView = articleImageCache[article.id] {
cell.articleImage.image = imageView
cell.shareButton.image = imageView
} else {
cell.articleImage.image = placeholder
loadArticleImage(article, articleCell: cell)
}
return cell
}
return UITableViewCell()
default:
return UITableViewCell()
}
}

UITableview load cell longtime Swift - Need advise tuning

Please kindly refer code in attach and refer in the image as well. I need your advise how can I make quicker for loading
http://imageshack.com/a/img901/7079/mOQkum.png
http://imageshack.com/a/img909/2775/E2zTKs.png
override func tableView(tableView: UITableView?, cellForRowAtIndexPath indexPath: NSIndexPath?) -> UITableViewCell {
let wishRow = self.wishDataList.objectAtIndex(indexPath!.row) as! NSMutableArray
println(userImageList.count)
switch (wishRow).count{
case 4:
let cell:WishHeaderTableViewCell = tableView!.dequeueReusableCellWithIdentifier("CellHeader", forIndexPath: indexPath!) as! WishHeaderTableViewCell
dispatch_async(dispatch_get_main_queue(), { () -> Void in
let dateFormatter = NSDateFormatter()
cell.LblCity.text = wishRow[1] as? String
cell.ImgAddWish.layer.cornerRadius = 20
cell.ImgAddWish.clipsToBounds = true
cell.ImgAddWish.autoresizesSubviews = false
// cell.ImageViewTripOwner.layer.cornerRadius = 20
// cell.ImageViewTripOwner.clipsToBounds = true
cell.ImageViewTripOwner.autoresizesSubviews = false
for object in self.userImageList{
if object[0] as? String == (wishRow[3] as? PFUser)?.objectId{
cell.ImageViewTripOwner.image = UIImage(data: (object[1] as! PFFile).getData()!)
//SharedFunction.imageResize(UIImage(data: (object[1] as PFFile).getData())!, sizeChange: CGSize(width: 40, height: 40))
}
}
})
return cell
default:
let cell:WishItemTableViewCell = tableView!.dequeueReusableCellWithIdentifier("CellItem", forIndexPath: indexPath!) as! WishItemTableViewCell
dispatch_async(dispatch_get_main_queue(), { () -> Void in
cell.LblItemName.text = wishRow[5] as? String
cell.ImgItem.image = wishRow[7] as? UIImage
//UIImage(data: (wishRow[7] as PFFile).getData())
//SharedFunction.imageResize(UIImage(data: (wishRow[7] as PFFile).getData())!, sizeChange: CGSize(width: 40, height: 40))
cell.ImgRequestorItem.layer.cornerRadius = 10
cell.ImgRequestorItem.clipsToBounds = true
cell.ImgRequestorItem.autoresizesSubviews = false
cell.ImgRequestorItem.backgroundColor = UIColor.whiteColor()
for object in self.userImageList{
if object[0] as? String == wishRow[8] as? String{
let realImage = UIImageView(frame: CGRect(x: 1.5, y: 1.5, width: 17, height: 17))
realImage.layer.cornerRadius = 8.5
realImage.clipsToBounds = true
dispatch_async(dispatch_get_main_queue(), { () -> Void in
realImage.image = UIImage(data: (object[1] as! PFFile).getData()!)
})
//SharedFunction.imageResize(UIImage(data: (object[1] as PFFile).getData())!, sizeChange: CGSize(width: 17, height: 17))
cell.ImgRequestorItem.addSubview(realImage)
}
}
cell.ImgBackgroundTick.layer.cornerRadius = 7.5
cell.ImgBackgroundTick.clipsToBounds = true
cell.ImgBackgroundTick.autoresizesSubviews = false
cell.ImgBackgroundTick.backgroundColor = UIColor.whiteColor()
let realImage = UIImageView(frame: CGRect(x: 1.5, y: 1.5, width: 12, height: 12))
realImage.layer.cornerRadius = 6
realImage.clipsToBounds = true
realImage.backgroundColor = UIColor(red: 0.450, green: 0.419, blue: 0.321 , alpha: 1)
realImage.image = UIImage(named: "ic_tick")
cell.ImgBackgroundTick.addSubview(realImage)
})
return cell
}
}
Wow, why are you using dispatch_async(dispatch_get_main_queue(), { () -> Void in in cell creation? You don't have to. Remove this method so the code should look like the following:
override func tableView(tableView: UITableView?, cellForRowAtIndexPath indexPath: NSIndexPath?) -> UITableViewCell {
let wishRow = self.wishDataList.objectAtIndex(indexPath!.row) as! NSMutableArray
println(userImageList.count)
switch (wishRow).count{
case 4:
let cell = tableView!.dequeueReusableCellWithIdentifier("CellHeader", forIndexPath: indexPath!) as! WishHeaderTableViewCell
let dateFormatter = NSDateFormatter()
cell.LblCity.text = wishRow[1] as? String
cell.ImgAddWish.layer.cornerRadius = 20
cell.ImgAddWish.clipsToBounds = true
cell.ImgAddWish.autoresizesSubviews = false
// cell.ImageViewTripOwner.layer.cornerRadius = 20
// cell.ImageViewTripOwner.clipsToBounds = true
cell.ImageViewTripOwner.autoresizesSubviews = false
for object in self.userImageList{
if object[0] as? String == (wishRow[3] as? PFUser)?.objectId{
cell.ImageViewTripOwner.image = UIImage(data: (object[1] as! PFFile).getData()!)
//SharedFunction.imageResize(UIImage(data: (object[1] as PFFile).getData())!, sizeChange: CGSize(width: 40, height: 40))
}
}
return cell
default:
let cell = tableView!.dequeueReusableCellWithIdentifier("CellItem", forIndexPath: indexPath!) as! WishItemTableViewCell
cell.LblItemName.text = wishRow[5] as? String
cell.ImgItem.image = wishRow[7] as? UIImage
//UIImage(data: (wishRow[7] as PFFile).getData())
//SharedFunction.imageResize(UIImage(data: (wishRow[7] as PFFile).getData())!, sizeChange: CGSize(width: 40, height: 40))
cell.ImgRequestorItem.layer.cornerRadius = 10
cell.ImgRequestorItem.clipsToBounds = true
cell.ImgRequestorItem.autoresizesSubviews = false
cell.ImgRequestorItem.backgroundColor = UIColor.whiteColor()
for object in self.userImageList{
if object[0] as? String == wishRow[8] as? String{
let realImage = UIImageView(frame: CGRect(x: 1.5, y: 1.5, width: 17, height: 17))
realImage.layer.cornerRadius = 8.5
realImage.clipsToBounds = true
dispatch_async(dispatch_get_main_queue(), { () -> Void in
realImage.image = UIImage(data: (object[1] as! PFFile).getData()!)
})
//SharedFunction.imageResize(UIImage(data: (object[1] as PFFile).getData())!, sizeChange: CGSize(width: 17, height: 17))
cell.ImgRequestorItem.addSubview(realImage)
}
}
cell.ImgBackgroundTick.layer.cornerRadius = 7.5
cell.ImgBackgroundTick.clipsToBounds = true
cell.ImgBackgroundTick.autoresizesSubviews = false
cell.ImgBackgroundTick.backgroundColor = UIColor.whiteColor()
let realImage = UIImageView(frame: CGRect(x: 1.5, y: 1.5, width: 12, height: 12))
realImage.layer.cornerRadius = 6
realImage.clipsToBounds = true
realImage.backgroundColor = UIColor(red: 0.450, green: 0.419, blue: 0.321 , alpha: 1)
realImage.image = UIImage(named: "ic_tick")
cell.ImgBackgroundTick.addSubview(realImage)
return cell
}}

Resources