什么是Twitter的流畅UITableView滚动技巧?

时间:2016-10-18 15:26:21

标签: ios swift uitableview

我在改进UITableView的滚动方面遇到了麻烦,该UITableView的自定义单元格使用autolayout进行布局。我有两种不同高度的自定义UITableViewCell(一个大约200px一个大约500px)我尝试了很多东西,包括:

  1. UITableViewCells的缓存高度
  2. 使用自动尺寸和估计行高
  3. 计算UITableViewCells的高度(这可能需要一些调查,任何人都有链接以正确的方式使用自定义tableviewcell执行此操作?)
  4. 使用不透明视图
  5. 尝试使用乐器来查看减速发生的位置
  6. 将代码从cellForRowAt移至willDisplayCell
  7. 异步缓存和显示属性文本。
  8. 异步缓存和显示图像。
  9. 以及许多声称可提高滚动速度的在线指南。但是,滚动仍然是生涩的!

    此外,当我尝试滚动到顶部单元格时,它有时无法到达顶部。我猜估计的细胞高度与此有关,但由于细胞高度不同,很难做到正确。

    仪器建议当我将文本设置到我的单元格的标签(即调整单元格高度的位)时,我的代码会变慢。

    我试图弄清楚Twitter应用程序(具有与我的UITableView基本相同的功能,具有属性文本和图像等)如何具有如此好的滚动功能。我从他们那里看到了一篇博文,说明了如何使用drawRect,但是,我很确定他们现在不会这样做。任何人都对如何实现60fps平滑滚动有任何见解?

    我的代码

    首先,我的两个表格单元格具有以下结构:

    表格单元格1:

     - UITableViewCell
         - UIView (content view)
             - UIView (Shadow view)
                 - UIView (Card background)
                     - UIView x 3
                     - UIButton x 5
                     - UILabel x 2
                     - UITextView
    

    我拥有名为shadow viewcard background的UIViews的原因是因为我向shadowPath添加了shadow view的{​​{1}}层。然后我向UIBezierPath添加一个半径。这两个不能在同一层上完成,因为半径仅在两个角中,并且在尝试同时具有阴影和半径时会出现问题。

    表格单元格2:

    card background

    这使用相同的代码添加阴影和圆角。

    要获得行的估计高度,请使用以下代码:

     - UITableViewCell
         - UIView (content view)
             - UIView (Shadow view)
                 - UIView (Card background)
                     - UIView x 3
                     - UIButton x 5
                     - UILabel x 3
                     - UITextView
                     - UIImageView x 2
    

    在我的UITableViewController中的func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat { let type = model.dataContent[indexPath.row]["type"].stringValue if type == MediaType.text.rawValue { return 150 } else { return 500 } } 内,我有以下代码:

    cellForRowAt

    当我设置两个UITableViewCells的indexPath时,会在自定义单元格类中调用此代码来更新单元格:

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        // Create the custom cell
        let mediaTableCellIdentifier = "MediaCell"
        let textTableCellIdentifier = "TextCell"
    
        let type = MediaType(rawValue: model.dataContent[indexPath.row]["type"].stringValue)
    
        var cell: TableCell!
        if type == .text {
            cell = tableView.dequeueReusableCell(withIdentifier: textTableCellIdentifier, for: indexPath) as! TableTextCell
            if flowState == .individualPost {
                (cell as! TableTextCell).expandView()
            }
        } else {
            cell = tableView.dequeueReusableCell(withIdentifier: mediaTableCellIdentifier, for: indexPath) as! TableMediaCell
            (cell as! TableMediaCell).postImageView.image = nil
        }
    
        cell.delegate = self
    
        if type == .image {
            let cell = cell as! TableMediaCell
            let urlString = model.dataContent[indexPath.row]["img"]["loc"].stringValue
    
            // Check if we have an image stored in our cache for the image URL. If not, download it.
            if let cellImage = model.imageCache[urlString] {
                cell.postImageView.image = cellImage
            }
            else {
                model.downloadImage(
                    atIndexPath: indexPath,
                    type: type!,
                    progress: { percentage in
                        cell.progressView.isHidden = false
                        cell.progressView.updateCirclePath(percentage: percentage)
                    },
                    completion: { [weak self] error, image in
                        if let strongSelf = self {
                            if error == nil {
                                // Store the image in to our cache
                                strongSelf.model.imageCache[urlString] = image
                                // Update the cell with our image
                                if let cellToUpdate = tableView.cellForRow(at: indexPath) as? TableMediaCell {
                                    cellToUpdate.postImageView.image = image
                                    cellToUpdate.progressView.resetCircleAnimation()
                                    cellToUpdate.progressView.isHidden = true
                                }
                            }
                            else {
                                print("Error downloading image: \(error!.localizedDescription)")
                            }
                        }
                    }
                )
            }
        } else if type == .video {
            let cell = cell as! TableMediaCell
            let urlString = "\(CloudFrontURL.video.rawValue)/\(model.dataContent[indexPath.row]["video"]["loc"].stringValue)"
            if let cellVideo = model.videoCache[urlString] {
                cell.videoPlayer = AVPlayer(playerItem: AVPlayerItem(asset: cellVideo))
                cell.videoPlayer!.actionAtItemEnd = AVPlayerActionAtItemEnd.none
                cell.videoView.playerLayer.player = cell.videoPlayer
            } else {
                let videoURL = URL(string: urlString)
    
                cell.videoPlayer = AVPlayer(url: videoURL!)
                model.videoCache[urlString] = cell.videoPlayer?.currentItem?.asset
                cell.videoPlayer!.actionAtItemEnd = AVPlayerActionAtItemEnd.none
    
                cell.videoView.playerLayer.player = cell.videoPlayer
            }
        }
    
        cell.caption.text = model.dataContent[indexPath.row]["caption"].stringValue
        cell.caption.isHidden = true
        model.retrieveCaption(at: indexPath, forCell: cell, withFont: cell.caption.font!) { (caption, cellToUpdate) in
            if let theCell = cellToUpdate {
                theCell.caption.attributedText = caption
                theCell.caption.isHidden = false
            }
        }
    
        cell.commentCount.text = model.dataContent[indexPath.row]["commentsCount"].stringValue
    
        // Enable/Disable the controls based on our settings
        cell.controlsEnabled = controlsEnabled
        // Provide each cell with our data
        cell.model = model
        // Setting this makes the controls update its data automatically.
        cell.indexPath = indexPath
    
        // If we've reached the last post in our loaded data, get the next page of posts (unless it's an individual post).
        if (indexPath.row == model.dataContent.count - 3 && !lastItemReached && flowState != .individualPost) {
            stateMachine.enterState(.retrievingNextPage)
        }
    
        return cell
    }
    

1 个答案:

答案 0 :(得分:0)

UITableView显示速度缓慢的一个非常明显的原因是您的cellForRowAtIndexPath太长了。您需要重构代码。保持Cell声明本身简短。

最佳做法是在cellForRowAtIndexPath声明Cell并调用一个用内容填充Cell的bindData函数。例如:

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

    if indexPath.section == 0 {

        let cell = tableView.dequeueReusableCellWithIdentifier("cell") as! Cell

        let content = contentArray[indexPath.row]
        cell.bindData(content)

        return cell
    }
}

extension Cell {
    bindData(content: ContentStruct) {
         label.text = content.name
    }   
}

同样在您的情况下,您应该在设置内容的函数和设置属性的函数之间进行重构。保持每个功能更短。

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

    if indexPath.section == 0 {

        let cell = tableView.dequeueReusableCellWithIdentifier("cell") as! Cell

        let content = contentArray[indexPath.row]
        cell.bindData(content)
        cell.setProperties

        return cell
    }
}

extension Cell {
    bindData(content: ContentStruct) {
         label.text = content.name
    }

    setProperties() {

        // set stuff like:
        // Enable/Disable the controls based on our settings
        cell.controlsEnabled = controlsEnabled
        // Provide each cell with our data
        cell.model = model
        // Setting this makes the controls update its data automatically.
        cell.indexPath = indexPath

        // If we've reached the last post in our loaded data, get the next page of posts (unless it's an individual post).
        if (indexPath.row == model.dataContent.count - 3 && !lastItemReached && flowState != .individualPost) {
            stateMachine.enterState(.retrievingNextPage)
        }
    }   
}