Swift無限循環(huán)的圖片輪播

前言

我們常見的一些廣告位、圖片輪播都是可以無限輪播的,以前參考文章 iOS開發(fā)系列--無限循環(huán)的圖片瀏覽器,自己也在項(xiàng)目中實(shí)際應(yīng)用了,現(xiàn)在用Swift重新實(shí)現(xiàn)一遍,發(fā)現(xiàn)代碼原來可以更加精簡,并且輪播手機(jī)相冊(cè)的所有圖片時(shí)遇到了一些問題,所以記錄一下,幫助需要用到的同學(xué)快速實(shí)現(xiàn)這個(gè)小功能。

功能實(shí)現(xiàn)

  1. 先看一下效果圖,這里分為本地圖片、網(wǎng)絡(luò)圖片、相冊(cè)三個(gè)部分來實(shí)現(xiàn),首先看一下本地效果。

其實(shí)本地的實(shí)現(xiàn)是最簡單的,相冊(cè)的實(shí)現(xiàn)更加復(fù)雜一些,因?yàn)橄鄡?cè)圖片牽扯到 PHCachingImageManager 的異步回調(diào)獲取圖片,需要單獨(dú)去處理。網(wǎng)絡(luò)圖片如果使用 Kingfisher 來處理,那么就和本地圖片一樣了,但是如果使用系統(tǒng)的方法,即使用

        let url = URL(string: urlString)
        let imageData = try! Data(contentsOf: url)
        let image = UIImage(data: imageData)

來獲取圖片,會(huì)有一些問題。下面會(huì)有單獨(dú)說明。

這里使用的 Xcode8Swift3

本地圖片輪播

  1. 原理分析
    這里一共使用了3個(gè) UIImageView ,然后滑動(dòng)結(jié)束時(shí)重置 ScrollView 的偏移值。

  2. 代碼實(shí)現(xiàn)-首頁
    首先是項(xiàng)目簡單地文件目錄。

    簡單文件結(jié)構(gòu)

    然后是 Main.storyboard 文件。這里能用故事版畫的控件我都是拒絕手寫的。
    Storyboard

    這里有一點(diǎn)需要注意,獲取相冊(cè)信息時(shí),需要在 info.plist 文件中添加字段,并且是必須添加,否則直接報(bào)錯(cuò)。
    訪問系統(tǒng)相冊(cè)

    首頁的代碼并沒有多少要說明的,就是一些基本的準(zhǔn)備工作,全部代碼如下(部分說明都在注釋中:

import UIKit
import Photos
class ViewController: UIViewController {

//  本地圖片
fileprivate var localImages: [UIImage]! {
    var newImages = [UIImage]()
    for index in 1 ... 4 {
           newImages.append(UIImage(named: "\(index).jpg")!)
        }
    return newImages
}   

// 網(wǎng)絡(luò)圖片鏈接
fileprivate var netImageUrls = ["http://photocdn.sohu.com/20141225/Img407278780.jpg",
"http://imgsrc.baidu.com/forum/w=580/sign=72d55a713b6d55fbc5c6762e5d234f40/85950d338744ebf84e0e5290dff9d72a6159a713.jpg",
"http://p5.image.hiapk.com/uploads/allimg/150210/7730-150210155949-50.jpg",
"http://file26.mafengwo.net/M00/80/AB/wKgB4lL5tX6AZGB6ABBgnBkJakw73.jpeg"]

// 相冊(cè)圖片
fileprivate var allAssets = PHAsset

override func viewDidLoad() {
    super.viewDidLoad()
    
    getAlbumAssets { (assets) in
        self.allAssets = assets
    }
}

/// 獲取系統(tǒng)相冊(cè)數(shù)據(jù)
///
/// - parameter callback: 回調(diào)結(jié)果
fileprivate func getAlbumAssets(callback: @escaping ([PHAsset]) -> Void) {
    
    PHPhotoLibrary.requestAuthorization { (status) in
        guard status == PHAuthorizationStatus.authorized else {
            print("獲取相冊(cè)權(quán)限后再進(jìn)行操作")
            return
        }
        PHAssetCollection.fetchAssetCollections(with: .smartAlbum, subtype: .smartAlbumUserLibrary, options: nil).enumerateObjects({ (collection, index, flag) in
            let result = PHAsset.fetchAssets(in: collection, options: nil)
            result.enumerateObjects({ (asset, index, flag) in
                self.allAssets.append(asset)
            })
            callback(self.allAssets)
        })       
    }
}

fileprivate let localImageSegue = "localImageSegue"
fileprivate let netUrlImageSegue = "netUrlImageSegue"
fileprivate let assetImageSegue = "assetImageSegue"
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    guard let previewVC = segue.destination as? PreviewViewController else {
        return
    }
    
    switch segue.identifier! {
    case localImageSegue:
        previewVC.getDate(resourceArray: localImages, .localImage)
    case netUrlImageSegue:
        previewVC.getDate(resourceArray: netImageUrls, .netImageUrl)
    case assetImageSegue:
        previewVC.getDate(resourceArray: allAssets, .asset)

    default:
        break
    }
}

}



#####輪播主體代碼  - 本地圖片
   
 - 我把主要需要注意的點(diǎn)和注釋都寫在代碼里了,參考時(shí)只需要注意一些 `storyboard` 的連接控件即可。因?yàn)槲以贒emo中需要處理 `UIImage` 、`String` 、 `PHAsset` 三種類型,所以在接收方法里用了 `[Any]` ,導(dǎo)致處理的時(shí)候要去分別判斷。只是單個(gè)數(shù)據(jù)類型的話并不需要這么麻煩。

 下面這些是實(shí)現(xiàn)的基本代碼,后面關(guān)于網(wǎng)絡(luò)圖片和相冊(cè)圖片都是在這個(gè)頁面里添加對(duì)應(yīng)的方法。      
   
 

import UIKit
import Photos

enum DataType {
case localImage
case netImageUrl
case asset
}

class PreviewViewController: UIViewController, UIScrollViewDelegate {

//  MARK: - Properties
@IBOutlet weak var imageScrollView: UIScrollView!

fileprivate let mScreenWidth = UIScreen.main.bounds.width
fileprivate let mScreenHeight = UIScreen.main.bounds.height

fileprivate var leftImageView: UIImageView!
fileprivate var centerImageView: UIImageView!
fileprivate var rightImageView: UIImageView!

fileprivate var currentIndex = 0

fileprivate var resource: [Any]!
fileprivate var dataType: DataType!


//  MARK: - 開放接口
internal func getDate(resourceArray: [Any], _ type: DataType) {
    dataType = type
    resource = resourceArray
}


//  MARK: - LifeCycle

override func viewDidLoad() {
    super.viewDidLoad()
    //  初始化控件
    setupScrollView()
    
    //  展示
    prepareToShowPhotos()
}
override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    navigationController?.isNavigationBarHidden = true
}

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    navigationController?.isNavigationBarHidden = false
    
}
//  隱藏StatusBar
override var prefersStatusBarHidden: Bool {
    return true
}

//  MARK: - Private Methods

/// 初始化ScrollView
fileprivate func setupScrollView() {
    leftImageView = prepareShowImageView(imageViewPosition: .leftImageView)
    centerImageView = prepareShowImageView(imageViewPosition: .centerImageView)
    rightImageView = prepareShowImageView(imageViewPosition: .rightImageView)
    
    //  FIXME: 測試

// leftImageView.backgroundColor = UIColor.red
// centerImageView.backgroundColor = UIColor.green
// rightImageView.backgroundColor = UIColor.orange

    imageScrollView.contentSize = CGSize(width: mScreenWidth * 3,
                                    height: 0)
    imageScrollView.showsHorizontalScrollIndicator = false
    imageScrollView.contentOffset = CGPoint(x: mScreenWidth, y: 0)
    imageScrollView.isPagingEnabled = true
}

//  位置
fileprivate enum ShowImageViewPosition {
    case leftImageView
    case centerImageView
    case rightImageView
}

/// 統(tǒng)一設(shè)置展示控件
///
/// - parameter imageViewPosition: 左、中、右位置
///
/// - returns: 設(shè)置完成UIImageView
fileprivate func prepareShowImageView(imageViewPosition: ShowImageViewPosition) -> UIImageView {
    let imageView = UIImageView()
    imageView.clipsToBounds = true
    imageView.contentMode = .scaleAspectFit
    imageView.frame = view.bounds
    imageView.backgroundColor = UIColor.black
    switch imageViewPosition {
    case .leftImageView:
        imageView.frame.origin.x = 0
    case .centerImageView:
        imageView.frame.origin.x = mScreenWidth
    case .rightImageView:
        imageView.frame.origin.x = 2 * mScreenWidth
    }
    imageScrollView.addSubview(imageView)
    
    return imageView
}


/// 展示圖片
fileprivate func prepareToShowPhotos() {
    //  如果僅僅有一張
    if resource.count == 1 {
        imageScrollView.contentSize = view.bounds.size
    }
    
    switch dataType! {
    case .localImage:
        setLocalImages()
    case .netImageUrl:
        setNetImages()
    case .asset:
        setPHAssetImages()
    }
}


/// 展示本地圖片
fileprivate func setLocalImages() {
    leftImageView.image = resource[(currentIndex + resource.count - 1) % resource.count] as? UIImage
    centerImageView.image = resource[currentIndex % resource.count] as? UIImage
    rightImageView.image = resource[(currentIndex + resource.count + 1) % resource.count] as? UIImage
}

/// 展示網(wǎng)絡(luò)圖片
fileprivate func setNetImages() {
    
}

/// 展示相冊(cè)圖片
fileprivate func setPHAssetImages() {
    
}



//  MARK: - UIScrollView Delegate

///  開始拖拽ScrollView時(shí)
fileprivate var originalOffsetX: CGFloat = 0
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
    originalOffsetX = scrollView.contentOffset.x
}

///  ScrollView 慣性結(jié)束
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
    //  當(dāng)滑動(dòng)幅度不夠時(shí),保留ScrollView的原生特性
    if originalOffsetX == scrollView.contentOffset.x {
        return
    }
    
    if currentIndex == 0 {
        currentIndex = resource.count
    }
    //  滑動(dòng)時(shí),更改顯示
    if originalOffsetX < scrollView.contentOffset.x {
        currentIndex += 1
    } else {
        currentIndex -= 1
    }
    
    //  更改偏移,重新展示
    imageScrollView.setContentOffset(CGPoint(x: mScreenWidth, y: 0), animated: false)
    
    switch dataType! {
    case .localImage:
        setLocalImages()
    case .netImageUrl:
        setNetImages()
    case .asset:
        setPHAssetImages()
    }
}

//  MARK: - Outlet Actions 
@IBAction func backAction(_ sender: UIButton) {
    navigationController!.popViewController(animated: true)
}

}

核心代碼到這里就結(jié)束了。上面的代碼直接復(fù)制到自己新建的demo  中就是可以直接運(yùn)行的。下面則是對(duì)于網(wǎng)絡(luò)圖片和相冊(cè)圖片的擴(kuò)展,因?yàn)槎紶砍兜疆惒教幚恚远际菍?duì)上面代碼的補(bǔ)充。

##### 網(wǎng)絡(luò)圖片的輪播

1. 首先說不使用第三方框架,僅僅使用下面主要代碼獲取網(wǎng)絡(luò)圖片時(shí),

let url = URL(string: urlString)
let imageData = try! Data(contentsOf: url)
let image = UIImage(data: imageData)

如果放在主線程去做,會(huì)造成很嚴(yán)重的卡頓,如果開啟線程,然后單獨(dú)下載圖片,會(huì)因?yàn)楫惒降膯栴}導(dǎo)致保存圖片的數(shù)組內(nèi)部重復(fù)保存。如果開啟線程依賴,也會(huì)有問題。總之,嘗試失敗,好在不是輪播重點(diǎn),暫時(shí)pass。有好的思路的時(shí)候再來補(bǔ)充。

2. 利用 `cocoapods` 導(dǎo)入第三方圖片框架 `Kingfisher` ,至于 `cocoapods` 的使用,網(wǎng)上有很多非常完善的教程。那么問題處理就簡單很多了,完全像本地圖片一樣了。

  效果圖:
![輪播網(wǎng)絡(luò)圖片.gif](http://upload-images.jianshu.io/upload_images/1910830-65dd5f6ae5850b95.gif?imageMogr2/auto-orient/strip)

  首先,定義默認(rèn)圖片屬性

fileprivate var placeHodlerImage = UIImage(named: "defaultLoad.png")


  然后,完善方法 `setNetImages()` :
///  展示網(wǎng)絡(luò)圖片
fileprivate func setNetImages() {      
    
    let leftUrl = URL(string: (self.resource[(self.currentIndex + self.resource.count - 1) % self.resource.count] as? String)!)
    leftImageView.kf.setImage(with: leftUrl, placeholder:placeHodlerImage, options: nil, progressBlock: nil, completionHandler: nil)
    
    let centerUrl = URL(string: (self.resource[self.currentIndex % self.resource.count] as? String)!)
    centerImageView.kf.setImage(with: centerUrl, placeholder:placeHodlerImage, options: nil, progressBlock: nil, completionHandler: nil)
    
    let rightUrl = URL(string: (self.resource[(self.currentIndex + self.resource.count + 1) % self.resource.count] as? String)!)
    rightImageView.kf.setImage(with: rightUrl, placeholder:placeHodlerImage, options: nil, progressBlock: nil, completionHandler: nil)

}


##### 本地相冊(cè)圖片的輪播
1. 帶有瑕疵的方案,之所以要說這一點(diǎn),因?yàn)橐郧拔揖鸵恢笔褂玫倪@種方式,遇到了一些坑,直到最后解決。

  **首先**,添加相冊(cè)元數(shù)據(jù) `PHAsset` 轉(zhuǎn)化為 `UIImage` 的方法:
/// 將Asset轉(zhuǎn)化為UIImage
///
/// - parameter singleAsset: 元數(shù)據(jù)
///
/// - returns: image
fileprivate func transformAssetToImage(singleAsset: PHAsset,_ callback: @escaping (UIImage) -> Void) {
    PHCachingImageManager.default().requestImage(for: singleAsset, targetSize: view.bounds.size, contentMode: .aspectFit, options: nil) { (requestImage, nil) in
        callback(requestImage!)
    }
}
  **然后**,完善方法 `setPHAssetImages()` :
/// 展示相冊(cè)圖片
fileprivate func setPHAssetImages() {
    //  左
    let leftAsset = resource[(currentIndex + resource.count - 1) % resource.count] as! PHAsset
    transformAssetToImage(singleAsset: leftAsset) { (requestImage) in
        DispatchQueue.main.async {
            self.leftImageView.image = requestImage
        }
    }
    
    //  中
    let centerAsset = resource[currentIndex % resource.count] as! PHAsset
    transformAssetToImage(singleAsset: centerAsset) { (requestImage) in
        DispatchQueue.main.async {
            self.centerImageView.image = requestImage
        }
    }
    
    //  右
    let rightAsset = resource[(currentIndex + 1) % resource.count] as! PHAsset
    transformAssetToImage(singleAsset: rightAsset) { (requestImage) in
        DispatchQueue.main.async {
            self.rightImageView.image = requestImage
        }
    }
}

  最后,看效果:

  ![圖片會(huì)抖動(dòng).gif](http://upload-images.jianshu.io/upload_images/1910830-65be999ad4540379.gif?imageMogr2/auto-orient/strip)
  可以很明顯的看到圖片的抖動(dòng)現(xiàn)象,這個(gè)問題其實(shí)現(xiàn)在我也不確定是什么原因造成的,只是結(jié)合自己后來的嘗試找到了解決方法,算是以結(jié)果倒推原因。

2. 改善版。為了避免重復(fù)的使用方法 `PHCachingImageManager` 來解析圖片,建立一個(gè)臨時(shí)圖片數(shù)組,存放解析到的圖片,然后從從圖片數(shù)組中讀取圖片。

  **首先**,創(chuàng)建一個(gè)臨時(shí)圖片數(shù)組:

/// 將相冊(cè)圖片緩存到內(nèi)存中
fileprivate var cacheImages: [UIImage]!

  **然后**,在接收方法中初始化:
//  MARK: - 開放接口
internal func getDate(resourceArray: [Any], _ type: DataType) {
    dataType = type
    resource = resourceArray
    
    cacheImages = Array(repeating:placeHodlerImage!, count: resource.count)
}
  **再然后**,完善方法 `setPHAssetImages()` 。定義一個(gè) 標(biāo)識(shí)數(shù)組,確保相冊(cè)的每一項(xiàng) `PHAsset` 都已經(jīng)被轉(zhuǎn)化為 `UIImage` 。只有第一次解析圖片時(shí),才會(huì)直接將圖片放置到左中右三個(gè) `UIImageView` 上,然后開始滑動(dòng) `ScrollView` 時(shí),再次解析圖片, `ScrollView` 慣性結(jié)束時(shí), 則是從緩存中讀取。
  設(shè)置標(biāo)識(shí)數(shù)組:
/// asset 是否完全轉(zhuǎn)化為 Image 的標(biāo)識(shí)
fileprivate var transformAssetToImageFlags = [Int]()
  完善 `setPHAssetImages()` 方法。這里添加了一個(gè) `isFirst` 布爾參數(shù),為了判斷是否是第一次進(jìn)來解析圖片。

/// 展示相冊(cè)圖片
fileprivate func setPHAssetImages(isFirst: Bool) {

    let index = currentIndex % resource.count
    if transformAssetToImageFlags.count == 0 {
        //  第一次執(zhí)行
        transformAssetToImageFlags.append(index)
    }
    
    //  判斷是否已經(jīng)有相同圖片添加到緩存數(shù)組
    var isAdd = false
    transformAssetToImageFlags.forEach { (flag) in
        if flag == index {
            isAdd = true
        }
    }
    if !isAdd {
        transformAssetToImageFlags.append(index)
    }
    
    //  左
    let leftAsset = resource[(currentIndex + resource.count - 1) % resource.count] as! PHAsset
    transformAssetToImage(singleAsset: leftAsset) { (requestImage) in
        if isFirst {
           self.leftImageView.image = requestImage
        }
        self.cacheImages[(self.currentIndex + self.resource.count - 1) % self.resource.count] = requestImage
    }
    
    //  中
    let centerAsset = resource[currentIndex % resource.count] as! PHAsset
    transformAssetToImage(singleAsset: centerAsset) { (requestImage) in
        if isFirst {
            self.centerImageView.image = requestImage
        }
        self.cacheImages[self.currentIndex % self.resource.count] = requestImage
    }
    
    //  右
    let rightAsset = resource[(currentIndex + 1) % resource.count] as! PHAsset
    transformAssetToImage(singleAsset: rightAsset) { (requestImage) in
        if isFirst {
            self.rightImageView.image = requestImage
        }
        self.cacheImages[(self.currentIndex + 1) % self.resource.count] = requestImage
    }
}

  修改第一次獲取圖片的方法:
/// 展示圖片
fileprivate func prepareToShowPhotos() {
    //  如果僅僅有一張
    if resource.count == 1 {
        imageScrollView.contentSize = view.bounds.size
    }
    
    switch dataType! {
    case .localImage:
        setLocalImages()
    case .netImageUrl:
        setNetImages()
    case .asset:
        setPHAssetImages(isFirst: true)
    }
}
  修改 `ScrollView` 拖動(dòng)時(shí)的方法:
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
    originalOffsetX = scrollView.contentOffset.x
    
    switch dataType! {
    case .localImage:
        break
    case .netImageUrl:
        break
    case .asset:
        //  判斷是否所有圖片都已經(jīng)轉(zhuǎn)化
        if transformAssetToImageFlags.count == resource.count {
            break
        }
        setPHAssetImages(isFirst: false)
    }
}
  添加讀取圖片的方法:
/// 從緩存的圖片數(shù)組中讀取數(shù)據(jù)
fileprivate func showImageFromCacheImages() {
    leftImageView.image = cacheImages[(self.currentIndex - 1) % self.resource.count]
    centerImageView.image = cacheImages[self.currentIndex % self.resource.count]
    rightImageView.image = cacheImages[(self.currentIndex + 1) % self.resource.count]
}
修改 `ScrollView` 慣性結(jié)束的方法:
///  ScrollView 慣性結(jié)束
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
    //  當(dāng)滑動(dòng)幅度不夠時(shí),保留ScrollView的原生特性
    if originalOffsetX == scrollView.contentOffset.x {
        return
    }
    
    if currentIndex == 0 {
        currentIndex = resource.count
    }
    //  滑動(dòng)時(shí),更改顯示
    if originalOffsetX < scrollView.contentOffset.x {
        currentIndex += 1
    } else {
        currentIndex -= 1
    }
    
    //  更改偏移,重新展示
    imageScrollView.setContentOffset(CGPoint(x: mScreenWidth, y: 0), animated: false)
    
    switch dataType! {
    case .localImage:
        setLocalImages()
    case .netImageUrl:
        setNetImages()
    case .asset:
        showImageFromCacheImages()
    }
}

  展示效果:

  ![還是有問題的](http://upload-images.jianshu.io/upload_images/1910830-2f8506e7564cdc85.gif?imageMogr2/auto-orient/strip)
    其實(shí)也就是從這里我推測最開始抖動(dòng)的原因是解析圖片是異步回調(diào)的,但是修改 `ScrollView` 時(shí)是重置了 `offset` ,在重置剛結(jié)束時(shí),獲取到新的圖片,就發(fā)現(xiàn)第一次展示的圖片會(huì)出現(xiàn)一個(gè)占位圖。如果同時(shí)轉(zhuǎn)化5張圖片,并存放到數(shù)組,就沒有任何問題了。這里只是我根據(jù)猜測找到的一種解決方法,甚至猜測的原因都可能是錯(cuò)的,但是確實(shí)解決了問題,并且從復(fù)用或者邏輯的角度來看,也并沒有什么問題,算是作為一個(gè)輪播的點(diǎn)寫出來共享一下。

  完善方法 `setPHAssetImages(_: )` :
/// 展示相冊(cè)圖片
fileprivate func setPHAssetImages(isFirst: Bool) {
    
    let index = currentIndex % resource.count
    if transformAssetToImageFlags.count == 0 {
        //  第一次執(zhí)行
        transformAssetToImageFlags.append(index)
    }
    
    //  判斷是否已經(jīng)有相同圖片添加到緩存數(shù)組
    var isAdd = false
    transformAssetToImageFlags.forEach { (flag) in
        if flag == index {
            isAdd = true
        }
    }
    if !isAdd {
        transformAssetToImageFlags.append(index)
    }
    
    //  左-1
    let leftAssetleft = resource[(currentIndex + resource.count - 2) % resource.count] as! PHAsset
    transformAssetToImage(singleAsset: leftAssetleft) { (requestImage) in
        self.cacheImages[(self.currentIndex + self.resource.count - 2) % self.resource.count] = requestImage
    }
    
    //  左
    let leftAsset = resource[(currentIndex + resource.count - 1) % resource.count] as! PHAsset
    transformAssetToImage(singleAsset: leftAsset) { (requestImage) in
        if isFirst {
           self.leftImageView.image = requestImage
        }
        self.cacheImages[(self.currentIndex + self.resource.count - 1) % self.resource.count] = requestImage
    }
    
    //  中
    let centerAsset = resource[currentIndex % resource.count] as! PHAsset
    transformAssetToImage(singleAsset: centerAsset) { (requestImage) in
        if isFirst {
            self.centerImageView.image = requestImage
        }
        self.cacheImages[self.currentIndex % self.resource.count] = requestImage
    }
    
    //  右
    let rightAsset = resource[(currentIndex + 1) % resource.count] as! PHAsset
    transformAssetToImage(singleAsset: rightAsset) { (requestImage) in
        if isFirst {
            self.rightImageView.image = requestImage
        }
        self.cacheImages[(self.currentIndex + 1) % self.resource.count] = requestImage
    }
    
    //  右+1
    let rightAssetRight = resource[(currentIndex + 2) % resource.count] as! PHAsset
    transformAssetToImage(singleAsset: rightAssetRight) { (requestImage) in
        self.cacheImages[(self.currentIndex + 2) % self.resource.count] = requestImage
    }
}  
  運(yùn)行效果:

  ![完善后](http://upload-images.jianshu.io/upload_images/1910830-1ec2ecca7812e090.gif?imageMogr2/auto-orient/strip)

 
##### 再說一點(diǎn)
  其實(shí)還是存在一點(diǎn)問題的。上面處理 `ScrollView` 的偏移值重置操作是在慣性結(jié)束后的方法里 `scrollViewDidEndDecelerating(_: )`,但是如果滑動(dòng)很快,即慣性還沒結(jié)束就連續(xù)滑動(dòng),就會(huì)出來拉不動(dòng)的情況,因?yàn)槠浦禌]有修改的話,始終只有三個(gè) `UIImageView`。如果取消慣性,即令  `scrollview.bounces = false` ,就會(huì)出現(xiàn)偶爾劃不動(dòng)的情況。這里我在 `ScrollView` 開始滑動(dòng)時(shí),令 `originalOffsetX = scrollView.bounds.width` ,雖然緩解了問題,但是還是會(huì)出現(xiàn)偶爾快速滑動(dòng)兩次,但是慣性結(jié)束方法執(zhí)行一次的情況,因?yàn)?`scrollViewWillBeginDragging(_: )` 和 `scrollViewDidEndDecelerating(_: )` 并不是一對(duì)一的關(guān)系。
上面都是在快速滑動(dòng)的時(shí)候出現(xiàn)的問題,如果開啟 `timer` 實(shí)現(xiàn)自動(dòng)輪播并不會(huì)出現(xiàn)上面的問題。
在網(wǎng)上找了一些類似無限輪播的例子,好像并沒有注意這方面的問題。還有利用 `UICollectionView` 來實(shí)現(xiàn)輪播功能,不知道會(huì)不會(huì)出現(xiàn)這個(gè)問題,暫時(shí)并沒有嘗試。如果以后有好的思路解決這個(gè)問題,再來更新。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容