2016-07-15 64 views
2

我使用XIB文件來設計我的UITableView中的單元格。我也使用dequeue mecanism,例如:let cell = tableView.dequeueReusableCellWithIdentifier("articleCell", forIndexPath: indexPath) as! ArticleTableViewCell。我預先計算了我的ViewController的viewDidLoad中的所有行高,因此方法func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat立即返回正確的值。所有這些工作。滾動時UITableView與自動佈局不平滑

在我的UITableViewCell中,我使用了許多動態高度標籤(lines = 0)。佈局是這樣的:

enter image description here

我不使用透明背景,我所有的子視圖是不透明的,用指定的背景顏色。我檢查了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) 
      } 
     } 
    } 
} 

我所有的圖片在後臺線程下載,所以滾動時沒有圖像加載。

的佈局是在我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模擬器上,絕對沒有滯後,非常平滑。

任何idead?也許是因爲我使用UIStackView來嵌入我的標籤?我這樣做是因爲在空標籤時隱藏標籤更容易,所以其他元素向上移動,以避免在空標籤處留有間距。

我嘗試了很多事情,但不能得到tableview滾動順利,任何幫助將不勝感激。

+0

謝謝你的提示。我問,因爲可能有人有同樣的問題,這會對我有很大的幫助。無論如何,我會試着找出儀器。 – Tiois

+0

順便說一句,滯後不是在幾秒鐘內......但是當滾動緩慢時,您可以看到滾動在下一個表格出現之前不是流體。希望儀器公司會幫我弄清楚,我還沒有使用的工具。我應該使用什麼工具工具? – Tiois

+0

我做了一些優化。我調整了我的圖像大小,使UIImage具有與UIImageView相同的大小。我在prepareForReuse方法中在我的UIImageView中加載了一個臨時佔位符UIImage,因此如果一個單元沒有要顯示的圖像,它將顯示此佔位符。這張圖片是從「磁盤」加載的,造成了一些延遲。我現在使用相同的UIImage實例來避免這種情況。現在,唯一的問題是,當從NIB創建一個新的單元(沒有要出隊的單元)時,會有一個小滯後,當有足夠的單元出隊時這個滯後不存在。有任何解決這個問題的方法嗎? – Tiois

回答

2

我所有的優化都使它在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() 
    } 

} 

在我的視圖控制器的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() 

    } 
} 
+0

嗨,我有一個類似的問題,即使自動佈局,圖像背景加載和壓縮,滾動並不平滑。但是,如果預先計算所有細胞,並且如果有很多細胞(如數千個細胞),那麼表的初始負荷是不是會很慢? – dickyj

+0

在這種情況下,我會預先計算當前顯示的單元格的10個下一個單元格。 – Tiois

+0

但是,一旦您計算了接下來的10個單元,會不會影響垂直滾動條的位置?我的意思是這可能會導致再次跳動? – dickyj