美文网首页小斑Swift开发实战iOS-swift
Swift 3.0 商城开发 —— 图片滑动组件

Swift 3.0 商城开发 —— 图片滑动组件

作者: zZ爱吃菜 | 来源:发表于2017-04-10 16:50 被阅读367次

    效果图

    imageimage

    知识点

    • 学习 UIScrollView 和 UIPageControl 的基本用法
    • 学习 三个 UIImageView 实现无限循环滚动思想
    • 学习 我的 UIView 常用扩展
    • 学习 给我们的视图设置代理事件

    主要设计思想

    概要介绍我在设计此控件的一些具体步骤与思想

    • 创建 UIScrollView 作为滚动视图的容器;
    • 创建 UIPageControl 作为显示页数以及总页数的组件
    • 在 UIScrollView 中创建三个 UIImageView 分别作为 上一页 当前页 下一页
    • 通过绑定数据 data 的 didSet 来动态部署 UIScrollView 中的 UIImageView 视图。(实现无限循环)
    • 通过 Timer 实现定时跳转到下一个视图
    • 调用 UIScrollView 扩张 extension 实现具体操作细节

    教程开始

    1. UIScrollView 使用

    博主习惯创建组件的方式如下:

    // fileprivate 是 Swift 3.0 新增加的访问控制权限:文件内访问
    // 封装控件时,我们要主动的隐藏掉具体的实现,只开放使用接口即可
    fileprivate var scrollView: UIScrollView = {
        let object = UIScrollView()
        // 是否可以拉伸滑动
        object.bounces = false
        // 隐藏 水平和垂直 滚动条
        object.showsVerticalScrollIndicator = false
        object.showsHorizontalScrollIndicator = false
        // 分页
        object.isPagingEnabled = true
        return object
    }()
    
    1. UIPageControl 使用

    创建 UIPageControl 组件,构建如下:

    fileprivate var pageControl: UIPageControl = {
        let object = UIPageControl()
        // 单页面下 隐藏
        object.hidesForSinglePage = true
        // 当前页背景色
        object.currentPageIndicatorTintColor = UIColor.red
        // 其他也背景色
        object.pageIndicatorTintColor = UIColor.gray
        return object
    }()
    
    1. 主视图配置

    主要包括:主视图的属性配置、添加子视图

    private func prepareUI() {
        self.backgroundColor = UIColor.white
        self.scrollView.delegate = self
        // 添加 滑动试图
        self.addSubview(scrollView)
        createScrollView()
        // 添加 页面控制
        self.addSubview(pageControl)
    }
    private func layoutUI() {
        scrollView.frame = self.bounds
        pageControl.frame = CGRect(x: self.frame.width - 85, y: self.frame.height - 25, width: 80, height: 20)
        scrollView.contentSize = CGSize(width: CGFloat(imageCount) * viewSize.width, height: viewSize.height)
    }
    
    
    1. 构建 UIScrollView 子视图

    重点:通常情况下,开发者可能根据图片数组的数量在 UIScrollView 中创建对应数量的 UIImageView 扩充 UIScrollView 的 contentSize 实现所有图片的滑动;这种设计思路比较常规,但有两个问题:1.如果图片数据过大,此控件将占用过大的内存去创建视图;2.很难实现无限循环滑动。

    我的思想:在 UIScrollView 中只创建三个 UIImageView 作为 上一页图片 当前页图片 下一页图片 的容器。通过判断当前页所在 图片数据中的位置,动态为三个 UIImageView 填充指定图片。

    // 创建 滑动视图子视图 
    func createScrollView() {
        for i in 0 ..< 3 {
            let imageView: UIImageView = {
                let object = UIImageView()
                object.isUserInteractionEnabled = true
                object.contentMode = UIViewContentMode.scaleToFill
                return object
            }()
            imageView.frame = CGRect(x: CGFloat(i) * viewSize.width, y: 0, width: viewSize.width, height: viewSize.height)
            let tap = UITapGestureRecognizer(target: self, action: #selector(touchImage))
            imageView.addGestureRecognizer(tap)
            scrollView.addSubview(imageView)
        }
    }
    
    
    1. 重点:动态更新 UIImageView 视图实现无限滚动

    核心知识:如果通过三个 UIImageView 实现 无限图片的 无限循环滑动:

    func updateScrollView() {
        // 遍历三次
        for i in 0 ..< 3 {
            获取 UIScrollView 中 UIImageView
            let imageView = scrollView.subviews[i] as! UIImageView
            // 获取 当前展示的图片是 序号
            var index = pageControl.currentPage
            // 如果 UIImageView UIScrollView 中的第二个视图,即 上一个图片
            if i == 0 {
                // 则 index 等于 当前图片序号 - 1
                index -= 1
            }
            // 如果 UIImageView 是 UIScrollView 中的第三个视图,即下一个图片
            if i == 2 {
                // 则 index 等于 当前图片序号 + 1
                index += 1
            }
            // 越界操作 操作
            if index < 0 {
                index = pageControl.numberOfPages - 1
            }
            
            if index >= pageControl.numberOfPages {
                index = 0
            }
            imageView.tag = index
            
            if let currentData = data {
                imageView.image = UIImage(named: currentData[index].imageUrl!)
            }
        }
        // 调整 UIScrollView 偏移量 使其永远只显示中间的 UIImageView
        scrollView.contentOffset = CGPoint(x: viewSize.width, y: 0)
    }
    
    

    糊涂的童鞋,奉上我画的示意图

    假如我们的图片数据只有 6 张图片;
    红色代表原始数组,蓝色代表我们的 UIScrollView 两边越界是 填充收尾图片

    imageimage

    最后部分

    设置定时器

    // MARK: Timer
    fileprivate func startTimer() {
        let selector = #selector(nextImage)
        timer = Timer(timeInterval: 3.0, target: self, selector: selector, userInfo: nil, repeats: true)
        RunLoop.main.add(timer!, forMode: RunLoopMode.commonModes)
    }
    
    fileprivate func stopTimer() {
        timer?.invalidate()
        timer = nil
    }
    

    扩展完善交互时的事件

    extension ImageScrollView: UIScrollViewDelegate {
        
        // 当 scrollView 有偏移量时出发
        func scrollViewDidScroll(_ scrollView: UIScrollView) {
            var page: Int = 0
            var minDistance: CGFloat = CGFloat(MAXFLOAT)
            for i in 0 ..< 3 {
                let imageView = scrollView.subviews[i] as! UIImageView
                // 由于当前页有用都是中间页,所以当 偏移量==图片宽度时,就是当前页。
                let distance:CGFloat = abs(imageView.x - scrollView.contentOffset.x)
                if distance < minDistance {
                    minDistance = distance
                    page = imageView.tag
                }
            }
            pageControl.currentPage = page
        }
        
        // 开始拖动 UIScrollView 事件
        func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
            stopTimer()
        }
        
        // 结束拖动 UIScrollView 事件
        func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
            startTimer()
        }
        
        // 
        func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
            updateScrollView()
        }
        
        // UIScrollView 结束滚动
        func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
            updateScrollView()
        }
        
    }
    
    

    学会设置代理

    具体步骤:

    1. 定义代理 delegate
    2. 配置代理事件
    3. 实例对象,调用对象代理
    // 1
    @objc protocol ImageScrollViewDelegate {
        @objc optional func touchAt(index: Int)
    }
    // 2
    class ImageScrollView: UIView {
        var delegate: ImageScrollViewDelegate?
        
        // 触发代理
        func touchImage(tap: UITapGestureRecognizer) {
            if let index = tap.view?.tag {
                delegate?.touchAt!(index: index)
            }
        }
        ...... }
    // 3
    // 实现代理:在调用 ImageScrollView 控件的 VC 中扩展,具体看源码
    extension ImageScrollViewController: ImageScrollViewDelegate {
        func touchAt(index: Int) {
            print(index)
        }
    }
    

    源码

    组件源码

    import UIKit
    
    @objc protocol ImageScrollViewDelegate {
        @objc optional func touchAt(index: Int)
    }
    
    class ImageScrollView: UIView {
        var delegate: ImageScrollViewDelegate?
        fileprivate var timer: Timer?
        var viewSize: CGSize!
        var data: [ImageScrollData]? {
            didSet {
                if timer != nil {
                    timer!.invalidate()
                    timer = nil
                }
                
                if let scrollData = data {
                    pageControl.numberOfPages = scrollData.count
                    pageControl.currentPage = 0
                    updateScrollView()
                    startTimer()
                }
            }
        }
        
        override init(frame: CGRect) {
            super.init(frame: frame)
            self.frame = frame
            viewSize = frame.size
            prepareUI()
            layoutUI()
        }
        
        required init?(coder aDecoder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
        
        
        private func prepareUI() {
            self.backgroundColor = UIColor.white
            self.scrollView.delegate = self
            // 添加 滑动试图
            self.addSubview(scrollView)
            createScrollView()
            // 添加 页面控制
            self.addSubview(pageControl)
        }
        
        private func layoutUI() {
            scrollView.frame = self.bounds
            pageControl.frame = CGRect(x: self.frame.width - 85, y: self.frame.height - 25, width: 80, height: 20)
            scrollView.contentSize = CGSize(width: CGFloat(3) * viewSize.width, height: viewSize.height)
        }
        
        // 创建 滑动视图子视图
        func createScrollView() {
            for i in 0 ..< 3 {
                let imageView: UIImageView = {
                    let object = UIImageView()
                    object.isUserInteractionEnabled = true
                    object.contentMode = UIViewContentMode.scaleToFill
                    return object
                }()
                imageView.frame = CGRect(x: CGFloat(i) * viewSize.width, y: 0, width: viewSize.width, height: viewSize.height)
                let tap = UITapGestureRecognizer(target: self, action: #selector(touchImage))
                imageView.addGestureRecognizer(tap)
                scrollView.addSubview(imageView)
            }
        }
        
        func updateScrollView() {
            for i in 0 ..< 3 {
                let imageView = scrollView.subviews[i] as! UIImageView
                var index = pageControl.currentPage
                if i == 0 {
                    index -= 1
                }
                
                if i == 2 {
                    index += 1
                }
                
                if index < 0 {
                    index = pageControl.numberOfPages - 1
                }
                
                if index >= pageControl.numberOfPages {
                    index = 0
                }
                imageView.tag = index
                
                if let currentData = data {
                    imageView.image = UIImage(named: currentData[index].imageUrl!)
                }
            }
            scrollView.contentOffset = CGPoint(x: viewSize.width, y: 0)
        }
        
        // MARK: Timer
        fileprivate func startTimer() {
            let selector = #selector(nextImage)
            timer = Timer(timeInterval: 3.0, target: self, selector: selector, userInfo: nil, repeats: true)
            RunLoop.main.add(timer!, forMode: RunLoopMode.commonModes)
        }
        
        fileprivate func stopTimer() {
            timer?.invalidate()
            timer = nil
        }
        
        func nextImage() {
            scrollView.setContentOffset(CGPoint(x: 2.0 * viewSize.width, y: 0), animated: true)
        }
        
        func touchImage(tap: UITapGestureRecognizer) {
            if let index = tap.view?.tag {
                delegate?.touchAt!(index: index)
            }
        }
        
        // 初始化 滑动视图
        fileprivate var scrollView: UIScrollView = {
            let object = UIScrollView()
            object.bounces = false
            object.showsVerticalScrollIndicator = false
            object.showsHorizontalScrollIndicator = false
            object.isPagingEnabled = true
            return object
        }()
        
        fileprivate var pageControl: UIPageControl = {
            let object = UIPageControl()
            object.hidesForSinglePage = true
            object.currentPageIndicatorTintColor = UIColor.red
            object.pageIndicatorTintColor = UIColor.gray
            return object
        }()
        
    }
    
    extension ImageScrollView: UIScrollViewDelegate {
        
        // 当 scrollView 有偏移量时出发
        func scrollViewDidScroll(_ scrollView: UIScrollView) {
            var page: Int = 0
            var minDistance: CGFloat = CGFloat(MAXFLOAT)
            for i in 0 ..< 3 {
                let imageView = scrollView.subviews[i] as! UIImageView
                // 由于当前页有用都是中间页,所以当 偏移量==图片宽度时,就是当前页。
                let distance:CGFloat = abs(imageView.x - scrollView.contentOffset.x)
                if distance < minDistance {
                    minDistance = distance
                    page = imageView.tag
                }
            }
            pageControl.currentPage = page
        }
        
        func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
            stopTimer()
        }
        
        func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
            startTimer()
        }
        
        func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
            updateScrollView()
        }
        
        func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
            updateScrollView()
        }
        
    }
    
    extension UIView {
        /// X值
        open var x: CGFloat {
            return self.frame.origin.x
        }
        /// Y值
        open var y: CGFloat {
            return self.frame.origin.y
        }
        /// 宽度
        open var width: CGFloat {
            return self.frame.size.width
        }
        ///高度
        open var height: CGFloat {
            return self.frame.size.height
        }
        open var size: CGSize {
            return self.frame.size
        }
        open var origin: CGPoint {
            return self.frame.origin
        }
    }
    
    
    

    调用的 ViewController

    import UIKit
    
    class ImageScrollViewController: UIViewController {
        
        var imageScrollView = ImageScrollView(frame: CGRect(x: 0, y: 0, width: SCREEN_WIDTH, height: 150))
        var data = [ImageScrollData]()
    
        override func viewDidLoad() {
            super.viewDidLoad()
            self.view.backgroundColor = UIColor.white
            edgesForExtendedLayout = .init(rawValue: 0)
            self.title = "图片无限滚动"
            self.view.addSubview(imageScrollView)
            for i in 1 ... 6 {
                let item = ImageScrollData(imageUrl: "image_scroll_0\(i).jpg", imageDescribe: nil)
                data.append(item)
            }
            imageScrollView.data = data
        }
        
        
    
        override func didReceiveMemoryWarning() {
            super.didReceiveMemoryWarning()
            // Dispose of any resources that can be recreated.
        }
    
    }
    
    extension ImageScrollViewController: ImageScrollViewDelegate {
        func touchAt(index: Int) {
            print(index)
        }
    }
    
    

    相关文章

      网友评论

      • 兰德耍:这个只能本地图片轮播了。 网络加载不行呢
        兰德耍:@zZ爱吃菜 我直接把链接传给过去在用kf加载图片,但是一个图片一直轮播,图片就处于一直加载的状态,显示不出来:sweat:
        zZ爱吃菜:可以的,你把网络请求的数据,解析了,传递到这个组件里就可以了
      • 蓦然之间的:我都是直接用collectionView,然后数量数组的数量乘以300,开始位置定位100.反在collectionView可重用
      • SoolyChristina:建议使用collectionView。可重用
        zZ爱吃菜:@SoolyChristina 两种思路都可,对于滑动数据过大的情况,我这种方式对内存消耗更小

      本文标题:Swift 3.0 商城开发 —— 图片滑动组件

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