【问题标题】:Async image loading with ReactiveCocoa (4.2.1) and Swift使用 ReactiveCocoa (4.2.1) 和 Swift 进行异步图像加载
【发布时间】:2016-06-30 02:22:12
【问题描述】:

我是第一次在 Swift 中使用 ReactiveCocoa 的初学者。我正在构建一个显示电影列表的应用程序,并且我正在使用 MVVM 模式。我的 ViewModel 如下所示:

class HomeViewModel {

    let title:MutableProperty<String> = MutableProperty("")
    let description:MutableProperty<String> = MutableProperty("")
    var image:MutableProperty<UIImage?> = MutableProperty(nil)

    private var movie:Movie

    init (withMovie movie:Movie) {

        self.movie = movie

        title.value = movie.headline
        description.value = movie.description

        Alamofire.request(.GET, movie.pictureURL)
            .responseImage { response in

                if let image = response.result.value {
                    print("image downloaded: \(image)")
                    self.image.value = image
                }
        }

    }
}

我想像这样在 UITableView 中配置我的单元格:

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

    let cell = tableView.dequeueReusableCellWithIdentifier("MovieCell", forIndexPath: indexPath) as! MovieCell
    let movie:Movie = movieList[indexPath.row]
    let vm = HomeViewModel(withMovie: movie)

    // fill cell with data
    vm.title.producer.startWithNext { (newValue) in
        cell.titleLabel.text = newValue
    }

    vm.description.producer.startWithNext { (newValue) in
        cell.descriptioLabel.text = newValue
    }

    vm.image.producer.startWithNext { (newValue) in
        if let newValue = newValue {
            cell.imageView?.image = newValue as UIImage
        }
    }

    return cell
}

这是 Reactive Cocoa 的正确方法吗?我是否需要将 Title 和 description 声明为 Mutable 或只是 image (唯一一个改变)。我想我可以使用绑定,但我不确定如何继续。

【问题讨论】:

    标签: ios swift mvvm reactive-cocoa reactive-cocoa-4


    【解决方案1】:

    要使用 Reactive Cocoa + MVVM 模式来做到这一点,我首先将所有逻辑从其视图模型中配置单元格转移到单元格类本身中。然后从 viewModel 中删除 MutableProperties(它们实际上不是可变的,我们不需要这些信号)。并且对于图像公开一个信号生成器,它将在调用 start() 时执行网络请求以获取图像,而不是在 ViewModel 上调用 init 时隐式获取它,给我们类似

    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
      let cell = tableView.dequeueReusableCellWithIdentifier("MovieCell", forIndexPath: indexPath) as! MovieCell
      cell.viewModel = self.viewModelForIndexPath(indexPath)
      return cell
    }
    
    private func viewModelForIndexPath(indexPath: NSIndexPath) -> MovieCellViewModel {
      let movie: Movie = movieList[indexPath.row]
      return HomeViewModel(movie: movie)
    }
    

    然后

    class MovieCell: UITableViewCell
      @IBOutlet weak var titleLabel: UILabel
      @IBOutlet weak var descriptionLabel: UILabel
      @IBOutlet weak var imageView: UIImageView
    
      var viewModel: MovieCellViewModel {
        didSet {
          self.configureFromViewModel()
        }
      }
    
      private func configureFromViewModel() {
        self.titleLabel.text = viewModel.title
        self.descriptionLabel.text = viewModel.description
        viewModel.fetchImageSignal()
          .takeUntil(self.prepareForReuseSignal()) //stop fetching if cell gets reused
          .startWithNext { [weak self] image in
            self?.imageView.image = image
          }
      }
    
      //this could also go in a UITableViewCell extension if you want to use it other places
      private func prepareForReuseSignal() -> Signal<(), NoError> {
        return Signal { observer in
          self.rac_prepareForReuseSignal // reactivecocoa builtin function
            .toSignalProducer() // obj-c RACSignal -> swift SignalProducer
            .map { _ in () } // AnyObject? -> Void
            .flatMapError { _ in .empty } // NSError -> NoError
            .start(observer)
        }
      }
    }
    

    在 ViewModel 中

    struct HomeViewModel {
      private var movie: Movie
    
      var title: String {
        return movie.headline
      }
    
      var description: String {
        return movie.description
      }
    
      func fetchImageSignal() -> SignalProducer<UIImage, NSError> {
        return SignalProducer { observer, disposable in
          Alamofire.request(.GET, movie.pictureURL)
            .responseImage { response in
              if let image = response.result.value {
                print("image downloaded: \(image)")
                observer.sendNext(image) //send the fetched image on the signal
                observer.sendCompleted()
              } else {
                observer.sendFailed( NSError(domain: "", code: 0, userInfo: .None)) //send your error
              }
            }
      }
    }
    

    【讨论】:

      猜你喜欢
      • 2017-03-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-05-16
      • 2013-04-08
      • 2018-03-23
      • 1970-01-01
      相关资源
      最近更新 更多