ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [뉴스어플 정리] 5. 뷰모델을 테이블뷰에 붙이기
    iOS/뉴스어플(RxSwift) 2021. 5. 24. 23:29
    import UIKit
    import RxSwift
    import RxCocoa
    
    class RootViewController: UIViewController {
        
        let tableView: UITableView = {
            let tv = UITableView()
            tv.register(NewsTableViewCell.self, forCellReuseIdentifier: NewsTableViewCell.identifier)
            return tv
        }()
        
        var viewModel = NewsTableViewCellViewModelList()

    테이블뷰를 만들고 커스텀 테이블뷰셀을 register했다.

    그리고 ViewModelList 클래스 인스턴스를 만든다.

     

    이 클래스 인스턴스 viewModel은 초기화되면서 다음 과정을 호로록 진행한다.

    API호출 -> Observable<[Article]> -> Observable<[ViewModel]> -> BehaviorSubject<[ViewModel]>

    이 BehaviorSubject의 onNext로 [ViewModel]이 들어가있다.

     

    func subscribeArticles() {
            viewModel.articleObservable
                .observe(on: MainScheduler.instance)
                .bind(to: tableView.rx.items(cellIdentifier: NewsTableViewCell.identifier, cellType: NewsTableViewCell.self)) { index, item, cell in
                    cell.newsTitleLabel.text = item.newsTitle
                    cell.subTitleLabel.text = item.subTitle
                    cell.urlToNews = item.urlToNews
                    cell.setImage(viewModel: item)
                }.disposed(by: disposeBag)
        }

    viewModel의 articleObservable은 BehaviorSubject<[ViewModel]>로 onNext로 [ViewModel]이 들어가있다.

    이걸 tableView.rx.items에 bind한다. tableView.rx.items(cellIdentifier:, cellType:)을 이용한다.

    그리고 index, item, cell을 사용하는 클로저를 작성한다.

     

    index는 IndexPath, item은 ViewModel, cell은 해당 index의 cell이다.

    item은 [ViewModel]이 아니라 ViewModel이다.

    articleObservable에 있는 [ViewModel]에서 해당 index의 ViewModel 하나가 튀어 나온것이다.

    (주의할점은 dequeueReusableCell이므로 index는 화면에 보이는 cell까지의 index이다.)

     

    이제 드디어 우리가 만든 뷰모델을 뷰에 적용시킨다.

    cell에 item의 뉴스타이틀, 서브타이틀, 터치시 들어갈 뉴스 url을 넣어준다.

     

    뉴스이미지를 표시하는건 cell에 함수를 만들어서 구현했는데

    이 부분은 더 공부가 필요해보이지만 일단 정리를 해놓으려고 한다.

     

    // 커스텀셀에서 구현한 함수.
    
    func setImage(viewModel: NewsTableViewCellViewModel) {
            if let newsImage = viewModel.newsImage {
                newsImageView.image = newsImage
            }
            else {
                guard let url = URL(string: viewModel.urlToImage ?? "") else {
                    newsImageView.image = UIImage(systemName: "xmark.octagon")
                    viewModel.newsImage = UIImage(systemName: "xmark.octagon")
                    return
                }
                URLSession.shared.dataTask(with: url) {[weak self] data, _, error in
                    if let _ = error {
                        DispatchQueue.main.async {
                            self?.newsImageView.image = UIImage(systemName: "xmark.octagon")
                            viewModel.newsImage = UIImage(systemName: "xmark.octagon")
                        }
                    }
                    else if let data = data {
                        DispatchQueue.main.async {
                            viewModel.newsImage = UIImage(data: data)
                            self?.newsImageView.image = UIImage(data: data)
                        }
                    }
                }.resume()
            }
        }

    위 함수는 커스텀셀에서 구현한 함수다.

    뷰모델이 인자로 들어가서 뷰모델의 newsImage가 nil이 아니면 그 이미지를 사용한다.

    뷰모델은 테이블뷰에 bind하는 부분에서 item이다.

     

    nil이라면 URLSession을 이용해 다운받아서 사용하고 뷰모델의 newsImage에 넣는다.

    한번 다운받고 뷰모델의 newsImage에 넣어두기 때문에 셀이 다시 사용될때 저장된 이미지를 사용한다.

     

    이렇게 하는 이유 : 

    이미지를 받아놓지 않으면 셀이 다시 사용될때마다 URLSession을 이용해 다운받아야한다.

    이미지를 매번 다운받으면 로딩시간이 걸리기 때문에 이미지가 느리게 표시된다.

    이미지를 저장해놓고 더 빠르게 이미지를 표시해야한다.

     

    그래서 뷰모델의 newsImage에 다운받은 UIImage를 넣어놨다.

    여기까지해서 받아온 뉴스데이터를 받아서 뷰모델을 만들고 테이블뷰에 적용시켰다.

     


    - 이미지를 표시하는 방법에 대해서

    cell에서 함수를 만들지 말고 뷰모델에서 이 이미지를 다룰 수 있으면 좋을거 같아서

    이 이미지 받는 코드를 여기저기 옮기면서 연구하고있다.

     

    하지만 지금 내 실력으로는 지금 만든 방법이 제일 간단하고 편한 방법인거 같아

    혼자 연구는 그만하고 여러자료좀 찾아봐야겠다.

Designed by Tistory.