ios - UITableView with autolayout not smooth when scrolling -
i'm using xib files design cells in uitableview. use dequeue mecanism, instance : let cell = tableview.dequeuereusablecellwithidentifier("articlecell", forindexpath: indexpath) as! articletableviewcell
. precalculate row height in viewdidload
of viewcontroller method func tableview(tableview: uitableview, heightforrowatindexpath indexpath: nsindexpath) -> cgfloat
returns instantly right value. of works.
in uitableviewcell, i'm using many dynamic height labels (lines = 0). layout :
i don't use transparent background, subviews opaque specified background color. checked color blended layers
(everything green) , color misaligned images
(nothing yellow).
here 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 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 images downloaded in background thread, there no image loading when scrolling.
the layout modified in articletableviewcell
when set "article" property : cell.article = article
:
var article: article? { didset { updateui() } }
and 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 when setting label text, causes lag. setlineheight
method transforms text of label nsattributedstring specify line height, when removing code , setting text label, small lag occurs when displaying new cell.
if remove label setup code, cells displays default label text , tableview scroll smooth, , heights correct too. whenever set label text, lag occurs.
i'm running app on iphone 6s. on 6s simulator, there's absolutely no lag @ all, smooth.
any idead? maybe it's because i'm using uistackview embed labels? did because it's easier hide labels when empty, other elements moved up, avoid having spacing empty label is.
i tried many thing can't tableview scroll smoothly, appreciated.
all optimizations made 100% fluid on 6s device on 5s, it's not smooth. don't wanna test on 4s device! reached autolayout performance limit when using multiple multiline labels.
after deep analysis of time profiler, result dynamic labels height (3 in case) constraint between them, , label have attributed text (used set line height, not bottleneck), seems lag caused uiview::layoutsubviews renders labels, update constraints, etc... why when don't change label text, smooth. solution here not use autolayout , layout de subviews programmatically in layoutsubviews method of custom uitableviewcell subclass.
for wondering how this, achieved make 100% smooth scroll without autolayout , multiple lables dynamic height (multiline). here uitableview subclass (i use base class because 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 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 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 viewdidload of viewcontroller, pre-calculate 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! 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 row height 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 cell in cellforrowatindexpath
(i have multiple cell types in tableview, there's couple of checks 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() } }
Comments
Post a Comment