-
自定義單元格
import UIKit
import RxSwift
import RxCocoa
//單元格類
class MyTableCell: UITableViewCell {
var disposeBag: DisposeBag?
//單元格尾部的活動指示器
var indicator:UIActivityIndicatorView!
//當(dāng)前顯示的數(shù)據(jù)
var movieRecord:MovieRecord! {
didSet{
let disposeBag = DisposeBag() //保證 cell 被重用的時候不會被多次訂閱
//設(shè)置標(biāo)題
self.textLabel?.text = movieRecord.name
//先判斷圖片當(dāng)前的狀態(tài)
if movieRecord.state == .filtered || movieRecord.state == .failed {
//之前已經(jīng)處理完畢的就直接顯示出來
self.imageView?.image = movieRecord.image
// 也可以保存到緩存中,在這里從緩存中拿數(shù)據(jù)
// let img = QSFileManager.qs_readFromFile(in: .cache, contentType: .data, fileName: movieRecord.name + ".txt")
// self.imageView?.image = UIImage.init(data: img as! Data)
}else{
//只要圖片狀態(tài)是沒處理完畢的,一律先顯示個占位符圖片
self.imageView?.image = UIImage(named: "placeholder")
//顯示活動指示器
self.indicator.startAnimating()
//訂閱序列
Observable.of(movieRecord)
.delay(0.3, scheduler: MainScheduler.instance) //延遲0.3秒
.observeOn(ConcurrentDispatchQueueScheduler(qos: .userInitiated))//后臺
.flatMap { return self.fetchImage($0) } //下載電影圖片
.flatMap { self.applySepiaFilter($0) } //給圖片添加濾鏡
.observeOn(MainScheduler.instance) //回到主線程顯示圖片
.subscribe(onNext: {
self.imageView?.image = $0.image
self.indicator.stopAnimating()
})
.disposed(by: disposeBag) //cell離開可視區(qū)域后自動取消訂閱
}
self.disposeBag = disposeBag
}
}
//單元格重用時調(diào)用
override func prepareForReuse() {
super.prepareForReuse()
disposeBag = nil //保證 cell 被重用的時候不會被多次訂閱
}
//初始化
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
//為了提示用戶,將cell的accessory view設(shè)置為UIActivityIndicatorView。
indicator = UIActivityIndicatorView(activityIndicatorStyle: .gray)
self.accessoryView = indicator
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
//獲取圖片
func fetchImage(_ movie:MovieRecord) -> Observable<MovieRecord> {
//如果當(dāng)前的電影條目還未獲取圖片則去獲取圖片
if movie.state == .new {
if let imageData = try? Data(contentsOf: movie.url){
movie.image = UIImage(data:imageData)
movie.state = .downloaded //圖片狀態(tài)改成下載成功
}else{
movie.image = UIImage(named: "failed")! //未加載到海報顯示默認(rèn)的“暫無圖片”
movie.state = .failed //圖片狀態(tài)改成失敗
}
}
return Observable.of(movie)
}
//給圖片添加棕褐色濾鏡
func applySepiaFilter(_ movie:MovieRecord) -> Observable<MovieRecord> {
//只有剛下載的圖片需要處理
if movie.state == .downloaded {
let inputImage = CIImage(data:UIImagePNGRepresentation(movie.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, from: outputImage.extent)
movie.image = UIImage(cgImage: outImage!)
movie.state = .filtered //圖片狀態(tài)改成添加濾鏡成功
} else {
movie.image = UIImage(named: "failed")! //處理失敗的海報顯示默認(rèn)的“暫無圖片”
movie.state = .failed //圖片狀態(tài)改成失敗
}
// 圖片處理完保存到緩存中
// _ = QSFileManager.qs_writeToFile(in: .cache, data: NSData.init(data: UIImagePNGRepresentation(movie.image!)!) as AnyObject, fileName: movie.name + ".txt")
}
return Observable.of(movie)
}
}
-
QSFileManager
/**
* 把數(shù)據(jù)寫入文件
*
* @param pathType 放在哪個路徑下,Document或Cache,默認(rèn) unKnown
* @param data 要寫入的數(shù)據(jù)
* @param fileName 文件名,可以一個文件名,也可以傳一個路徑,如果pathType為unKnown,需要傳入完整路徑
*
* return 是否寫入成功
*/
class func qs_writeToFile(in pathType: QSPathType = .unKnown, data: AnyObject, fileName: String) -> Bool {
// 獲取路徑
var path = String.init()
switch pathType {
case .document:
path = self.qs_getDocumentPath()
case .cache:
path = self.qs_getCachePath()
case .unKnown:
path = fileName
}
// 文件路徑
if !fileName.hasPrefix("/") {
path.append("/")
}
let filePath = path + fileName
return data.write(toFile: filePath, atomically: true)
}
/**
* 讀取文件內(nèi)容
*
* @param pathType 放在哪個路徑下,Document或Cache,默認(rèn) unKnown
* @param contentType 文件內(nèi)容的類型,默認(rèn) string
* @param fileName 文件名,可以一個文件名,也可以傳一個路徑,如果pathType為unKnown,需要傳入完整路徑
*
* return 返回文件中數(shù)據(jù),拿不到數(shù)據(jù)返回nil
*/
class func qs_readFromFile(in pathType: QSPathType = .unKnown, contentType: QSFileContentType = .string, fileName: String) -> AnyObject? {
// 獲取路徑
var path = String.init()
switch pathType {
case .document:
path = self.qs_getDocumentPath()
case .cache:
path = self.qs_getCachePath()
case .unKnown:
path = fileName
}
// 文件路徑
if !fileName.hasPrefix("/") {
path.append("/")
}
let filePath = path + fileName
switch contentType {
case .array:
return NSArray.init(contentsOfFile: filePath)
case .dictionary:
return NSDictionary.init(contentsOfFile: filePath)
case .string:
return try? String.init(contentsOfFile: filePath, encoding: String.Encoding.utf8) as AnyObject
case .data:
return try? NSData.init(contentsOfFile: filePath) as AnyObject
}
}
-
Cell屬性的記錄(MovieRecord)
import UIKit
// 這個枚舉包含所有電影圖片的狀態(tài)
enum MovieRecordState {
case new, downloaded, filtered, failed
}
// 電影條目類
class MovieRecord {
let name:String //電影標(biāo)題
let url:URL //電影海報圖片地址
var image:UIImage? //電影海報圖片
var state = MovieRecordState.new //圖片當(dāng)前狀態(tài)
init(name:String, url:URL) {
self.name = name
self.url = url
}
}
-
ViewController
//創(chuàng)建表格視圖
self.tableView = UITableView(frame: self.view.frame, style:.plain)
//創(chuàng)建一個重用的單元格
self.tableView!.register(MyTableCell.self, forCellReuseIdentifier: "Cell")
//單元格無法選中
self.tableView.allowsSelection = false
//設(shè)置單元格高度
self.tableView!.rowHeight = 100
self.view.addSubview(self.tableView!)
//數(shù)據(jù)源地址
let dataSourcePath = Bundle.main.path(forResource: "movies", ofType: "plist")
let datasourceDictionary = NSDictionary(contentsOfFile: dataSourcePath!)
//初始化數(shù)據(jù)
for(key,value) in datasourceDictionary!{
let name = key as? String
let url = URL(string:value as? String ?? "")
if name != nil && url != nil {
let movieRecord = MovieRecord(name:name!, url:url!)
self.movies.append(movieRecord)
}
}
//設(shè)置單元格數(shù)據(jù)(其實就是對 cellForRowAt 的封裝)
Observable.just(movies)
.bind(to: tableView.rx.items) { (tableView, row, element) in
//初始化cell
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell")
as! MyTableCell
//設(shè)置單元格數(shù)據(jù)
cell.movieRecord = element
//返回單元格
return cell
}
.disposed(by: disposeBag)
參考文章:Swift - RxSwift的使用詳解65(表格圖片加載優(yōu)化)
Swift - 表格圖片加載優(yōu)化(拖動表格時不加載,停止時只加載當(dāng)前頁圖片)