美文网首页iOS Developer
iOS带缓存横向ScrollView

iOS带缓存横向ScrollView

作者: Jimmy木 | 来源:发表于2017-06-02 22:01 被阅读558次

经常需要用到横向滑动的控件,控件里面也有一个个Cell,就像横向的UITableView。发现网上也有很多横向UITableView的方案,甚至通过旋转UITableView来实现横向滑动。


光酱

  我通常是通过UIScrollView来实现横向滑动,但是一旦加载内容过多就会出现卡顿。因此我通过添加界面缓存来避免卡顿的问题。还通过模仿UITableView的dataSource来实现面向协议编程。

实现步骤

自定义控件HMHorizontalScrollView。首先通过包含UIScrollView实现横向滑动。通过添加HMHorizontalScrollCell实现内容填充。设立一个Cell的缓存数组,显示复用缓存数组中的Cell。

缓存数组的内容需要根据滑动的位置进行位置更新。缓存的显示会根据ScrollView的滑动进行改变,保证在滑动到下一个Cell之前,已经有Cell在那个位置等待显示了。

最后实现协议编程,通过配置DataSource来传递界面显示配置,通过协议的返回值设置显示内容。通过Delegate首先点击事件。

类图

缓存原理

定义一个数组,将显示界面放入数组中。每次滑动的时候重新计算缓存界面的显示位置,改变未显示出来的界面的位置。

假如一个屏幕可以显示3个Cell的话,可能最多会显示4个Cell,这个时候就需要在前后多加一个Cell,用来预加载界面,这样在快速滑动的时候就会更顺畅。通过不断更新这些显示的Cell的位置和信息,就可以完成缓存机制。

示意图

当向左滑时,当4号Cell滑出屏幕时,就把5号Cell移到第一个的位置。
  当向右滑时,当1号Cell滑出屏幕时,就把0号Cell移到最后一个的位置。
  当滑到最左边或最右边时,就保持数据原始的顺序,不再做改变。

extension HMHorizontalScrollView: UIScrollViewDelegate {

func scrollViewDidScroll(_ scrollView: UIScrollView) {
    // 第一次次进来会可能向下滑动64
    scrollView.contentOffset.y = 0
    
    if numberOfCacheView < maxNumberOfCacheView {
        return
    }
    
    if scrollView.contentOffset.x < 0 {
        // 滑到第一个
        resetItemViewOfStart()
        
    } else if scrollView.contentOffset.x + scrollView.w > scrollView.contentSize.width {
        // 滑到最后一个
        resetItemViewOfEnd()
        
    } else if cells[1].frame.origin.x + cells[0].w + separatorWidth < scrollView.contentOffset.x  {
        // 左滑
        resetItemViewOfLeftPan()
        // 更新最后一个显示
        let lastCellIndex = Int((cells.last?.frame.origin.x ?? 0) / (cellSize.width + separatorWidth))
        if lastCellIndex > 0 && lastCellIndex < numberOfCells {
            _ = dataSource?.horizontalScrollView(in: self, cellAt: lastCellIndex)
        }
        
    } else if cells[numberOfCacheView - 2].frame.origin.x > scrollView.contentOffset.x + scrollView.frame.width {
        // 右滑
        resetItemViewOfRightPan()
        // 更新第一个显示
        let firstCellIndex = Int((cells.first?.frame.origin.x ?? 0) / (cellSize.width + separatorWidth))
        if firstCellIndex > 0 && firstCellIndex < numberOfCells {
            _ = dataSource?.horizontalScrollView(in: self, cellAt: firstCellIndex)
        }
    }
    
}

// 滑到第一个
func resetItemViewOfStart() {
    
    hm_for(cells) { cell, index in
        
        cell.x = separatorWidth + (cellSize.width + separatorWidth) * CGFloat(index)
        
        if let index = indexOf(x: cell.x) {
            _ = dataSource?.horizontalScrollView(in: self, cellAt: index)
        }
    }
}

// 滑到最后一个
func resetItemViewOfEnd() {
    
    hm_for(cells) { cell, index in
        cell.x = scrollView.contentSize.width - (cellSize.width + separatorWidth) * CGFloat(numberOfCacheView - index)
        
        if let index = indexOf(x: cell.x) {
            _ = dataSource?.horizontalScrollView(in: self, cellAt: index)
        }
    }
}

// 左滑
func resetItemViewOfLeftPan() {
    
    let temp = cells.first!
    temp.x = cells.last!.x + (cellSize.width + separatorWidth)
    
    hm_for(cells) { (cell, index) in
        if index < cells.count - 1 {
            cells[index] = cells[index + 1]
        } else {
            cells[index] = temp
        }
    }
}

// 右滑
func resetItemViewOfRightPan() {
    
    let temp = cells.last!
    temp.x = cells.first!.x - (cellSize.width + separatorWidth)
    
    hm_for(cells) { (cell, index) in
        if index < cells.count - 1 {
            cells[cells.count - index - 1] = cells[cells.count - index - 2]
        } else {
            cells[cells.count - index - 1] = temp
        }
    }
}

func indexOf(x: CGFloat) -> Index? {
    
    if cellSize.width + separatorWidth != 0 {
        return Int(x / (cellSize.width + separatorWidth))
    }
    
    return nil
}
}

面向协议编程

定义一个HMHorizontalScrollViewDataSource协议,这个协议主要是为了获取数据源信息。设置一个dataSource的委托,通过委托在实现界面配置数据。当需要某些数据的时候,dataSource实现协议获取返回值,返回值就是需要的数据。

  • 协议

    protocol HMHorizontalScrollViewDataSource:     NSObjectProtocol {
    // cell数量
    func numberOfCells(in horizontalScrollView: HMHorizontalScrollView) -> Int
    // 自身的宽度
    func viewWidth(in horizontalScrollView: HMHorizontalScrollView) -> CGFloat
    // cell的宽度
    func horizontalScrollView(_ horizontalScrollView: HMHorizontalScrollView, cellSizeAt index: Index) -> CGSize
    // 返回cell
    func horizontalScrollView(in horizontalScrollView: HMHorizontalScrollView, cellAt index: Index) -> HMHorizontalScrollCell
    }
    
  • 协议使用

    // cell的数量
    fileprivate var numberOfCells: Int {
      return dataSource?.numberOfCells(in: self) ?? 0
    }
    // cell尺寸
    fileprivate var cellSize: CGSize {
      return dataSource?.horizontalScrollView(self, cellSizeAt: 0) ?? CGSize()
    }
    // 界面宽度
    fileprivate var viewWidth: CGFloat {
      return dataSource?.viewWidth(in: self) ?? 0
    }
    

当显示界面的Cell很少时,就不需要缓存了,需要特殊处理。

当ScrollView滑动到边缘时,也需要重新计算Cell排布,不然隐藏的缓存Cell就会显示出来。

需要考虑边缘条件。

通过dataSource配置HMHorizontialView的数据源,HMHorizontialView内部通过复用显示Cell,可以在一次加载多个Cell而不卡顿。
  UIScollView可以实现横向滑动的分页显示,但是一次加载过多的内容会出现卡顿,通过复用显示的界面来避免卡顿。并通过仿照UITableView的
  dataSource实现面向协议的编程。

最后有木有人推荐个工作机会!

Demo地址:
https://github.com/hm306599934/HMHorizontalScrollView/tree/master

相关文章

网友评论

    本文标题:iOS带缓存横向ScrollView

    本文链接:https://www.haomeiwen.com/subject/pborfxtx.html