小斑Swift开发实战iOS-swift

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

2017-04-10  本文已影响367人  zZ爱吃菜

效果图

imageimage

知识点

主要设计思想

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

教程开始

  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)
    }
}

上一篇下一篇

猜你喜欢

热点阅读