[1]에서는 BehaviorRelay로 accept를 했습니다. 이제는 뷰에서 해당 상태이상을 감지하는 Rx를 만들어보겠습니다.
override func viewDidLoad {
Task { try await self.getFish() }
// myData.accept(data)
}
함수를 만들었으니 데이터를 한번 쏴주어야 겠죠. 로드되는 시점에서 데이터를 날려줍니다.
이제 mydata로 날아간 값을 어떻게 요리할것인지 필요합니다.
myData.bind(to: fishView.tableView.rx.items(cellIdentifier: Item.reuseIdentifier, cellType: Item.self)) {row, item, cell in
// [1]과 [2]중 아무거나 사용해도 무관
// url String -> UIImage [1]
let _ = self.loadImage(item.image_url)
.observe(on: MainScheduler.instance)
.bind(to: cell.fishImageView.rx.image)
// url String -> UIImage [2]
cell.fishImageView.setImageUrl(item.image_url)
cell.numberLabel.text = "\(item.number)"
cell.fishLocationLabel.text = item.location
cell.fishNameLabel.text = item.name
cell.fishPriceLabel.text = "\(item.sell_nook)"
}.disposed(by: disposeBag)
myData에 있는 값을 bind시킵니다. bind는 묶는다는 말이니까 어디에 연결할것인지 물어보는 것과 같습니다. 저희는 fishView라는 UIView에 테이블뷰가 아울렛으로 존재하죠. 따라서 이 테이블뷰의 items(cell)에 바인드해야합니다.
items를 직접 살펴봅시다.
cellIdentifier에는 String이 들어가네요. 어떻게 활용하려는 걸까요?
어디서 많이 본 모양새입니다. 바로 Rx를 사용하지 않고 테이블 뷰를 생성하려고 할 때 코드와 동일합니다.
cellIdentifier: Item.reuseIdentifier, cellType: Item.self))
따라서 생김새를 확인했으니, [1]에서 만든 Extension을 통해 만들어둔 값을 넣어주시면 되겠습니다.
다음은 뒷 부분 클로저입니다.
세가지를 escape시키고 있습니다. 생김새를 보아하니 인덱스,요소,셀이 확실해집니다.
cell.numberLabel.text = "\(item.number)"
셀에는 이름을 띄울 수 있는 텍스트를 준비해보았습니다. 따라서 접근할 수 있는 label이 있겠고, label이 있으니 text를 set할 수 있겠습니다. 어떠한 요소를 받아서 넣을 것인지. 요소의 해당되는 값을 넣어주면 될 것 같습니다.
저는 여기에 헤더를 추가했었습니다.
extension FishViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let v = tableView.dequeueReusableHeaderFooterView(withIdentifier: Header.reuseIdentifier) as! Header
return v
}
}
이렇게 코드를 짜고 실행해보면 이상하게도 헤더가 나오지 않습니다. 왜일까요?
fishView.tableView.rx.setDelegate(self).disposed(by: disposeBag)
대리자를 설정해주지 않아서 그렇습니다.
rx의 setDelegate는 일반 Delegate뿐만 아니라 Rx를 사용할 때 필요한 Delegate또한 사용가능하게끔 해줍니다.
final class FishViewHeader: UITableViewHeaderFooterView {
enum Constant {
static let size: CGSize = .init(width: UIView.noIntrinsicMetric, height: 35)
}
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return Header.Constant.size.height
}
헤더도 마찬가지로 수정이 용이하게 구현부에 높이를 따로 만들어 쓰는 것이 좋습니다.
이미지 또한 캐시를 사용해 로딩을 최대한 줄이는 방향으로 만들어줍니다.
class ImageCacheManager {
static let shared = NSCache<NSString, UIImage>()
private init() {}
}
import UIKit
extension UIImageView {
func setImageUrl(_ url: String) {
let cacheKey = NSString(string: url)
if let cachedImage = ImageCacheManager.shared.object(forKey: cacheKey) {
print("yes")
self.image = cachedImage
return
}
DispatchQueue.global(qos: .background).async {
if let imageUrl = URL(string: url) {
URLSession.shared.dataTask(with: imageUrl) { (data, res, err) in
if let _ = err {
DispatchQueue.main.async {
self.image = UIImage()
}
return
}
DispatchQueue.main.async {
if let data = data, let image = UIImage(data: data) {
ImageCacheManager.shared.setObject(image, forKey: cacheKey)
self.image = image
}
}
}.resume()
}
}
}
}
//사용부
cell.citizenImage.setImageUrl(item.image_url)
func loadImage(_ url: String) -> Observable<UIImage?> {
let cache = NSString(string: url)
return Observable.create { emitter in
if let cachedImage = ImageCacheManager.shared.object(forKey: cache) {
print("yes")
emitter.onNext(cachedImage)
emitter.onCompleted()
}
let myUrl = URL(string: url)!
let task = URLSession.shared.dataTask(with: myUrl) { data, response, error in
guard let data else {
emitter.onError(error!)
return
}
let image = UIImage(data: data)
ImageCacheManager.shared.setObject(image!, forKey: cache)
emitter.onNext(image)
emitter.onCompleted()
}
task.resume()
return Disposables.create {
task.cancel()
}
}
}
//사용부
let _ = self.loadImage(item.image_url)
.observe(on: MainScheduler.instance)
.bind(to: cell.citizenImage.rx.image)