[英]UITableView with UITableViewCells + AutoLayout - Not As Smooth As It *Should* Be
[英]UITableView with autolayout not smooth when scrolling
我正在使用XIB文件來設計UITableView中的單元格。 我也使用dequeue mecanism,例如: let cell = tableView.dequeueReusableCellWithIdentifier("articleCell", forIndexPath: indexPath) as! ArticleTableViewCell
let cell = tableView.dequeueReusableCellWithIdentifier("articleCell", forIndexPath: indexPath) as! ArticleTableViewCell
。 我在我的ViewController的viewDidLoad
中預先計算了我的所有行高,所以方法func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat
立即返回正確的值。 所有這一切都有效。
在我的UITableViewCell中,我使用了許多動態高度標簽(行= 0)。 布局是這樣的:
我沒有使用透明背景,我的所有子視圖都是不透明的,具有指定的背景顏色。 我檢查了Color Blended Layers
(一切都是綠色)和Color Misaligned Images
(沒有黃色)。
這是我的cellForRowAtIndexPath方法:
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()
}
}
然后,在willDisplayCell方法中:
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)
}
}
}
}
我的所有圖像都是在后台線程中下載的,因此滾動時不會加載圖像。
當我設置“article”屬性時,在我的ArticleTableViewCell
修改了布局: cell.article = article
:
var article: Article? {
didSet {
updateUI()
}
}
我的updateUI函數:
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)
}
}
}
}
問題是在設置標簽文本時,這會導致延遲。 setLineHeight
方法將標簽的文本轉換為NSAttributedString以指定行高,但即使刪除此代碼並僅設置文本標簽,在顯示新單元格時也會出現小的延遲。
如果我刪除所有標簽設置代碼,單元格將顯示默認標簽文本,並且tableview滾動非常流暢,高度也是正確的。 每當我設置標簽文本時,都會出現滯后。
我在iPhone 6s上運行應用程序。 在6s模擬器上,完全沒有任何延遲,非常流暢。
任何想法? 也許是因為我正在使用UIStackView來嵌入我的標簽? 我這樣做是因為在空的時候隱藏標簽會更容易,所以其他元素向上移動,以避免空標簽的間距。
我嘗試了很多東西,但無法讓tableview順利滾動,任何幫助都將不勝感激。
我所有的優化使其在6s設備上幾乎100%流暢,但在5s時,它真的不順暢。 我甚至不想在4s設備上進行測試! 使用多個多行標簽時,我達到了自動布局性能限制。
在對Time Profiler進行深入分析之后,結果是動態標簽高度(在我的情況下為3),它們之間有約束,而那些標簽已經歸因於文本(用於設置行高,但這不是瓶頸),它似乎延遲是由UIView :: layoutSubviews引起的,它會渲染標簽,更新約束等等......這就是為什么當我不更改標簽文本時,一切都很順利。 這里唯一的解決方案是不在自定義UITableViewCell子類的layoutSubviews方法中以編程方式使用autolayout和layout de子視圖。
對於那些想知道如何做到這一點的人,我實現了100%平滑的滾動而沒有自動布局和多個帶動態高度的標簽(多線)。 這是我的UITableView子類(我使用基類,因為我有2個類似的單元格類型):
//
// 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
}
}
}
和基類:
//
// 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()
}
}
在我的ViewController的viewDidLoad上,我預先計算了所有行高:
// 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
}
}
設置所請求單元格的行高:
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
}
}
返回cellForRowAtIndexPath
的單元格(我的tableView中有多個單元格類型,因此需要進行幾項檢查):
// 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()
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.