2016-06-30 6 views
1

Ich bin ein Anfänger mit ReactiveCocoa mit Swift zum ersten Mal. Ich erstelle eine App mit einer Liste von Filmen und verwende das MVVM-Muster. Mein Ansichtsmodell sieht wie folgt aus:Async Bild laden mit ReactiveCocoa (4.2.1) und Swift

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 
       } 
     } 

    } 
} 

und ich möchte meine Zellen im UITableView so konfigurieren:

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 
} 

Ist das der richtige Ansatz für Reactive Cocoa? Muss ich den Titel und die Beschreibung als Veränderbar oder nur als Bild deklarieren (als einziger veränderlich). Ich denke, ich könnte binden, aber ich bin mir nicht sicher, wie es weitergehen soll.

Antwort

1

Um dies unter Verwendung von Reactive Cocoa + MVVM-Mustern zu tun, würde ich zuerst die gesamte Logik verschieben, um die Zelle von ihrem Ansichtsmodell in die Zellklasse selbst zu konfigurieren. und entfernen Sie dann die MutableProperties aus dem viewModel (sie sind nicht wirklich änderbar und wir brauchen diese Signale nicht). und für das Bild eines Signalerzeuger, dass er das Netzwerkanforderung wird durchführen, um das Bild zu holen, wenn start() genannt wird, anstatt implizit holt es, wenn init auf dem Ansichtsmodell genannt wird, gibt uns so etwas wie

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) 
} 

und dann

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) 
    } 
    } 
} 

und in der Ansichtsmodell

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 
      } 
     } 
    } 
}