iOS带缓存横向ScrollView

经常需要用到横向滑动的控件,控件里面也有一个个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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,802评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,109评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,683评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,458评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,452评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,505评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,901评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,550评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,763评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,556评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,629评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,330评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,898评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,897评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,140评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,807评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,339评论 2 342

推荐阅读更多精彩内容

  • ## iOS常用问题总结#### iOS基础知识回顾##### 1、为什么说Objective-C是一门动态的语言...
    蝉始鸣阅读 458评论 0 3
  • 作者si1ence2016.05.20 10:24* http://www.jianshu.com/p/bc3f8...
    Kiddz阅读 1,173评论 0 12
  • 为了写这篇文章,早上九点半刚过,我就在金科世界走廊拍下了这张照片。在横店电影城门口贴着一张海报,宣传今天播放的电影...
    若雨菲竹阅读 97评论 0 0
  • 第一步:认知(工具:生命数字。)3号下午。 课程形式: 以学员中一人生命数字的示范,给到大家生命数字计算的方法,能...
    谷应阅读 166评论 0 0
  • 白云山能仁寺如峰师父,说了一年叫我送张画给他,今天终于上山完成了我的心愿,谢谢如峰师父的欣赏和喜欢!感恩!br>
    李伍香阅读 198评论 0 4