詳細寫一個功能完善的PhotoBrowser(二)加載多張圖片以及加載網絡圖片

寫一個功能完善的圖片瀏覽器

最終效果 源碼

網絡.gif

本地圖片.gif

本篇文章后完成的效果

本篇效果.gif

前言: 在之前我們已經實現了對單張圖片的手勢處理縮放,如果順利的話 , 我們的photoView目前對于單張圖片的功能支持已經很好了, 接下來我們來實現對多張圖片的瀏覽, 實現真正意義上的圖片瀏覽器, 同時會用到kingfisher(實現了和SDWebImage相似的功能的一個加載圖片的swift框架 -- 王巍寫)來加載網絡圖片

分析

1.實現多張圖片的滾動瀏覽, 相信大家都會有思路怎么實現的, 因為就是相當于一個圖片輪播器 , 而且還不需要自動滾動, 甚至不需要實現循環滾動, 那么應該是很簡單就能實現的

  • 使用UIScrollView來實現圖片的滾動, 那么在這個過程中就需要注意到循環利用ImageView的處理,否則會浪費很多的內存容易造成內存爆滿, 你可以使用兩個或者三個ImageView來實現, 具體的思路分析和實現可以參考這里, 或者參考MJPhotoBroswer自己來管理一個ImageView的重用機制

  • 從上面使用UIScrollView的分析中感覺到,要手動來實現重用還是需要做不少的工作, 這里筆者希望比較簡單高效的實現PhotoBroswer, 所以選擇了使用UICollectionView來實現, 因為它自帶有重用機制, 我們可以直接拿來使用, 如果不是很熟悉collectionView的使用,也不用太擔心, 本次不會用到它的很多高級的功能, 不過后面會提到一點collectionView的分頁使用技巧

2 . 實現網絡圖片的加載

  • 其實要很簡單加載服務器的圖片, 使用apple提供給我們的一些API就可以很簡單把圖片"加載"處理, 不過需要注意的是我們提到的只是能夠"加載"出來, 但是其中還有很多的細節需要處理, 比如,

a.你應該考慮異步加載圖片不要阻塞主線程, 那么當有多張圖片的時候,你需要處理多個線程的開銷和效率.
b. 對于加載完成的圖片你應該考慮緩存, 以便于之后能很快加載, 那么緩存你需要處理"內存緩存"(臨時緩存到內存,加載速度很快, 但是緩存多張圖片到內存的時候會消耗大量的內存, 所以需要管理緩存到內存的文件大小, 及時清空緩存)和"磁盤緩存"(持久化保存在沙盒)
相信僅僅是上面提到兩點, 一定會讓大多數的讀者感覺到很難實現(是的, 鑒于筆者也感覺到自己實現這個網絡圖片的加載的困難和自己的能力有限),所以我們應該考慮其他的方便的方法來實現, 當然上面給出了自己實現的思路, 有能力的朋友不妨自己去實現一下

  • 既然自己實現很麻煩, 就找第三方來幫忙了, 這里使用到了"王巍"寫的"kingfisher", 這個純swift的圖片加載庫提供了和SDWebImage相類似的接口使用很是方便, 同時很驚訝的是這個框架較好的實現了GIF圖片的加載(關于GIF圖片的加載后面可能會提到怎么去實現)

注意, 在使用kingfisher加載多張網絡圖片的時候, 你可能會注意到, xcode上面顯示的內存消耗是很大的, 在實現PhotoBrowser的時候, 我使用SDWebImage和Kingfisher加載了相同的圖片, 發現在xcode上面顯示的內存消耗兩者確實是相差很大的, 你會明顯的發現kingfisher消耗了比SDWebImage多很多的內存, 所以筆者當時去打擾了一下王巍, 他說到這兩個框架的實現思路是相似的, 內存消耗上不應該有很大的區別, 可能是xcode自身顯示的bug, 后來我用真機測試多張圖片確實是沒有收到內存警告, 所以大家可以放心的使用

思路寫的比較啰嗦, 下面進入實現部分

1). 自定義UICollectionViewCell用于展示每一張圖片

  • 新建文件PhotoViewCell, 這里直接把之前的PhotoView的代碼拿過來稍作改變就可以利用之前詳細寫一個功能完善的PhotoBrowser同時支持GIF(一)里寫的對單張圖片的處理, 因為只是換了一個容器而已
  • 新建文件PhotoModel來作為圖片模型, 因為每一個cell顯示一張圖片, 所以它擁有這張圖片的photoModel


    photoModel.png
這里在設置了photoModel的時候, 我么利用屬性觀察器來設置image

    private func setupImage() {
        // 首先判斷是否正確設置了photoModel
        guard let photo = photoModel else {
            assert(false, "設置的圖片模型不正確")
            return
        }
        
        // 如果是加載本地的圖片, 直接設置圖片即可, 注意這里是photoBrowser需要提升的地方
        // 因為對于本地圖片的加載沒有做處理, 所以當直接使用 UIImage(named"")的形式加載圖片的時候, 會消耗大量的內存
        // 不過鑒于參考了其他的圖片瀏覽器框架, 大家對本地圖片都沒有處理, 因為這個確實用的很少, 畢竟都是用來加載網絡圖片的情況比較多
        // 如果發現確實需要處理后面會努力處理這個問題
        if photo.localImage != nil {
            // 注意這個image的屬性觀察器中, 我處理了imageView的frame
            image = photo.localImage
            // 加載完成后直接返回
            return
        }
        
        // 加載網路圖片, 首先判斷url是否合法
        guard let urlString = photo.imageUrlString, let url = NSURL(string: urlString) else {
            assert(false, "設置的url不合法")
            return
        }
        // 設置默認圖片
        if let sourceImageView = photo.sourceImageView {
            image = sourceImageView.image
        }
        // 如果沒有提供默認的圖片, 就設置一張默認的圖片
        image =  image ?? UIImage(named: "2")

        // 這里使用kingfisher來加載網絡圖片 很簡單的調用
        imageView.kf_setImageWithURL(url, placeholderImage: image, optionsInfo: nil, progressBlock: {[weak self] (receivedSize, totalSize) in
            let progress = Double(receivedSize) / Double(totalSize)
            //            print(progress)
            // 這里面能夠獲取到加載進度, 便于提供進度條顯示
            
        }) {[weak self] (image, error, cacheType, imageURL) in
            // 加載完成
            // 注意: 因為這個閉包是多線程調用的 所以可能存在 沒有顯示完圖片,就點擊了返回
            // 這個時候self已經被銷毀了 所以使用[unonwed self] 將會導致"野指針"的問題
            // 使用 [weak self] 保證安全訪問self
            // 但是這也不是絕對安全的, 比如在 self 銷毀之前, 進入了這個閉包 那么strongSelf 有值 進入
            // 如果在這時恰好 self 銷毀了,那么之后調用strongSelf 都將會出錯crash
            // 可以考慮使用withExtendedLifetime
            //            withExtendedLifetime(self, { () -> self in
            //
            //            })
            if let strongSelf = self  {
                // 加載完成, 設置圖片, 觸發里面的屬性觀察器設置imageView
                strongSelf.image = image
                if let _ = image { return }
                // 提示加載錯誤
            }
        }
    }
  1. 新建文件PhotoBroswer來處理多張圖片的顯示
  • 設置collection view
    private lazy var collectionView: UICollectionView = {[unowned self] in
        let flowLayout = UICollectionViewFlowLayout()
        flowLayout.scrollDirection = .Horizontal
        // 每個cell的尺寸  -- 寬度設置為UICollectionView.bounds.size.width ---> 滾一頁就是一個完整的cell
        flowLayout.itemSize = CGSize(width: self.zj_width + PhotoBrowser.contentMargin, height: self.zj_height)
        flowLayout.minimumLineSpacing = 0.0
        flowLayout.minimumInteritemSpacing = 0.0
        flowLayout.sectionInset = UIEdgeInsetsZero
        
        // 分頁每次滾動 UICollectionView.bounds.size.width
        let collectionView = UICollectionView(frame: CGRect(x: 0.0, y: 0.0, width: self.zj_width + PhotoBrowser.contentMargin, height: self.zj_height), collectionViewLayout: flowLayout)
        collectionView.delegate = self
        collectionView.dataSource = self
        collectionView.pagingEnabled = true
        collectionView.registerClass(PhotoViewCell.self, forCellWithReuseIdentifier: PhotoBrowser.cellID)
        self.insertSubview(collectionView, atIndex: 0)
        
        return collectionView
    }()
  • 處理collection view的代理和datasource方法, 這里面比較容易理解
    func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
        return 1
    }
    
    func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return photoModels.count
    }
    
    func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCellWithReuseIdentifier(PhotoBrowser.cellID, forIndexPath: indexPath) as! PhotoViewCell
        // 避免出現重用出錯的問題, 大家可以試下注釋這行會帶來什么不想要的效果, 然后應該就理解了這個方法為何存在
        cell.resetUI()
        let currentModel = photoModels[indexPath.row]
        
        // 注意之前直接傳了self的一個函數給singleTapAction 造成了循環引用
        cell.singleTapAction = {[unowned self](ges: UITapGestureRecognizer) in
            self.dismiss()
        }
        
        return cell
    }
    
    // 這里監控collectionView的滾動, 是希望在滾動超過一半的時候改更改圖片的索引, 這個會在之后的toolBar上使用到, 來顯示索引
    func scrollViewDidScroll(scrollView: UIScrollView) {
        // 向下取整
        currentIndex = Int(scrollView.contentOffset.x / scrollView.zj_width + 0.5)

    }
  1. 前面提到的處理collection view的分頁的一點小技巧
  • 如果你只是簡單設置了collectionView.pagingEnabled = true,設置 flowLayout.itemSize = CGSize(width: self.zj_width , height: self.zj_height), 并且設置cell里面的scrollView和contentView的尺寸相同, 那么滾動的效果是這樣的
沒有間隙.png

我們希望兩張圖片之間有一定的間隙, 那么很直接, 直接將cell里的scrollView的寬度減少一點應該就可以了

    /// 懶加載
    lazy var scrollView: UIScrollView = {
        let scrollView = UIScrollView(frame: CGRect(x: 0.0, y: 0.0, width: self.contentView.zj_width - PhotoBrowser.contentMargin, height: self.contentView.zj_height))
間隙.gif

這樣圖片之間的間隙自然是出來的, 但是發現滾動完成后后面的圖片顯示不正常. 因為collectionView每次滾動一頁的寬度是UICollectionView.bounds.size.width, 所以和cell的尺寸沒有關系, 那么我們再處理一下

分頁分析.png
        // 每個cell的尺寸  -- 寬度設置為UICollectionView.bounds.size.width ---> 滾一頁就是一個完整的cell
        flowLayout.itemSize = CGSize(width: self.zj_width + PhotoBrowser.contentMargin, height: self.zj_height)

    /// cell中scrollView的尺寸
        let scrollView = UIScrollView(frame: CGRect(x: 0.0, y: 0.0, width: self.contentView.zj_width - PhotoBrowser.contentMargin, height: self.contentView.zj_height))

        // 分頁每次滾動 UICollectionView.bounds.size.width
        let collectionView = UICollectionView(frame: CGRect(x: 0.0, y: 0.0, width: self.zj_width + PhotoBrowser.contentMargin, height: self.zj_height), collectionViewLayout: flowLayout)

到目前為止, 多張圖片的顯示以及網絡圖片的加載處理基本就完整了,一個比較成型的PhotoBrowser就完成了, 至于里面的toolBar和提示框, 還有過渡動畫可能會在以后寫寫,歡迎關注


詳細請移步源碼,里面都有詳細的Demo使用示例 如果您覺得有幫助,不妨給個star鼓勵一下, 歡迎關注


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

推薦閱讀更多精彩內容

  • 因為要結局swift3.0中引用snapKit的問題,看到一篇介紹Xcode8,swift3變化的文章,覺得很詳細...
    uniapp閱讀 4,463評論 0 12
  • 前言 iOS里的UI控件其實沒有幾個,界面基本就是圍繞那么幾個控件靈活展開,最難的應屬UICollectionVi...
    alenpaulkevin閱讀 31,979評論 9 176
  • 昨天晚上突如其來的生病,突然感到自己好脆弱,在那種特別疼痛的狀態下,心里打翻了五味瓶,不是滋味,去急診的路上 心里...
    昊子_011c閱讀 199評論 0 0
  • 本人黨輝,生于1974年,父親是一名軍轉干部從小就繼承了不屈不撓不服輸的軍人精神。在不斷打拼的道路上一直激勵著我。...
    黨義強閱讀 408評論 0 1
  • 這也是多年前的事情了,我每次想起來都莫名的傷感。 當時工作在上海,壓力不大,薪水不高,工作輕松。上海當時ADSL剛...
    花開遍野閱讀 295評論 0 2