列表的单元格中包含有图片在开发中很常见。通常我们可以直接在tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath)中给单元格设置图片。 



但有时这些图片要从远程加载,或者要给图片作裁减,添加滤镜等操作。如果这些操作还是直接在主线程中进行,由于上下拖动表格滚动条的时候,单元格渲染是实时进行的。那么单元格便会不断地进行图片加载,渲染,影响效率造成卡顿。如果图片大的话还会浪费流量。 




下面通过一个展示“热门电影”表格,演示如何进行优化。 




1,最初的代码


这个是功能的最简单实现,后面我们要在这个基础上进行改进。


UITableView里除了显示文字,还从网络下载海报图片。同时为了使CPU资源占用更明显,还给图片添加了棕褐色滤镜。


由于这些都在主线程操作,可以发现滚动表格的时候,界面比较卡。(而且下载过的图片由于没有保存本地,来回拖动滚动条会发现图片在重复下载。)



Swift - 表格图片加载优化(拖动表格时不加载,停止时只加载当前页图片)_图片下载




movies.plist - 存储电影名字和对应的海报url地址


(下面是部分代码片断)




1


2


3


4


5


6


7


8


9


10


11


12


13


14


15


16


17


18


19


20


21


22




​<​​​​plist​​ ​​version​​​​=​​​​"1.0"​​​​>​


​<​​​​dict​​​​>​


​<​​​​key​​​​>港囧</​​​​key​​​​>​


​<​​​​string​​​​>http://img3.douban.com/view/movie_poster_cover/spst/public/p2266145079.jpg</​​​​string​​​​>​


​<​​​​key​​​​>第三种爱情</​​​​key​​​​>​


​<​​​​string​​​​>http://img3.douban.com/view/movie_poster_cover/mpst/public/p2267236332.jpg</​​​​string​​​​>​


​<​​​​key​​​​>碟中谍5</​​​​key​​​​>​


​<​​​​string​​​​>http://img3.douban.com/view/movie_poster_cover/mpst/public/p2263582212.jpg</​​​​string​​​​>​


​<​​​​key​​​​>小黄人大眼萌</​​​​key​​​​>​


​<​​​​string​​​​>http://img3.douban.com/view/movie_poster_cover/mpst/public/p2265761240.jpg</​​​​string​​​​>​


​<​​​​key​​​​>夏洛特烦恼</​​​​key​​​​>​


​<​​​​string​​​​>http://img3.douban.com/view/movie_poster_cover/mpst/public/p2264377763.jpg</​​​​string​​​​>​


​<​​​​key​​​​>魔镜</​​​​key​​​​>​


​<​​​​string​​​​>http://img4.douban.com/view/movie_poster_cover/mpst/public/p2266110047.jpg</​​​​string​​​​>​


​<​​​​key​​​​>长江7号</​​​​key​​​​>​


​<​​​​string​​​​>http://img3.douban.com/view/movie_poster_cover/mpst/public/p2268754172.jpg</​​​​string​​​​>​


​<​​​​key​​​​>九层妖塔</​​​​key​​​​>​


​<​​​​string​​​​>http://img4.douban.com/view/movie_poster_cover/mpst/public/p2267502788.jpg</​​​​string​​​​>​


​<​​​​key​​​​>hangge.com</​​​​key​​​​>​


​<​​​​string​​​​>NO URL</​​​​string​​​​>​


​</​​​​dict​​​​>​


​</​​​​plist​​​​>​



ListViewController.swift - 主页面代码



1


2


3


4


5


6


7


8


9


10


11


12


13


14


15


16


17


18


19


20


21


22


23


24


25


26


27


28


29


30


31


32


33


34


35


36


37


38


39


40


41


42


43


44


45


46


47


48


49


50


51


52


53


54


55


56


57


58


59


60


61


62


63


64


65


66


67




​import​​ ​​UIKit​


​import​​ ​​CoreImage​


 


​//图片数据源地址​


​let​​ ​​dataSourcePath = ​​​​NSBundle​​​​.mainBundle().pathForResource(​​​​"movies"​​​​, ofType: ​​​​"plist"​​​​)​


 


​class​​ ​​ListViewController​​​​: ​​​​UITableViewController​​ ​​{​


 


​//电影图片字典集合(使用了懒加载)​


​lazy​​ ​​var​​ ​​movies = ​​​​NSDictionary​​​​(contentsOfFile: dataSourcePath!)!​


 


​override​​ ​​func​​ ​​viewDidLoad() {​


​super​​​​.viewDidLoad()​


​self​​​​.title = ​​​​"热门电影"​


​}​


 


​override​​ ​​func​​ ​​didReceiveMemoryWarning() {​


​super​​​​.didReceiveMemoryWarning()​


​}​


 


​//获取记录数​


​override​​ ​​func​​ ​​tableView(tableView: ​​​​UITableView​​​​?, numberOfRowsInSection section: ​​​​Int​​​​) -> ​​​​Int​​ ​​{​


​return​​ ​​movies.count​


​}​


 


​//创建单元格​


​override​​ ​​func​​ ​​tableView(tableView: ​​​​UITableView​​​​, cellForRowAtIndexPath indexPath: ​​​​NSIndexPath​​​​)​


​-> ​​​​UITableViewCell​​ ​​{​


​let​​ ​​cell = tableView.dequeueReusableCellWithIdentifier(​​​​"CellIdentifier"​​​​,​


​forIndexPath: indexPath) ​​​​as​​ ​​UITableViewCell​


 


​//设置单元格文本​


​let​​ ​​rowKey = movies.allKeys[indexPath.row] ​​​​as​​​​! ​​​​String​


​cell.textLabel?.text = rowKey​


 


​//设置单元格图片​


​var​​ ​​image : ​​​​UIImage​​​​?​


​if​​ ​​let​​ ​​imageURL = ​​​​NSURL​​​​(string:movies[rowKey] ​​​​as​​​​! ​​​​String​​​​) {​


​let​​ ​​imageData = ​​​​NSData​​​​(contentsOfURL:imageURL)​


​let​​ ​​unfilteredImage = ​​​​UIImage​​​​(data:imageData!)​


​image = ​​​​self​​​​.applySepiaFilter(unfilteredImage!)​


​}​


 


​if​​ ​​image != ​​​​nil​​ ​​{​


​cell.imageView?.image = image!​


​}​​​​else​​​​{​


​//cell.imageView?.image = nil //未加载到海报则空白​


​cell.imageView?.image = ​​​​UIImage​​​​(named: ​​​​"failed"​​​​) ​​​​//未加载到海报显示默认的“暂无图片”​


​}​


 


​return​​ ​​cell​


​}​


 


​//给图片添加棕褐色滤镜​


​func​​ ​​applySepiaFilter(image:​​​​UIImage​​​​) -> ​​​​UIImage​​​​? {​


​let​​ ​​inputImage = ​​​​CIImage​​​​(data:​​​​UIImagePNGRepresentation​​​​(image)!)​


​let​​ ​​context = ​​​​CIContext​​​​(options:​​​​nil​​​​)​


​let​​ ​​filter​​ ​​= ​​​​CIFilter​​​​(name:​​​​"CISepiaTone"​​​​)​


​filter​​​​!.setValue(inputImage, forKey: kCIInputImageKey)​


​filter​​​​!.setValue(0.8, forKey: ​​​​"inputIntensity"​​​​)​


​if​​ ​​let​​ ​​outputImage = ​​​​filter​​​​!.outputImage {​


​let​​ ​​outImage = context.createCGImage(outputImage, fromRect: outputImage.extent)​


​return​​ ​​UIImage​​​​(​​​​CGImage​​​​: outImage)​


​}​


​return​​ ​​nil​


​}​


​}​



源码下载: Swift - 表格图片加载优化(拖动表格时不加载,停止时只加载当前页图片)_滤镜_02​T1.zip​

2,功能改进:使用后台线程进行图片下载,和滤镜添加

(1)通过使用NSOperation和NSOperationQueue,创建两个队列。一个用来下载图片,一个用来处理图片。由于这些不在主线程中进行,所以拖动滚动条的时候就不会卡顿了。


(2)下载和滤镜是分别实现,因为有可能在图片下载时,用户将图片划动出屏幕了,这时不会再添加滤镜。当下一次用户划到当前行时,就不需要再下载图片了;只需要添加滤镜!这样可以提高效率。


(3)为便于观察,这里将maxConcurrentOperationCount设置为1。表示一个队列中同时只有一个线程运行。(实际应用中可根据情况多开几个线程)


(4)开始的时候,会显示默认占位图片。图片下载成功后,会替换显示真实图片。图片下载失败,则显示失败图片。滤镜处理完毕后,替换显示处理后的图片。


(5)为增强用户体验,刚开始单元格尾部附件图片是一个转圈动画。一旦图片处理完毕或下载失败,便将其去除。



Swift - 表格图片加载优化(拖动表格时不加载,停止时只加载当前页图片)_滤镜_03




(1)电影条目类和状态枚举 - MovieRecord.swift




1


2


3


4


5


6


7


8


9


10


11


12


13


14


15


16


17


18


19


20




​import​​ ​​UIKit​


 


​// 这个枚举包含所有电影图片的状态​


​enum​​ ​​MovieRecordState​​ ​​{​


​case​​ ​​New​​​​, ​​​​Downloaded​​​​, ​​​​Filtered​​​​, ​​​​Failed​


​}​


 


​// 电影条目类​


​class​​ ​​MovieRecord​​ ​​{​


​let​​ ​​name:​​​​String​


​let​​ ​​url:​​​​NSURL​


​var​​ ​​state = ​​​​MovieRecordState​​​​.​​​​New​


​//默认初始图片​


​var​​ ​​image = ​​​​UIImage​​​​(named: ​​​​"placeholder"​​​​)​


 


​init​​​​(name:​​​​String​​​​, url:​​​​NSURL​​​​) {​


​self​​​​.name = name​


​self​​​​.url = url​


​}​


​}​



(2)队列管理类 - MovieOperations.swift




1


2


3


4


5


6


7


8


9


10


11


12


13


14


15


16


17


18


19


20


21


22


23


24




​import​​ ​​Foundation​


 


​//队列管理类,追踪每个操作的状态​


​class​​ ​​MovieOperations​​ ​​{​


​//追踪进行中的和等待中的下载操作​


​lazy​​ ​​var​​ ​​downloadsInProgress = [​​​​NSIndexPath​​​​:​​​​NSOperation​​​​]()​


​//图片下载队列​


​lazy​​ ​​var​​ ​​downloadQueue:​​​​NSOperationQueue​​ ​​= {​


​var​​ ​​queue = ​​​​NSOperationQueue​​​​()​


​queue.name = ​​​​"Download queue"​


​queue.maxConcurrentOperationCount = 1​


​return​​ ​​queue​


​}()​


 


​//追踪进行中的和等待中的滤镜操作​


​lazy​​ ​​var​​ ​​filtrationsInProgress = [​​​​NSIndexPath​​​​:​​​​NSOperation​​​​]()​


​//图片处理队列​


​lazy​​ ​​var​​ ​​filtrationQueue:​​​​NSOperationQueue​​ ​​= {​


​var​​ ​​queue = ​​​​NSOperationQueue​​​​()​


​queue.name = ​​​​"Image Filtration queue"​


​queue.maxConcurrentOperationCount = 1​


​return​​ ​​queue​


​}()​


​}​



(3)图片下载操作任务类 - ImageDownloader.swift




1


2


3


4


5


6


7


8


9


10


11


12


13


14


15


16


17


18


19


20


21


22


23


24


25


26


27


28


29


30


31


32


33


34


35


36


37


38


39




​import​​ ​​UIKit​


 


​//图片下载操作任务​


​class​​ ​​ImageDownloader​​​​: ​​​​NSOperation​​ ​​{​


​//电影条目对象​


​let​​ ​​movieRecord: ​​​​MovieRecord​


 


​init​​​​(movieRecord: ​​​​MovieRecord​​​​) {​


​self​​​​.movieRecord = movieRecord​


​}​


 


​//在子类中重载NSOperation的main方法来执行实际的任务。​


​override​​ ​​func​​ ​​main() {​


​//在开始执行前检查撤消状态。任务在试图执行繁重的工作前应该检查它是否已经被撤消。​


​if​​ ​​self​​​​.cancelled {​


​return​


​}​


​//sleep(1) //这个只是为了便于测试观察 ​


 


​//下载图片。​


​let​​ ​​imageData = ​​​​NSData​​​​(contentsOfURL:​​​​self​​​​.movieRecord.url)​


 


​//再一次检查撤销状态。​


​if​​ ​​self​​​​.cancelled {​


​return​


​}​


 


​//如果有数据,创建一个图片对象并加入记录,然后更改状态。如果没有数据,将记录标记为失败并设置失败图片。​


​if​​ ​​imageData?.length > 0 {​


​self​​​​.movieRecord.image = ​​​​UIImage​​​​(data:imageData!)​


​self​​​​.movieRecord.state = .​​​​Downloaded​


​}​


​else​


​{​


​self​​​​.movieRecord.state = .​​​​Failed​


​self​​​​.movieRecord.image = ​​​​UIImage​​​​(named: ​​​​"failed"​​​​)​


​}​


​}​


​}​



(4)滤镜处理任务类 - ImageFiltration.swift




1


2


3


4


5


6


7


8


9


10


11


12


13


14


15


16


17


18


19


20


21


22


23


24


25


26


27


28


29


30


31


32


33


34


35


36


37


38


39


40


41


42


43


44


45


46


47


48


49


50




​import​​ ​​UIKit​


​import​​ ​​CoreImage​


 


​//滤镜处理任务​


​class​​ ​​ImageFiltration​​​​: ​​​​NSOperation​​ ​​{​


​//电影条目对象​


​let​​ ​​movieRecord: ​​​​MovieRecord​


 


​init​​​​(movieRecord: ​​​​MovieRecord​​​​) {​


​self​​​​.movieRecord = movieRecord​


​}​


 


​//在子类中重载NSOperation的main方法来执行实际的任务。​


​override​​ ​​func​​ ​​main () {​


​if​​ ​​self​​​​.cancelled {​


​return​


​}​


 


​if​​ ​​self​​​​.movieRecord.state != .​​​​Downloaded​​ ​​{​


​return​


​}​


 


​if​​ ​​let​​ ​​filteredImage = ​​​​self​​​​.applySepiaFilter(​​​​self​​​​.movieRecord.image!) {​


​self​​​​.movieRecord.image = filteredImage​


​self​​​​.movieRecord.state = .​​​​Filtered​


​}​


​}​


 


​//给图片添加棕褐色滤镜​


​func​​ ​​applySepiaFilter(image:​​​​UIImage​​​​) -> ​​​​UIImage​​​​? {​


​let​​ ​​inputImage = ​​​​CIImage​​​​(data:​​​​UIImagePNGRepresentation​​​​(image)!)​


 


​if​​ ​​self​​​​.cancelled {​


​return​​ ​​nil​


​}​


​let​​ ​​context = ​​​​CIContext​​​​(options:​​​​nil​​​​)​


​let​​ ​​filter​​ ​​= ​​​​CIFilter​​​​(name:​​​​"CISepiaTone"​​​​)​


​filter​​​​?.setValue(inputImage, forKey: kCIInputImageKey)​


​filter​​​​?.setValue(0.8, forKey: ​​​​"inputIntensity"​​​​)​


​let​​ ​​outputImage = ​​​​filter​​​​?.outputImage​


 


​if​​ ​​self​​​​.cancelled {​


​return​​ ​​nil​


​}​


 


​let​​ ​​outImage = context.createCGImage(outputImage!, fromRect: outputImage!.extent)​


​let​​ ​​returnImage = ​​​​UIImage​​​​(​​​​CGImage​​​​: outImage)​


​return​​ ​​returnImage​


​}​


​}​



(5)主页代码 - ListViewController.swift




1


2


3


4


5


6


7


8


9


10


11


12


13


14


15


16


17


18


19


20


21


22


23


24


25


26


27


28


29


30


31


32


33


34


35


36


37


38


39


40


41


42


43


44


45


46


47


48


49


50


51


52


53


54


55


56


57


58


59


60


61


62


63


64


65


66


67


68


69


70


71


72


73


74


75


76


77


78


79


80


81


82


83


84


85


86


87


88


89


90


91


92


93


94


95


96


97


98


99


100


101


102


103


104


105


106


107


108


109


110


111


112


113


114


115


116


117


118


119


120


121


122


123


124


125


126




​import​​ ​​UIKit​


​import​​ ​​CoreImage​


 


​class​​ ​​ListViewController​​​​: ​​​​UITableViewController​​ ​​{​


 


​var​​ ​​movies = [​​​​MovieRecord​​​​]()​


​let​​ ​​movieOperations = ​​​​MovieOperations​​​​()​


 


​override​​ ​​func​​ ​​viewDidLoad() {​


​super​​​​.viewDidLoad()​


​self​​​​.title = ​​​​"热门电影"​


 


​//加载,处理电影列表数据​


​fetchMovieDetails();​


​}​


 


​override​​ ​​func​​ ​​didReceiveMemoryWarning() {​


​super​​​​.didReceiveMemoryWarning()​


​}​


 


​//加载,处理电影列表数据​


​func​​ ​​fetchMovieDetails() {​


​//图片数据源地址​


​let​​ ​​dataSourcePath = ​​​​NSBundle​​​​.mainBundle().pathForResource(​​​​"movies"​​​​, ofType: ​​​​"plist"​​​​)​


​let​​ ​​datasourceDictionary = ​​​​NSDictionary​​​​(contentsOfFile: dataSourcePath!)​


 


​for​​​​(key,value) ​​​​in​​ ​​datasourceDictionary!{​


​let​​ ​​name = key ​​​​as​​​​? ​​​​String​


​let​​ ​​url = ​​​​NSURL​​​​(string:value ​​​​as​​​​? ​​​​String​​ ​​?? ​​​​""​​​​)​


​if​​ ​​name != ​​​​nil​​ ​​&& url != ​​​​nil​​ ​​{​


​let​​ ​​movieRecord = ​​​​MovieRecord​​​​(name:name!, url:url!)​


​self​​​​.movies.append(movieRecord)​


​}​


​}​


 


​self​​​​.tableView.reloadData()​


​}​


 


​//获取记录数​


​override​​ ​​func​​ ​​tableView(tableView: ​​​​UITableView​​​​?, numberOfRowsInSection section: ​​​​Int​​​​) -> ​​​​Int​​ ​​{​


​return​​ ​​movies.count​


​}​


 


​//创建单元格​


​override​​ ​​func​​ ​​tableView(tableView: ​​​​UITableView​​​​, cellForRowAtIndexPath indexPath: ​​​​NSIndexPath​​​​)​


​-> ​​​​UITableViewCell​​ ​​{​


​let​​ ​​cell = tableView.dequeueReusableCellWithIdentifier(​​​​"CellIdentifier"​​​​,​


​forIndexPath: indexPath) ​​​​as​​ ​​UITableViewCell​


 


​//为了提示用户,将cell的accessory view设置为UIActivityIndicatorView。​


​if​​ ​​cell.accessoryView == ​​​​nil​​ ​​{​


​let​​ ​​indicator = ​​​​UIActivityIndicatorView​​​​(activityIndicatorStyle: .​​​​Gray​​​​)​


​cell.accessoryView = indicator​


​}​


​let​​ ​​indicator = cell.accessoryView ​​​​as​​​​! ​​​​UIActivityIndicatorView​


 


​//获取当前行所对应的电影记录。​


​let​​ ​​movieRecord = movies[indexPath.row]​


 


​//设置文本和图片​


​cell.textLabel?.text = movieRecord.name​


​cell.imageView?.image = movieRecord.image​


 


​//检查图片状态。设置适当的activity indicator 和文本,然后开始执行任务​


​switch​​ ​​(movieRecord.state){​


​case​​ ​​.​​​​Filtered​​​​:​


​indicator.stopAnimating()​


​case​​ ​​.​​​​Failed​​​​:​


​indicator.stopAnimating()​


​cell.textLabel?.text = ​​​​"Failed to load"​


​case​​ ​​.​​​​New​​​​:​


​indicator.startAnimating()​


​startDownloadForRecord(movieRecord, indexPath: indexPath)​


​case​​ ​​.​​​​Downloaded​​​​:​


​indicator.startAnimating()​


​startFiltrationForRecord(movieRecord, indexPath: indexPath)​


​}​


 


​return​​ ​​cell​


​}​


 


​//执行图片下载任务​


​func​​ ​​startDownloadForRecord(movieRecord: ​​​​MovieRecord​​​​, indexPath: ​​​​NSIndexPath​​​​){​


​//判断队列中是否已有该图片任务​


​if​​ ​​let​​ ​​_ = movieOperations.downloadsInProgress[indexPath] {​


​return​


​}​


 


​//创建一个下载任务​


​let​​ ​​downloader = ​​​​ImageDownloader​​​​(movieRecord: movieRecord)​


​//任务完成后重新加载对应的单元格​


​downloader.completionBlock = {​


​if​​ ​​downloader.cancelled {​


​return​


​}​


​dispatch_async(dispatch_get_main_queue(), {​


​self​​​​.movieOperations.downloadsInProgress.removeValueForKey(indexPath)​


​self​​​​.tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .​​​​Fade​​​​)​


​})​


​}​


​//记录当前下载任务​


​movieOperations.downloadsInProgress[indexPath] = downloader​


​//将任务添加到队列中​


​movieOperations.downloadQueue.addOperation(downloader)​


​}​


 


​//执行图片滤镜任务​


​func​​ ​​startFiltrationForRecord(movieRecord: ​​​​MovieRecord​​​​, indexPath: ​​​​NSIndexPath​​​​){​


​if​​ ​​let​​ ​​_ = movieOperations.filtrationsInProgress[indexPath]{​


​return​


​}​


 


​let​​ ​​filterer = ​​​​ImageFiltration​​​​(movieRecord: movieRecord)​


​filterer.completionBlock = {​


​if​​ ​​filterer.cancelled {​


​return​


​}​


​dispatch_async(dispatch_get_main_queue(), {​


​self​​​​.movieOperations.filtrationsInProgress.removeValueForKey(indexPath)​


​self​​​​.tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .​​​​Fade​​​​)​


​})​


​}​


​movieOperations.filtrationsInProgress[indexPath] = filterer​


​movieOperations.filtrationQueue.addOperation(filterer)​


​}​


​}​



源码下载:Swift - 表格图片加载优化(拖动表格时不加载,停止时只加载当前页图片)_滤镜_02​T2.zip​



3,功能再次改进:拖动滚动条的时候不加载,停止后只加载当前页


当滚动表格时,比如快速的滚动到最后一条数据。虽然前面的单元格都已移出可视区域。但其实这些图片的下载和处理任务已经添加到后台队列中,并默默地在执行。


(1)解决这个问题,就要保证表格在滚动的情况下,不添加任务到队列中。而且只有当表格停止后,只把当前可见区域的图片添加到任务队列中。


(2)同时,对于那些原来在可视区域的队列任务。如果对应单元格被移出可视区域,那么其任务也要取消。




只需在主页面代码里稍作修改即可:




1


2


3


4


5


6


7


8


9


10


11


12


13


14


15


16


17


18


19


20


21


22


23


24


25


26


27


28


29


30


31


32


33


34


35


36


37


38


39


40


41


42


43


44


45


46


47


48


49


50


51


52


53


54


55


56


57


58


59


60


61


62


63


64


65


66


67


68


69


70


71


72


73


74


75


76


77


78


79


80


81


82


83


84


85


86


87


88


89


90


91


92


93


94


95


96


97


98


99


100


101


102


103


104


105


106


107


108


109


110


111


112


113


114


115


116


117


118


119


120


121


122


123


124


125


126


127


128


129


130


131


132


133


134


135


136


137


138


139


140


141


142


143


144


145


146


147


148


149


150


151


152


153


154


155


156


157


158


159


160


161


162


163


164


165


166


167


168


169


170


171


172


173


174


175


176


177


178


179


180


181


182


183


184


185


186


187


188


189


190


191


192


193


194


195


196


197


198


199


200


201


202


203


204


205


206


207


208


209


210


211


212


213


214


215


216


217


218


219


220


221




importUIKit


importCoreImage


 


classListViewController: UITableViewController{


     


    varmovies = [MovieRecord]()


    letmovieOperations = MovieOperations()


     


    overridefuncviewDidLoad() {


        super.viewDidLoad()


        self.title = "热门电影"


         


        //加载,处理电影列表数据


        fetchMovieDetails();


    }


     


    overridefuncdidReceiveMemoryWarning() {


        super.didReceiveMemoryWarning()


    }


     


    //加载,处理电影列表数据


    funcfetchMovieDetails() {


        //图片数据源地址


        letdataSourcePath = NSBundle.mainBundle().pathForResource("movies",


            ofType: "plist")


        letdatasourceDictionary = NSDictionary(contentsOfFile: dataSourcePath!)


         


        for(key,value) indatasourceDictionary!{


            letname = key as? String


            leturl = NSURL(string:value as? String?? "")


            ifname != nil&& url != nil{


                letmovieRecord = MovieRecord(name:name!, url:url!)


                self.movies.append(movieRecord)


            }


        }


         


        self.tableView.reloadData()


    }


     


    //获取记录数


    overridefunctableView(tableView: UITableView?, numberOfRowsInSection section: Int)


        -> Int{


        returnmovies.count


    }


     


    //创建单元格


    overridefunctableView(tableView: UITableView,


        cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{


            letcell = tableView.dequeueReusableCellWithIdentifier("CellIdentifier",


                forIndexPath: indexPath) asUITableViewCell


             


            //为了提示用户,将cell的accessory view设置为UIActivityIndicatorView。


            ifcell.accessoryView == nil{


                letindicator = UIActivityIndicatorView(activityIndicatorStyle: .Gray)


                cell.accessoryView = indicator


            }


            letindicator = cell.accessoryView as! UIActivityIndicatorView


             


            //获取当前行所对应的电影记录。


            letmovieRecord = movies[indexPath.row]


             


            //设置文本和图片


            cell.textLabel?.text = movieRecord.name


            cell.imageView?.image = movieRecord.image


             


            //检查图片状态。设置适当的activity indicator 和文本,然后开始执行任务


            switch(movieRecord.state){


            case.Filtered:


                indicator.stopAnimating()


            case.Failed:


                indicator.stopAnimating()


                cell.textLabel?.text = "Failed to load"


            case.New, .Downloaded:


                indicator.startAnimating()


                //只有停止拖动的时候才加载


                if(!tableView.dragging && !tableView.decelerating) {


                    self.startOperationsForMovieRecord(movieRecord, indexPath: indexPath)


                }


            }


             


            returncell


    }


     


    //图片任务


    funcstartOperationsForMovieRecord(movieRecord: MovieRecord, indexPath: NSIndexPath){


        switch(movieRecord.state) {


        case.New:


            startDownloadForRecord(movieRecord, indexPath: indexPath)


        case.Downloaded:


            startFiltrationForRecord(movieRecord, indexPath: indexPath)


        default:


            NSLog("do nothing")


        }


    }


     


    //执行图片下载任务


    funcstartDownloadForRecord(movieRecord: MovieRecord, indexPath: NSIndexPath){


        //判断队列中是否已有该图片任务


        iflet_ = movieOperations.downloadsInProgress[indexPath] {


            return


        }


         


        //创建一个下载任务


        letdownloader = ImageDownloader(movieRecord: movieRecord)


        //任务完成后重新加载对应的单元格


        downloader.completionBlock = {


            ifdownloader.cancelled {


                return


            }


            dispatch_async(dispatch_get_main_queue(), {


                self.movieOperations.downloadsInProgress.removeValueForKey(indexPath)


                self.tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)


            })


        }


        //记录当前下载任务


        movieOperations.downloadsInProgress[indexPath] = downloader


        //将任务添加到队列中


        movieOperations.downloadQueue.addOperation(downloader)


    }


     


    //执行图片滤镜任务


    funcstartFiltrationForRecord(movieRecord: MovieRecord, indexPath: NSIndexPath){


        iflet_ = movieOperations.filtrationsInProgress[indexPath]{


            return


        }


         


        letfilterer = ImageFiltration(movieRecord: movieRecord)


        filterer.completionBlock = {


            iffilterer.cancelled {


                return


            }


            dispatch_async(dispatch_get_main_queue(), {


                self.movieOperations.filtrationsInProgress.removeValueForKey(indexPath)


                self.tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)


            })


        }


        movieOperations.filtrationsInProgress[indexPath] = filterer


        movieOperations.filtrationQueue.addOperation(filterer)


    }


     


    //视图开始滚动


    overridefuncscrollViewWillBeginDragging(scrollView: UIScrollView) {


        //一旦用户开始滚动屏幕,你将挂起所有任务并留意用户想要看哪些行。


        suspendAllOperations()


    }


     


    //视图停止拖动


    overridefuncscrollViewDidEndDragging(scrollView: UIScrollView,


        willDecelerate decelerate: Bool) {


        //如果减速(decelerate)是 false ,表示用户停止拖拽tableview。


        //此时你要继续执行之前挂起的任务,撤销不在屏幕中的cell的任务并开始在屏幕中的cell的任务。


        if!decelerate {


            loadImagesForOnscreenCells()


            resumeAllOperations()


        }


    }


     


    //视图停止减速


    overridefuncscrollViewDidEndDecelerating(scrollView: UIScrollView) {


        //这个代理方法告诉你tableview停止滚动,执行操作同上


        loadImagesForOnscreenCells()


        resumeAllOperations()


    }


     


    //暂停所有队列


    funcsuspendAllOperations () {


        movieOperations.downloadQueue.suspended = true


        movieOperations.filtrationQueue.suspended = true


    }


     


    //恢复运行所有队列


    funcresumeAllOperations () {


        movieOperations.downloadQueue.suspended = false


        movieOperations.filtrationQueue.suspended = false


    }


     


    //加载可见区域的单元格图片


    funcloadImagesForOnscreenCells () {      


        //开始将tableview可见行的index path放入数组中。


        ifletpathsArray = self.tableView.indexPathsForVisibleRows {


            //通过组合所有下载队列和滤镜队列中的任务来创建一个包含所有等待任务的集合


            letallMovieOperations = NSMutableSet()


            forkey inmovieOperations.downloadsInProgress.keys{


                allMovieOperations.addObject(key)


            }


            forkey inmovieOperations.filtrationsInProgress.keys{


                allMovieOperations.addObject(key)


            }


             


            //构建一个需要撤销的任务的集合。从所有任务中除掉可见行的index path,


            //剩下的就是屏幕外的行所代表的任务。


            lettoBeCancelled = allMovieOperations.mutableCopy() as! NSMutableSet


            letvisiblePaths = NSSet(array: pathsArray)


            toBeCancelled.minusSet(visiblePaths asSet<NSObject>)


             


            //创建一个需要执行的任务的集合。从所有可见index path的集合中除去那些已经在等待队列中的。


            lettoBeStarted = visiblePaths.mutableCopy() as! NSMutableSet


            toBeStarted.minusSet(allMovieOperations asSet<NSObject>)


             


            // 遍历需要撤销的任务,撤消它们,然后从 movieOperations 中去掉它们


            forindexPath intoBeCancelled {


                letindexPath = indexPath as! NSIndexPath


                ifletmovieDownload = movieOperations.downloadsInProgress[indexPath] {


                    movieDownload.cancel()


                }


                movieOperations.downloadsInProgress.removeValueForKey(indexPath)


                ifletmovieFiltration = movieOperations.filtrationsInProgress[indexPath] {


                    movieFiltration.cancel()


                }


                movieOperations.filtrationsInProgress.removeValueForKey(indexPath)


            }


             


            // 遍历需要开始的任务,调用 startOperationsForPhotoRecord


            forindexPath intoBeStarted {


                letindexPath = indexPath as! NSIndexPath


                letrecordToProcess = self.movies[indexPath.row]


                startOperationsForMovieRecord(recordToProcess, indexPath: indexPath)


            }


        }


    }


}






源码下载:Swift - 表格图片加载优化(拖动表格时不加载,停止时只加载当前页图片)_滤镜_02T3.zip