Swift圖片無(wú)限輪播+定時(shí)器循環(huán)引用問(wèn)題

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()
        }
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,763評(píng)論 6 539
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,238評(píng)論 3 428
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 177,823評(píng)論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 63,604評(píng)論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,339評(píng)論 6 410
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 55,713評(píng)論 1 328
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,712評(píng)論 3 445
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,893評(píng)論 0 289
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,448評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,201評(píng)論 3 357
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,397評(píng)論 1 372
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,944評(píng)論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,631評(píng)論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 35,033評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 36,321評(píng)論 1 293
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,128評(píng)論 3 398
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,347評(píng)論 2 377

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