ISListViewAdapter 1.0.0

ISListViewAdapter 1.0.0

测试已测试
语言语言 Objective-CObjective C
许可证 MIT
发布上次发布2014年12月

Jason Barrie Morley 维护。



  • Jason Barrie Morley

确定 UITableViewUICollectionView 正确的更新集是困难的。 ISListViewAdapter 会为您完成所有这些工作,将标识符数组映射到列表视图所需的内部结构。当标识符数组发生变化时,它可以自动确定增加、删除、更新和移动操作,并且可以使用提供的各种便利方法将这些操作应用到 UITableViewUICollectionView 上。

安装

入门

ISListViewAdapter 需要客户端实现数据源和绑定到 UITableViewUICollectionView 实例的粘合剂。

数据源

客户端必须提供 ISListViewAdapterDataSource 协议的自定义实现,该协议作为 ISListViewAdapter 的数据模型。 ISListViewAdapterDataSource 通过不透明的标识符索引条目,而 ISListViewAdapter 保持在 UITableViewUICollectionView 使用和这些标识符之间的映射。

这个协议的简单实现示例可能会如下所示。这仅仅是将内容暴露在 NSDictionary 上。

#import <ISListViewAdapter/ISListViewAdapter.h>
#import "CustomDataSource.h"

@interface CustomDataSource ()
@property (nonatomic, strong) NSDictionary *items;
@property (nonatomic, strong) ISListViewAdapterInvalidator *invalidator;
@end

@implementation CustomDataSource

- (id)init
{
  self = [super init];
  if (self) {
    self.items =
    @{@"item_a": @{@"title": @"Title For Item A",
                   @"section": @"Section One"},
      @"item_b": @{@"title": @"Title For Item B",
                   @"section": @"Section Two"},
      @"item_c": @{@"title": @"Title For Item C",
                   @"section": @"Section One"}};
  }
  return self;
}

// Required

- (void)identifiersForAdapter:(ISListViewAdapter *)adapter completionBlock:(ISListViewAdapterBlock)completionBlock
{
  completionBlock([self.items allKeys]);
}

- (void)adapter:(ISListViewAdapter *)adapter itemForIdentifier:(id)identifier completionBlock:(ISListViewAdapterBlock)completionBlock
{
  NSDictionary *item = self.items[identifier];
  completionBlock(item);
}

// Optional

- (id)adapter:(ISListViewAdapter *)adapter summaryForIdentifier:(id)identifier
{
  NSDictionary *item = self.items[identifier];
  return [NSString stringWithFormat:
          @"%@, %@",
          item[@"title"],
          item[@"section"]];
}

- (NSString *)adapter:(ISListViewAdapter *)adapter sectionForIdentifier:(id)identifier
{
  NSDictionary *item = self.items[identifier];
  reeturn item[@"section"];
}

// Called when the data source is added to the adapter.
// ISListViewAdapterInvalidator should be retained if it is ever necessary for
// the data source to invalidate the ISListViewAdapter.
- (void)adapter:(ISListViewAdapter *)adapter initialize:(ISListViewAdapterInvalidator *)invalidator
{
  self.invalidator = invalidator;
}

@end

所有数据源回调都在主运行循环上执行。可以使用完成块异步提供长运行操作的结果。请注意,由于所有回调都在主运行循环上执行,您应将任何长时间运行的操作跨帖子以避免阻塞 UI。

摘要和章节回调是可选的

  • adapter:summaryForIdentifier: 返回一个符合 NSObjectid(用于通过 isEqual: 比较),它描述了给定标识符的项目当前状态。它被 ISListViewAdapter 用于识别项目的更新。如果没有提供摘要,则假定对象不可变,项目将不会被更新或重新加载。
  • adapter:sectionForIdentifier: 返回要显示给定标识符的项目所属章节的标题(假定是唯一的)。章节内的项目排序对应于通过 identifiersForAdapter:completionBlock: 返回的排序。章节排序对应于通过 identifiersForAdapter:completionBlock: 返回的项目的查看顺序。

连接器和观察者

一旦有了数据源,就必须创建一个 ISListViewAdapter 实例,并将其绑定到您的列表视图实例。 ISListViewAdapterConnectorISListViewAdapter 实例与表格视图和收集视图提供了一种现成的连接器。

#import <ISListViewAdapter/ISListViewAdapter.h>
#import "CustomTableViewController.h"
#import "CustomDataSource.h"

@interface CustomTableViewController ()
@property (nonatomic, strong) id<ISListViewAdapterDataSource> dataSource;
@property (nonatomic, strong) ISListViewAdapter *adapter;
@property (nonatomic, strong) ISListViewAdapterConnector *connector;
@end

@implementation CustomTableViewController

- (void)viewDidLoad
{
  self.dataSource = [CustomDataSource new]
  self.adapter = [ISListViewAdapter adapterWithDataSource:dataSource];
  self.connector = [ISListViewAdapterConnector connectorWithAdapter:adapter
                                                          tableView:self.tableView];
}

- (void)viewDidAppear:(BOOL)animated
{
  [super viewDidAppear:animated];

  // Inform the connector that our view has been fully constructed and it is safe to
  // apply incremental updates to our UITableView (or UICollectionView).
  [self.connector ready];
}

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
  return [self.connector numberOfSections];
}

- (NSInteger)tableView:(UITableView *)tableView
 numberOfRowsInSection:(NSInteger)section
{
  return [self.connector numberOfItemsInSection:section];
}

- (NSString *)tableView:(UITableView *)tableView
titleForHeaderInSection:(NSInteger)section
{
  return [self.adapter titleForSection:section];
}

- (UITableViewCell *)tableView:(UITableView *)tableView
         cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
  UITableViewCell *cell =
  [self.tableView dequeueReusableCellWithIdentifier:kCellIdentifier
                                       forIndexPath:indexPath];
  NSDictionary *item = (NSDictionary *)[[self.adapter itemForIndexPath:indexPath] fetchBlocking];

  // Configure the cell using the details of the fetched item. e.g.
  cell.textLabel.text = item[@"title"];

  return cell;
}

@end

如果您希望使用自己实现的列表视图与 ISListViewAdapter 结合,或者以自定义的方式处理更改(例如,将更改的项目滚动到视图),则可以实现 ISListViewAdapterObserver 协议,并使用 addAdapterObserver:removeAdapterObserver: 来观察 ISListViewAdapter

- (void)adapter:(ISListViewAdapter *)adapter performBatchUpdates:(ISListViewAdapterChanges *)changes
{
  for (ISListViewAdapterOperation *operation in changes.operations) {
    // Check the type of the operation and determine the correct change...
  }
}

获取项目

ISListViewAdapterItem 为根据 NSIndexPath 获取项目提供了一个机制。项目自身为 id 类型,允许您在内部使用任何对象:上面的例子使用了 NSDictionary 实例作为项目,但这也可以是您自己的自定义对象(如 NSManagedObjectFCModel 等)。

项目可以同步和异步获取。通常,同步获取项目是安全的(如果您使用的是快速机制,如 NSDictionary 进行项目查找),但如果您正在从数据库或其他较慢的数据源中进行获取,则可能希望使用异步获取。在极端情况下,异步获取可能用于直接从网络获取项目。

同步获取

- (UITableViewCell *)tableView:(UITableView *)tableView
         cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
  UITableViewCell *cell = /* ... */

  ISListViewAdapterItem *item = [self.adapter itemForIndexPath:indexPath];
  id myItem = [item fetchBlocking];

  // Configure the cell...

  return cell;
}

异步获取

ISListViewAdapterItem fetch: 保证无论支持数据源的行为如何,回调都会在主运行循环中发生。

- (UITableViewCell *)tableView:(UITableView *)tableView
         cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
  UITableViewCell *cell = // ...

  __weak UITableViewCell *weakCell = cell;
  ISListViewAdapterItem *item = [self.adapter itemForIndexPath:indexPath];
  [item fetch:^(id myItem) {
    UITableViewCell *strongCell = weakCell;
    if (cell) {

      // Configure the cell...

      // Ensure the cell is redrawn.
      [cell setNeedsLayout];
    }
  }];

  return cell;
}

转换数据源

有时,在数据源之间进行转换很有用。

CustomDataSource *secondDataSource = [CustomDataSource new];
[self.adapter transitionToDataSource:secondDataSource];

性能

ISListViewAdapter 为非常大的数据集而设计。

  • 只有需要确定列表中项目位置和计算内容更改时的更改的数据才会被获取并保持在内存中:项目本身是异步获取的,并且仅在需要绘制 UI 中的项目时才获取。
  • 更改计算是异步执行的,每个新的 ISListViewAdapter 都会创建自己的 dispatch queue:在大数据集中,更新可能需要更长时间计算,但这样做不应该阻止主运行循环。

测试

ISListViewAdapter 包含一些相当全面的浸泡测试,旨在用尽可能多的输入来驱动 UITableView 和 UICollectionView。

cd Sample
pod install
xcodebuild build -workspace ISListViewAdapterSample.xcworkspace -scheme ISListViewAdapterSample

限制

ISListViewAdapter 通过首先识别和应应用到部分插入、删除和移动,然后在应用部分更改之后,仅确定项目更改来识别更改。这意味着,在当前实现中,项目永远不会在部分之间进行动画过渡。

变更日志

1.0.0

  • 初始发布。

许可证

ISListViewAdapter 在 MIT 许可证下可用。有关更多信息,请参阅 LICENSE 文件。