Ios 使用ReactiveCocoa在分页UITableView中设置行添加/重新加载/删除动画
我有一个分页的Ios 使用ReactiveCocoa在分页UITableView中设置行添加/重新加载/删除动画,ios,objective-c,uitableview,reactive-cocoa,ios-animations,Ios,Objective C,Uitableview,Reactive Cocoa,Ios Animations,我有一个分页的表格视图,顶部有两个选项卡,分别是最新的和流行的,我无法使用[tableView beginUpdates]和[tableView endUpdates]来正确设置模型更改的动画 表视图有3种加载模式: 加载ModereFresh:当用户使用pull刷新时 LoadingModeNewPage:当用户到达表格视图的末尾时,需要加载新页面 加载模式新选项卡:当用户更改顶部的选项卡时 我从后端每页收到30个项目(用于分页) 另外,我在tableView的末尾有一个加载单元格,这样当
表格视图
,顶部有两个选项卡,分别是最新的和流行的,我无法使用[tableView beginUpdates]
和[tableView endUpdates]
来正确设置模型更改的动画
表视图有3种加载模式
:
:当用户使用pull刷新时加载ModereFresh
:当用户到达LoadingModeNewPage
表格视图的末尾时,需要加载新页面
:当用户更改顶部的选项卡时加载模式新选项卡
tableView
的末尾有一个加载单元格,这样当某人到达表的末尾时,他/她可以看到它正在加载,当新数据到达时,加载单元格被推到tableView
的末尾,看不见
我想要的行为如下:
- 当用户拉取刷新时,将获取30个新项目,并将其设置为
的支持模型,tableView
应将项目从0重新加载到29,并删除其余项目,因为用户可能翻页超过1页,因此刷新前可能有30多个项目,因此,我们需要在刷新后删除额外的项李>tableView
- 当用户到达表的末尾时,将获取30个新项,并将其附加到
的支持模型的末尾,我们应该将这30行插入tableView
tableView
- 当用户切换选项卡时,我首先希望清除
中的所有单元格,以便我提到的加载单元格是唯一的单元格。因此,我将tableView
的备份模型设置为空数组,并从tableView
中删除所有行。取回30个新项目后,我将这30行插入tableView
表视图
ViewModel
上有3个属性,表示对indexPaths的更改:indexPathsToDelete
、indexPathsToInsert
和indexPathsToReload
,这些属性与支持模型中的更改绑定在一起,使用-[RACSignal combinePrevious:reduce:
。我的视图控制器观察这些属性,将它们拉到一起并立即应用更改。然而,不知何故,我的获取代码被调用了两次,这导致indexpath被计算和观察了不止一次,这导致与tableView
不一致,并导致崩溃。既然我似乎不明白发生了什么事,谁能帮我解决问题呢
以下是ViewModel
的代码(很抱歉,代码还没有重构,因为我还没有让它工作):
除此之外,我只在为表视图中的最后一个单元格调用[self.viewModel requestNewPage]
时调用[self.viewModel selectTabWithIndexIfNotSelected:index]
段选择更改时调用[self.viewModel selectTabWithIndexIfNotSelected:index]
<代码>[self.viewModel selectTabWithIndex:0]在viewDidLoad中,在刷新处理程序中[self.viewModel refreshCurrentTab]
我哪里出错了
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
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];
}
}];