Анимация добавления / перезагрузки / удаления строк в разбивке по страницам на UITableView с ReactiveCocoa
У меня есть нумерация страниц tableView
с двумя вкладками вверху, "Последние" и "Популярные", и я не могу правильно их анимировать с помощью [tableView beginUpdates]
а также [tableView endUpdates]
,
Есть 3 режима загрузки для tableView
:
LoadingModeRefresh
: когда пользователь обновляется с помощью pull для обновления.LoadingModeNewPage
: когда пользователь достигает концаtableView
так что новая страница должна быть загруженаLoadingModeNewTab
: когда пользователь меняет вкладку вверху
Я получаю 30 элементов на страницу из бэкэнда (для нумерации страниц)
Также у меня есть загрузочная ячейка в конце tableView
так что когда кто-то достигает конца таблицы, он / она может видеть, что она загружается, а когда поступают новые данные, эта ячейка загрузки перемещается в конец tableView
, вне поля зрения.
Мне нужно следующее поведение:
- Когда пользователь тянет для обновления, 30 новых элементов извлекаются и устанавливаются в качестве вспомогательной модели для
tableView
иtableView
следует перезагрузить элементы от 0 до 29 и удалить остальные, поскольку пользователь мог разместить на странице более 1 страницы, поэтому до обновления может быть более 30 элементов, поэтому нам необходимо удалить дополнительные элементы после обновления. - Когда пользователь достигает конца таблицы, 30 новых элементов извлекаются и добавляются в конец вспомогательной модели для
tableView
и мы должны вставить эти 30 строк вtableView
, - Когда пользователь переключает вкладки, я сначала хочу
tableView
быть очищенным от всех ячеек так, чтобы ячейка загрузки, о которой я упоминал, была единственной ячейкой. поэтому я установил модель поддержкиtableView
в пустой массив, и удалите все строки изtableView
, после получения 30 новых элементов я вставляю эти 30 строк вtableView
,
У меня 3 свойства на моем ViewModel
представление изменений в indexPaths: indexPathsToDelete
, indexPathsToInsert
а также indexPathsToReload
, которые связаны с изменениями в модели поддержки с -[RACSignal combinePrevious:reduce:]
, Мой контроллер представления наблюдает за этими свойствами, объединяет их в архив и применяет изменения сразу. однако каким-то образом мой код выборки вызывается дважды, что приводит к тому, что indexPaths вычисляется и наблюдается более одного раза, что вызывает несоответствия с tableView
и вызывает сбои. Может кто-нибудь помочь с тем, где проблема, так как я не могу понять, что происходит?
вот код для ViewModel
(извините за код, пока не реорганизовал его, так как у меня его пока нет):
const NSInteger kArticlePerPage = 30;
@interface FeedViewModel ()
@property (nonatomic, readwrite) Source *model;
@property (nonatomic) LoadingMode loadingMode;
@property (nonatomic) NSInteger selectedTabIndex;
@property (nonatomic, readwrite) NSInteger pagesRequested;
@property (nonatomic, readwrite) NSInteger pagesLoaded;
@property (nonatomic, readwrite) NSArray *indexPathsToDelete;
@property (nonatomic, readwrite) NSArray *indexPathsToInsert;
@property (nonatomic, readwrite) NSArray *indexPathsToReload;
- (NSArray *)indexPathsForRange:(NSRange)range inSection:(NSInteger)section;
@end
@implementation FeedViewModel
- (instancetype)initWithModel:(Source *)source
{
self = [super init];
if (!self) {
return nil;
}
_model = source;
_pagesLoaded = 0;
RACSignal *loadingModeSignal = RACObserve(self, loadingMode);
RACSignal *newTabSignal = [loadingModeSignal //
filter:^BOOL(NSNumber *loadingMode) {
return loadingMode.integerValue == LoadingModeNewTab;
}];
RACSignal *newPageSignal = [loadingModeSignal //
filter:^BOOL(NSNumber *loadingMode) {
return loadingMode.integerValue == LoadingModeNewPage;
}];
RACSignal *refreshSignal = [loadingModeSignal //
filter:^BOOL(NSNumber *loadingMode) {
return loadingMode.integerValue == LoadingModeRefresh;
}];
RAC(self, loading) = [loadingModeSignal //
map:^id(NSNumber *loadingMode) {
switch (loadingMode.integerValue) {
case LoadingModeFinished:
return @(NO);
default:
return @(YES);
}
}];
@weakify(self);
RACSignal *newArticlesSignal = [[[[RACSignal
combineLatest:@[ RACObserve(self, pagesRequested), RACObserve(self, selectedTabIndex) ]]
sample:RACObserve(self, loadingMode)] //
map:^id(RACTuple *tuple) {
@strongify(self);
return [self signalForNewArticlesForPage:tuple.first order:[tuple.second integerValue]];
}] //
switchToLatest];
RACSignal *articlesForNewTabSignal = [[newTabSignal //
flattenMap:^RACStream * (id value) { //
return [newArticlesSignal startWith:@[]];
}] //
skip:1];
RACSignal *articlesForNewPageSignal = [newPageSignal //
flattenMap:^RACStream * (id value) {
return [newArticlesSignal //
map:^id(NSArray *newArticles) {
@strongify(self);
Article *article = self.articles[0];
NSLog(@"article name: %@", article.title);
return [self.articles arrayByAddingObjectsFromArray:newArticles];
}];
}];
RACSignal *articlesForRefreshSignal = [refreshSignal //
flattenMap:^RACStream * (id value) { //
return newArticlesSignal;
}];
RAC(self, articles) = [RACSignal merge:@[
articlesForNewTabSignal, //
articlesForNewPageSignal, //
articlesForRefreshSignal
]];
RACSignal *articlesSignal = RACObserve(self, articles);
RAC(self, indexPathsToDelete) = [articlesSignal //
combinePreviousWithStart:@[] //
reduce:^id(NSArray *previous, NSArray *current) {
@strongify(self);
if (previous.count > current.count) {
return [self
indexPathsForRange:NSMakeRange(current.count,
previous.count - current.count)
inSection:0];
}
else {
return @[];
}
}];
RAC(self, indexPathsToInsert) = [articlesSignal //
combinePreviousWithStart:@[] //
reduce:^id(NSArray *previous, NSArray *current) { //
@strongify(self);
if (previous.count < current.count) {
return [self
indexPathsForRange:NSMakeRange(previous.count,
current.count - previous.count)
inSection:0];
}
else {
return @[];
}
}];
RAC(self, indexPathsToReload) = [articlesSignal //
combinePreviousWithStart:@[] //
reduce:^id(NSArray *previous, NSArray *current) {
if (previous.count >= current.count) {
return [self indexPathsForRange:NSMakeRange(0, current.count)
inSection:0];
}
else {
return @[];
}
}];
RAC(self, pagesLoaded) = [[RACObserve(self, articles) //
skip:1] //
map:^id(NSArray *array) { //
NSInteger pages = array.count / kArticlePerPage;
if (array.count % kArticlePerPage != 0) {
pages++;
}
return @(pages);
}];
RAC(self, separatorColorHexString) = [RACObserve(self, model.type) map:^id(NSNumber *type) {
if (type.integerValue == SourceTypeInspiration) {
return @"ffffff";
}
else {
return @"E5E5E5";
}
}];
RAC(self, segmentTitles) = [RACObserve(self, model) //
map:^id(Source *source) {
NSMutableArray *titles = [NSMutableArray array];
if (source.isPopularAvailable) {
[titles addObject:@"Popular"];
}
if (source.isLatestAvailable) {
[titles addObject:@"Latest"];
}
return titles;
}];
return self;
}
- (void)setCurrentSource:(Source *)source
{
self.model = source;
}
- (void)refreshCurrentTab
{
self.pagesRequested = 1;
self.loadingMode = LoadingModeRefresh;
}
- (void)requestNewPage
{
if (self.pagesRequested == self.pagesLoaded + 1) {
return;
}
self.pagesRequested = self.pagesLoaded + 1;
self.loadingMode = LoadingModeNewPage;
}
- (void)selectTabWithIndex:(NSInteger)index
{
self.selectedTabIndex = index;
self.pagesRequested = 1;
self.loadingMode = LoadingModeNewTab;
}
- (void)selectTabWithIndexIfNotSelected:(NSInteger)index
{
if (self.selectedTabIndex == index) {
return;
}
[self selectTabWithIndex:index];
}
- (NSArray *)indexPathsForRange:(NSRange)range inSection:(NSInteger)section
{
NSMutableArray *indexes = [NSMutableArray array];
for (NSUInteger i = range.location; i < range.location + range.length; i++) {
[indexes addObject:[NSIndexPath indexPathForRow:i inSection:section]];
}
return [indexes copy];
}
- (RACSignal *)signalForNewArticlesForPage:(NSNumber *)pageNumber order:(ArticleOrder)order
{
return [[SourceManager sharedManager] articlesForSourceKey:self.model.key
articleOrder:order
pageNumber:pageNumber];
}
- (void)setLoadingMode:(LoadingMode)loadingMode
{
_loadingMode = loadingMode;
}
@end
и код для наблюдения массивов indexPath в ViewController.m
:
RACSignal *toDeleteSignal = [RACObserve(self, viewModel.indexPathsToDelete) //
deliverOn:[RACScheduler mainThreadScheduler]]; //
RACSignal *toInsertSignal = [RACObserve(self, viewModel.indexPathsToInsert) //
deliverOn:[RACScheduler mainThreadScheduler]]; //
RACSignal *toReloadSignal = [RACObserve(self, viewModel.indexPathsToReload) //
deliverOn:[RACScheduler mainThreadScheduler]];
[[RACSignal zip:@[ toDeleteSignal, toInsertSignal, toReloadSignal ]]
subscribeNext:^(RACTuple *tuple) {
@strongify(self);
[self.tableView beginUpdates];
[self.tableView deleteRowsAtIndexPaths:tuple.first
withRowAnimation:UITableViewRowAnimationAutomatic];
[self.tableView insertRowsAtIndexPaths:tuple.second
withRowAnimation:UITableViewRowAnimationTop];
[self.tableView reloadRowsAtIndexPaths:tuple.third
withRowAnimation:UITableViewRowAnimationAutomatic];
[self.tableView endUpdates];
if (self.tableView.pullToRefresh.state == BPRPullToRefreshStateLoading) {
[self.tableView.pullToRefresh dismiss];
}
}];
Кроме этого я звоню только [self.viewModel requestNewPage]
когда tableView:willDisplayCell:
вызывается для последней ячейки в tableView, [self.viewModel selectTabWithIndexIfNotSelected:index]
когда выбор сегмента меняется. [self.viewModel selectTabWithIndex:0]
в viewDidLoad и [self.viewModel refreshCurrentTab]
в обработчике обновления.
Где я делаю ошибку?