Swift 3.0 商城開發 —— 圖片滑動組件

效果圖

image
image

知識點

  • 學習 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 兩邊越界是 填充收尾圖片

image
image

最后部分

設置定時器

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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 發現 關注 消息 iOS 第三方庫、插件、知名博客總結 作者大灰狼的小綿羊哥哥關注 2017.06.26 09:4...
    肇東周閱讀 12,255評論 4 61
  • 轉眼間我們都打卡第6天了,組長說了,這周作業是記錄一下讓你情緒失控的時刻。 我一直在想,我們為什么同樣的事情,發生...
    笑莉說閱讀 381評論 5 18
  • 不知何時開始,焦慮已經不是一種簡簡單單的心情。很多人開始發脾氣,有些人開始郁悶,有些人開始急躁。但是,焦慮是一種常...
    小簡貓閱讀 433評論 0 1
  • 首先要做到“知”。有三條建議: 摸底大調查。學生總有同校和同鄉,學生入校時,班主任一般會有一個調查小問卷,此時可以...
    侯志強閱讀 305評論 0 0
  • 兩個月前曹蕾跟我說要弄些“好玩”的事情,然后我看到了她的“玩好市集”。 一個月前,我看到曹蕾在朋友圈說要開始挑戰每...
    demi小貓閱讀 625評論 0 1