2016-09-19 21 views
6

我開始爲iOS應用程序開發我的第一個RxSwift項目,並學習反應式編程。分頁API調用RxSwift

到目前爲止,這個想法非常簡單,用戶搜索匹配搜索欄文本的電影,這會觸發一個請求,將UITableView填充到結果中。使用網上找到的教程和例子,我設法實現了這一點,沒有太多的麻煩。

當我試圖加載滾動表視圖底部觸發的結果的下一頁時,棘手的部分出現了。

這是迄今爲止使用的代碼:

public final class HomeViewModel: NSObject { 

    // MARK: - Properties 

    var searchText: Variable<String> = Variable("") 
    var loadNextPage: Variable<Void> = Variable() 

    lazy var pages: Observable<PaginatedList<Film>> = self.setupPages() 

    // MARK: - Reactive Setup 

    fileprivate func setupPages() -> Observable<PaginatedList<Film>> { 
     return self.searchText 
      .asObservable() 
      .debounce(0.3, scheduler: MainScheduler.instance) 
      .distinctUntilChanged() 
      .flatMapLatest { (query) -> Observable<PaginatedList<Film>> in 
       return TMDbAPI.Films(withTitle: query, atPage: 0) 
      } 
      .shareReplay(1) 
    } 
} 

這是我到目前爲止有:可觀察pages被綁定到我的表視圖中HomeViewController和搜索欄的文本被綁定到searchText

我正在使用Alamofire在幕後執行API調用,而TMDbAPI.Films(withTitle: query)返回分頁列表的Observable。

這裏是我的模型結構PaginatedList

public struct PaginatedList<T> { 

    // MARK: - Properties 

    let page: Int 
    let totalResults: Int 
    let totalPages: Int 
    let results: [T] 

    // MARK: - Initializer 

    init(page: Int, totalResults: Int, totalPages: Int, results: [T]) { 
     self.page = page 
     self.totalResults = totalResults 
     self.totalPages = totalPages 
     self.results = results 
    } 

    // MARK: - Helper functions/properties 

    var count: Int { return self.results.count } 

    var nextPage: Int? { 
     let nextPage = self.page + 1 
     guard nextPage < self.totalPages else { return nil } 
     return nextPage 
    } 

    static func Empty() -> PaginatedList { return PaginatedList(page: 0, totalResults: 0, totalPages: 0, results: []) } 
} 

extension PaginatedList { 

    // MARK: - Subscript 

    subscript(index: Int) -> T { 
     return self.results[index] 
    } 
} 

現在我正在尋找勾我loadNextPage變量來觀察的分頁列表的一種方式,它會觸發下一個頁面請求的反應方式。 而當搜索欄的文本改變它會分頁重置爲0

我相信將需要使用運營商scanconcat,但我仍然不知道如何...

任何建議關於如何實現這一目標,將不勝感激......

回答

2

基礎上提供了在RxSwift GitHub repo我設法做到這一點的例子。

基本上,我使用遞歸函數返回我的項目的流,它使用loadNextPage觸發器爲下一頁調用自身。 在這裏,我在我的API管理器使用的代碼:

class func films(withTitle title: String, startingAtPage page: Int = 0, loadNextPageTrigger trigger: Observable<Void> = Observable.empty()) -> Observable<[Film]> { 
    let parameters: FilmSearchParameters = FilmSearchParameters(query: title, atPage: page) 
    return TMDbAPI.instance.films(fromList: [], with: parameters, loadNextPageTrigger: trigger) 
} 

fileprivate func films(fromList currentList: [Film], with parameters: FilmSearchParameters, loadNextPageTrigger trigger: Observable<Void>) -> Observable<[Film]> { 

    return self.films(with: parameters).flatMap { (paginatedList) -> Observable<[Film]> in 
     let newList = currentList + paginatedList.results 
     if let _ = paginatedList.nextPage { 
      return [ 
       Observable.just(newList), 
       Observable.never().takeUntil(trigger), 
       self.films(fromList: newList, with: parameters.nextPage, loadNextPageTrigger: trigger) 
      ].concat() 
     } else { return Observable.just(newList) } 
    } 
} 

fileprivate func films(with parameters: FilmSearchParameters) -> Observable<PaginatedList<Film>> { 
    guard !parameters.query.isEmpty else { return Observable.just(PaginatedList.Empty()) } 
    return Observable<PaginatedList<Film>>.create { (observer) -> Disposable in 
     let request = Alamofire 
      .request(Router.searchFilms(parameters: parameters)) 
      .validate() 
      .responsePaginatedFilms(queue: nil, completionHandler: { (response) in 
       switch response.result { 
       case .success(let paginatedList): 
        observer.onNext(paginatedList) 
        observer.onCompleted() 
       case .failure(let error): 
        observer.onError(error) 
       } 
      }) 
     return Disposables.create { request.cancel() } 
    } 
} 

然後在我的視圖模型,這是我必須要做的:

fileprivate func setupFilms() -> Observable<[Film]> { 

    let trigger = self.nextPageTrigger.asObservable().debounce(0.2, scheduler: MainScheduler.instance) 

    return self.textSearchTrigger 
     .asObservable() 
     .debounce(0.3, scheduler: MainScheduler.instance) 
     .distinctUntilChanged() 
     .flatMapLatest { (query) -> Observable<[Film]> in 
      return TMDbAPI.films(withTitle: query, loadNextPageTrigger: trigger) 
     } 
     .shareReplay(1) 
} 
7

這裏是你如何構建它:

// Some kind of page request result. Modify it to be what you're using. 

struct SomePageResult { 
    let content: String 
} 

// Needs modification to return your actual data 

func getPage(query: String, number: UInt) -> SomePageResult { 
    return SomePageResult(content: "some content for search (\(query)) on page \(number)") 
} 

// Actual implementation 

let disposeBag = DisposeBag() 

var loadNextPage = PublishSubject<Void>() 
var searchText = PublishSubject<String>() 
let currentPage = searchText 
    .distinctUntilChanged() 
    .flatMapLatest { searchText in 
     return loadNextPage.asObservable() 
      .startWith(()) 
      .scan(0) { (pageNumber, _) -> UInt in 
       pageNumber + 1 
      } 
      .map { pageNumber in 
       (searchText, pageNumber) 
      } 
    } 
    .map { (searchText, pageNumber) in 
     getPage(searchText, number: pageNumber) 
    } 

currentPage 
    .subscribeNext { print($0) } 
    .addDisposableTo(disposeBag) 

searchText.onNext("zebra") 
searchText.onNext("helicopter") 
loadNextPage.onNext() 
searchText.onNext("unicorn") 
searchText.onNext("unicorn") 
searchText.onNext("ant") 
loadNextPage.onNext() 
loadNextPage.onNext() 
loadNextPage.onNext() 

輸出:

SomePageResult(內容: 「部分內容搜索(斑馬)第1頁」)
SomePageResult(內容: 「部分內容搜索(直升機)第1頁」)
SomePageResult(內容:「部分內容搜索第2" 頁(直升機))
SomePageResult(內容: 「部分內容搜索(麒麟)第1頁」)
SomePageResult(內容: 「部分內容搜索(螞蟻)第1頁」)
SomePageResult (內容:第2頁上的「一些搜索內容(ant)」)
SomePageResult(內容:「第3頁上的一些搜索內容(ant)」)
SomePageResult(內容:「部分內容,第4頁搜索(螞蟻)」)