Swift圖片輪播
- 效果圖
- 核心思想
一個(gè)UIScrollView,三個(gè)UIImageView,一個(gè)[UIImage](至少兩張圖片)
ScrollView的ContentOffset等于三倍屏幕寬,ImageView在ScrollView上的位置始終固定,當(dāng)滾動(dòng)時(shí)修改各自對(duì)應(yīng)的圖片,滾動(dòng)完成后將Scrollview的ContentOffset置于中間,這樣就形成圖片循環(huán)輪播的”假象”。
左滑示意圖:
右滑示意圖:
核心代碼如下:
private func updateImage() {
if currentPage == 0 {
leftImageView.image = imageArray.last
centerImageView.image = imageArray[currentPage]
rightImageView.image = imageArray[currentPage + 1]
} else if currentPage == imageArray.count - 1 {
leftImageView.image = imageArray[currentPage - 1]
centerImageView.image = imageArray[currentPage]
rightImageView.image = imageArray.first
} else {
leftImageView.image = imageArray[currentPage - 1]
centerImageView.image = imageArray[currentPage]
rightImageView.image = imageArray[currentPage + 1]
}
if let completeOperate = operate {
completeOperate(page: currentPage)
}
pageControl.currentPage = currentPage
scrollView.setContentOffset(CGPoint(x: width, y: 0), animated: false)
}
- 具體實(shí)現(xiàn)過(guò)程
本文是封裝了一個(gè)繼承于UIView的類(lèi),需要的時(shí)候直接拿出來(lái)用就行了。
屬性
private var scrollView = UIScrollView()
private var pageControl = UIPageControl()
private var leftImageView = UIImageView()
private var centerImageView = UIImageView()
private var rightImageView = UIImageView()
private var currentPage = 0
private var width: CGFloat!
private var height: CGFloat!
private var timer: NSTimer?
/// 滾動(dòng)方向
enum RollingDirection : Int {
case Left
case Right
}
/// 指示器當(dāng)前頁(yè)顏色
var currentPageIndicatorTintColor:UIColor = .whiteColor(){
willSet{
pageControl.currentPageIndicatorTintColor = newValue
}
}
/// 指示器顏色
var pageIndicatorTintColor:UIColor = .whiteColor(){
willSet{
pageControl.pageIndicatorTintColor = newValue
}
}
/// 是否自動(dòng)滾動(dòng)
var autoRoll = false {
willSet {
if newValue {
startTimer()
} else {
stopTimer()
}
}
}
/// 滾動(dòng)方向
var direction: RollingDirection = .Right {
willSet {
stopTimer()
}
didSet {
if autoRoll {
startTimer()
}
}
}
/// 間隔時(shí)間
var timeInterval: NSTimeInterval = 3 {
willSet {
stopTimer()
}
didSet {
if autoRoll {
startTimer()
}
}
}
/// 圖片數(shù)組
var imageArray: [UIImage] = [] {
willSet {
stopTimer()
currentPage = 0
pageControl.numberOfPages = newValue.count
}
didSet {
updateImage()
if autoRoll {
startTimer()
}
}
}
/// 滾動(dòng)完成響應(yīng)事件
var operate: ((page: Int)->())?
自定義構(gòu)造函數(shù),使用時(shí)可以選擇是否自動(dòng)滾動(dòng),滾動(dòng)時(shí)間和方向等
/**
自定義構(gòu)造函數(shù)
- parameter frame: frame
- parameter isAutoRoll: 是否自動(dòng)滾動(dòng)
- parameter rollDirection: 滾動(dòng)方向
- parameter timeInt: 滾動(dòng)時(shí)間間隔
- parameter images: 圖片數(shù)組
- parameter scrollCompleteOperate: 滾動(dòng)完成響應(yīng)事件,參數(shù)page為當(dāng)前頁(yè)數(shù)
*/
init(frame: CGRect,
isAutoRoll: Bool?,
rollDirection: RollingDirection?,
timeInt: NSTimeInterval?,
images:[UIImage],
scrollCompleteOperate:((page: Int)->())?) {
super.init(frame: frame)
initializeUserInterface()
imageArray = images
pageControl.numberOfPages = imageArray.count
if let autoR = isAutoRoll {
autoRoll = autoR
}
if let direct = rollDirection {
direction = direct
}
if let timeI = timeInt {
timeInterval = timeI
}
if let completeOperate = scrollCompleteOperate {
operate = completeOperate
}
//初始化
updateImageData()
startTimer()
}
重寫(xiě)父類(lèi)構(gòu)造函數(shù)
//重寫(xiě)父類(lèi)初始化方法
override init(frame: CGRect) {
super.init(frame: frame)
initializeUserInterface()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
fatalError("init(coder:) has not been implemented")
}
定時(shí)器相關(guān),這里使用自定義類(lèi)來(lái)創(chuàng)建定時(shí)器,下文會(huì)提到,防止定時(shí)器循環(huán)引用導(dǎo)致視圖控制器不能被釋放
//啟動(dòng)定時(shí)器
private func startTimer() {
timer = nil
//調(diào)用自定義對(duì)象,讓timer對(duì)其進(jìn)行強(qiáng)引用,而不對(duì)視圖控制器強(qiáng)引用
timer = WeakTimerObject.scheduledTimerWithTimeInterval(timeInterval, aTargat: self, aSelector: #selector(pageRoll), userInfo: nil, repeats: true)
}
//關(guān)閉定時(shí)器
private func stopTimer() {
if let _ = timer?.valid {
timer?.invalidate()
timer = nil
}
}
//定時(shí)器觸發(fā)方法
@objc private func pageRoll() {
switch direction {
case .Left:
scrollView.setContentOffset(CGPoint(x: 0, y: 0), animated: true)
case .Right:
scrollView.setContentOffset(CGPoint(x: width * 2, y: 0), animated: true)
}
}
滑動(dòng)方向判斷,無(wú)論是手動(dòng)滑動(dòng)還是自動(dòng)滑動(dòng)都要判斷左滑還是右滑
//判斷向左滑動(dòng)還是向右滑動(dòng)
private func judgeDirection(ratio: CGFloat) {
if ratio < 1 {
if currentPage == 0 {
currentPage = imageArray.count - 1
} else {
currentPage -= 1
}
} else if ratio > 1 {
if currentPage == imageArray.count - 1 {
currentPage = 0
} else {
currentPage += 1
}
}
updateImage()
}
實(shí)現(xiàn)ScrollView協(xié)議方法
//MARK:-scrollViewDelegate
//手動(dòng)滑動(dòng)停止調(diào)用
func scrollViewDidEndDecelerating(scrollView: UIScrollView) {
judgeDirection(scrollView.contentOffset.x / width)
}
//自動(dòng)滑動(dòng)停止調(diào)用
func scrollViewDidEndScrollingAnimation(scrollView: UIScrollView) {
judgeDirection(scrollView.contentOffset.x / width)
}
- 使用操作
1.調(diào)用自定義構(gòu)造函數(shù)
let imageRollView = ImageScrollView(frame: CGRect(x: 0, y: 64, width: SCREEN_WIDTH, height: SCREEN_HEIGHT - 64),
isAutoRoll: true,
rollDirection: .Right,
timeInt: 4,
images: ary) { (page) in
print("第\(page)頁(yè)")
}
self.view.addSubview(imageRollView)
2.調(diào)用默認(rèn)構(gòu)造函數(shù)
let imageRollView = ImageScrollView(frame: CGRect(x: 0, y: 64, width: SCREEN_WIDTH, height: SCREEN_HEIGHT - 64))
imageRollView.imageArray = ary
imageRollView.autoRoll = true
imageRollView.timeInterval = 3
imageRollView.direction = .Left
imageRollView.operate = {(page) in
print("第\(page)頁(yè)")
}
self.view.addSubview(imageRollView)
定時(shí)器問(wèn)題
- 問(wèn)題描述
Demo完成后檢查發(fā)現(xiàn)問(wèn)題,在VC中開(kāi)啟定時(shí)器,圖片輪播正常,并在析構(gòu)函數(shù)中將定時(shí)器移除:
deinit {
if let _ = timer?.valid {
timer?.invalidate()
timer = nil
print("停止定時(shí)器")
}
}
正常情況下,VC出棧后會(huì)自動(dòng)釋放內(nèi)存并調(diào)用deinit()函數(shù),然而問(wèn)題出現(xiàn)了,VC出棧后并沒(méi)有調(diào)用deinit()函數(shù),這就意味VC并沒(méi)有釋放,定時(shí)器一直處于開(kāi)啟狀態(tài):
最終發(fā)現(xiàn)問(wèn)題原因:NSTimer對(duì)象添加到Runloop的時(shí)候,會(huì)被Runloop強(qiáng)引用,同時(shí)NSTimer對(duì)象會(huì)對(duì)target對(duì)象強(qiáng)引用,從而導(dǎo)致循環(huán)引用。在一個(gè)視圖控制器中開(kāi)啟一個(gè)定時(shí)器,該視圖控制器釋放前,如果定時(shí)器未從Runloop中移除,那么該視圖控制器都不會(huì)被釋放
- 解決方法
解決問(wèn)題的核心是打破循環(huán)引用。本文解決方法是創(chuàng)建一個(gè)類(lèi),在類(lèi)中實(shí)現(xiàn)定時(shí)器scheduledTimerWithTimeInterval()方法,創(chuàng)建定時(shí)器的時(shí)候就使用該類(lèi)來(lái)創(chuàng)建,目的是讓NSTimer的target為該類(lèi)的對(duì)象,而不是視圖控制器,這樣NSTimer對(duì)該類(lèi)對(duì)象進(jìn)行強(qiáng)引用,而不對(duì)視圖控制器進(jìn)行強(qiáng)引用,這樣就打破循環(huán)引用這個(gè)環(huán)了。
1.構(gòu)造一個(gè)scheduledTimerWithTimeInterval()方法:
class WeakTimerObject: NSObject {
weak var targat: AnyObject?
var selector: Selector?
var timer: NSTimer?
static func scheduledTimerWithTimeInterval(interval: NSTimeInterval,
aTargat: AnyObject,
aSelector: Selector,
userInfo: AnyObject?,
repeats: Bool) -> NSTimer {
let weakObject = WeakTimerObject()
weakObject.targat = aTargat
weakObject.selector = aSelector
weakObject.timer = NSTimer.scheduledTimerWithTimeInterval(interval,
target: weakObject,
selector: #selector(fire),
userInfo: userInfo,
repeats: repeats)
return weakObject.timer!
}
2.將WeakTimerObject的對(duì)象設(shè)置為NSTimer的target,并響應(yīng)定時(shí)器方法
func fire(ti: NSTimer) {
if let _ = target {
targat?.performSelector(selector!, withObject: ti.userInfo)
} else {
timer?.invalidate()
}
}
}
- Demo下載
Swift圖片無(wú)限輪播