美文网首页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