DataSource: Data Shaping Operations

TKDataSource supports a full range of data shaping operations with APIs specific for your scenario.

The simplest way to shape your data is to use the block API. Just specify the block function responsible for the data operation:

// filter all values less or equal to 5
[self.dataSource filter:^BOOL(id item) { return [item integerValue] > 5; }];

// sort ascending
[self.dataSource sort:^NSComparisonResult(id obj1, id obj2) {
    NSInteger a = [obj1 integerValue];
    NSInteger b = [obj2 integerValue];
    if (a<b) { return NSOrderedDescending; }
    else if (a>b) { return NSOrderedAscending; }
    return NSOrderedSame;
}];

// group odd/even values
[self.dataSource group:^id(id item) { return @([item integerValue] % 2 == 0); }];

// multiply every value * 10
[self.dataSource map:^id(id item) { return @([item integerValue] * 10); }];

// find the max value
NSNumber *maxValue = [self.dataSource reduce:@(0) with:^id(id item, id value) {
    if ([item integerValue] > [value integerValue]) {
        return item;
    }
    return value;
}];
// filter all values less or equal to 5

dataSource!.filter { (i:Any) -> Bool in
    return (i as! NSNumber).int32Value > 5
} 

// sort ascending
dataSource!.sort {
    let a = $0 as! Int
    let b = $1 as! Int
    if a<b { return ComparisonResult.orderedDescending }
    else if a>b { return ComparisonResult.orderedAscending }
    return ComparisonResult.orderedSame
}

// group odd/even values
dataSource!.group { ($0 as! Int) % 2 }

// multiply every value * 10
dataSource!.map { ($0 as! Int) * 10 }

// find the max value
let maxValue = dataSource!.reduce(0) {
    if $0 as! Int > $1 as! Int {
        return $0
    }
    return $1
} as! Int
// filter all values less or equal to 5
dataSource.Filter ((NSObject item) => {
    return ((NSNumber)item).NIntValue > 5;
});

// sort ascending
dataSource.Sort ((NSObject obj1, NSObject obj2) => {
    nint a = ((NSNumber)obj1).NIntValue;
    nint b = ((NSNumber)obj2).NIntValue;
    if (a<b) return NSComparisonResult.Descending; 
    else if (a>b) return NSComparisonResult.Ascending;
    return NSComparisonResult.Same;
});

// group odd/even values
dataSource.Group ((NSObject item) => {
    return NSObject.FromObject(((NSNumber)item).NIntValue % 2 == 0);
});

// multiply every value * 10
dataSource.Map ((NSObject item) => {
    return NSObject.FromObject(((NSNumber)item).NIntValue * 10);
});

// find the max value
NSObject maxValue = dataSource.Reduce (NSObject.FromObject(0), (NSObject item, NSObject value) => {
    if (((NSNumber)item).NIntValue > ((NSNumber)value).NIntValue) 
        return item;
    return value;
});

TKDataSource supports also NSPredicate style queries. The following methods use queries and property names instead of block methods:

@implementation DataSourceDocsQueries
{
    TKDataSource *dataSource;
}

-(void)viewDidLoad
{
    [super viewDidLoad];
    NSMutableArray *items = [NSMutableArray new];
    [items addObject:[[DSItem alloc] initWithName:@"John" value:50 group:@"A"]];
    [items addObject:[[DSItem alloc] initWithName:@"Abby" value:33 group:@"A"]];
    [items addObject:[[DSItem alloc] initWithName:@"Smith" value:42 group:@"B"]];
    [items addObject:[[DSItem alloc] initWithName:@"Peter" value:28 group:@"B"]];
    [items addObject:[[DSItem alloc] initWithName:@"Paula" value:25 group:@"B"]];

    dataSource = [TKDataSource new];
    dataSource.itemSource = items;
    dataSource.displayKey = @"name";
    [dataSource filterWithQuery:@"value > 30"];
    [dataSource sortWithKey:@"value" ascending:true];
    [dataSource groupWithKey:@"group"];


    UITableView *tableView = [[UITableView alloc] initWithFrame:self.view.bounds];
    tableView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
    tableView.dataSource = dataSource;
    [self.view addSubview:tableView];
}

@end
class DataSourceDocsQueries: UIViewController {

    let dataSource = TKDataSource()

    override func viewDidLoad() {
        super.viewDidLoad()

        var items = [DataSourceItem]()
        items.append(DataSourceItem(name: "John", value: 50, group: "A"))
        items.append(DataSourceItem(name: "Abby", value: 33, group: "A"))
        items.append(DataSourceItem(name: "Smith", value: 42, group: "B"))
        items.append(DataSourceItem(name: "Peter", value: 28, group: "B"))
        items.append(DataSourceItem(name: "Paula", value: 25, group: "B"))

        dataSource.itemSource = items
        dataSource.displayKey = "name"

        dataSource.filter(withQuery: "value > 30")
        dataSource.sort(withKey: "value", ascending: true)
        dataSource.group(withKey: "group")


        let tableView = UITableView(frame: self.view.bounds)
        tableView.dataSource = dataSource
        tableView.autoresizingMask = UIViewAutoresizing(rawValue:~UIViewAutoresizing().rawValue)
        self.view.addSubview(tableView)
    }
}
public class DataSourceDocsQueries : XamarinExampleViewController
{
    TKDataSource dataSource;

    public DataSourceDocsQueries ()
    {
    }

    public override void ViewDidLoad ()
    {
        base.ViewDidLoad ();

        NSMutableArray items = new NSMutableArray();
        items.Add (new DSItem () { Name = "John", Value = 50, Group = "A" });
        items.Add (new DSItem () { Name = "Abby", Value = 33, Group = "A" });
        items.Add (new DSItem () { Name = "Smith", Value = 42, Group = "B" });
        items.Add (new DSItem () { Name = "Peter", Value = 28, Group = "B" });
        items.Add (new DSItem () { Name = "Paula", Value = 25, Group = "B" });

        this.dataSource = new TKDataSource ();
        this.dataSource.ItemSource = items;
        this.dataSource.DisplayKey = "Name";

        this.dataSource.FilterWithQuery ("Value > 30");
        this.dataSource.SortWithKey ("Value", true);
        this.dataSource.GroupWithKey ("Group");


        UITableView tableView = new UITableView (this.View.Bounds);
        tableView.DataSource = this.dataSource;
        this.View.AddSubview (tableView);
    }
}

All the methods mentioned above execute immediately when called. They operate directly on the current items view in TKDataSource. TKDataSource extends its API by supporting three collections with sorting, filtering and group descriptors.

The descriptors API supports both code blocks and queries with property names. This allows for specifying the data shaping operations before loading the data. In this scenario all descriptors are applied after the data is actually loaded in TKDataSource. This data-loading operation can happen on setting the itemSource property. The following code demonstrates this API:

@interface DataSourceDescriptorsAPI ()

@property (nonatomic, strong) TKDataSource *dataSource;

@end

@implementation DataSourceDescriptorsAPI

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.

    self.title = @"Descriptors API";

    self.dataSource = [TKDataSource new];

    [self.dataSource addFilterDescriptor:[[TKDataSourceFilterDescriptor alloc] initWithQuery:@"NOT (name like 'John')"]];
    [self.dataSource addSortDescriptor:[[TKDataSourceSortDescriptor alloc] initWithProperty:@"name" ascending:YES]];
    [self.dataSource addGroupDescriptor:[[TKDataSourceGroupDescriptor alloc] initWithProperty:@"group"]];

    NSMutableArray *items = [NSMutableArray new];
    [items addObject:[[DSItem alloc] initWithName:@"John" value:22.0 group:@"one"]];
    [items addObject:[[DSItem alloc] initWithName:@"Peter" value:15.0 group:@"one"]];
    [items addObject:[[DSItem alloc] initWithName:@"Abby" value:47.0 group:@"one"]];
    [items addObject:[[DSItem alloc] initWithName:@"Robert" value:45.0 group:@"two"]];
    [items addObject:[[DSItem alloc] initWithName:@"Alan" value:17.0 group:@"two"]];
    [items addObject:[[DSItem alloc] initWithName:@"Saly" value:33.0 group:@"two"]];

    self.dataSource.displayKey = @"name";
    self.dataSource.valueKey = @"value";
    self.dataSource.itemSource = items;

    [self.dataSource formatText:^NSString *(id item, TKDataSourceGroup *group) {
        DSItem *dsItem = (DSItem*)item;
        return [NSString stringWithFormat:@"%@ has %f points", dsItem.name, dsItem.value];
    }];

    UITableView *tableView = [[UITableView alloc] initWithFrame:self.view.bounds];
    tableView.dataSource = self.dataSource;
    tableView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
    [self.view addSubview:tableView];

}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}
class DataSourceDescriptorsAPI: TKExamplesExampleViewController {

    let dataSource = TKDataSource()

    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
        self.title = "Descriptors API"

        self.dataSource.addFilterDescriptor(TKDataSourceFilterDescriptor(query:"NOT (name like 'John')"))
        self.dataSource.addSortDescriptor(TKDataSourceSortDescriptor(property: "name", ascending: true))
        self.dataSource.addGroupDescriptor(TKDataSourceGroupDescriptor(property: "group"))

        var items = [DSItem]()

        items.append(DSItem(name: "John", value: 22, group: "one"))
        items.append(DSItem(name: "Peter", value: 15, group: "one"))
        items.append(DSItem(name: "Abby", value: 47, group: "one"))
        items.append(DSItem(name: "Robert", value: 45, group: "two"))
        items.append(DSItem(name: "Alan", value: 17, group: "two"))
        items.append(DSItem(name: "Saly", value: 33, group: "two"))

        self.dataSource.displayKey = "name"
        self.dataSource.valueKey = "value"
        self.dataSource.itemSource = items

        dataSource.formatText { (item, group) -> String! in
            let dsItem = item as! DSItem
            return "\(dsItem.name) has \(dsItem.value) points"
        }

        let tableView = UITableView(frame: self.view.bounds)
        tableView.dataSource = self.dataSource
        tableView.autoresizingMask = UIViewAutoresizing(rawValue: ~UIViewAutoresizing().rawValue)
        self.view.addSubview(tableView)
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}
public class DataSourceDescriptorsAPI: XamarinExampleViewController
{
    TKDataSource dataSource;

    public override void ViewDidLoad ()
    {
        base.ViewDidLoad ();

        dataSource = new TKDataSource ();

        dataSource.AddFilterDescriptor (new TKDataSourceFilterDescriptor ("NOT (Name like 'John')"));
        dataSource.AddSortDescriptor (new TKDataSourceSortDescriptor ("Name", true));
        dataSource.AddGroupDescriptor (new TKDataSourceGroupDescriptor ("Group"));

        var array = new NSMutableArray ();
        array.Add (new DSItem () { Name = "John", Value = 22.0f, Group = "one" });
        array.Add (new DSItem () { Name = "Peter", Value = 15.0f, Group = "one" });
        array.Add (new DSItem () { Name = "Abby", Value = 47.0f, Group = "one" });
        array.Add (new DSItem () { Name = "Robert", Value = 45.0f, Group = "two" });
        array.Add (new DSItem () { Name = "Alan", Value = 17.0f, Group = "two" });
        array.Add (new DSItem () { Name = "Saly", Value = 33.0f, Group = "two" });

        dataSource.DisplayKey = "Name";
        dataSource.ValueKey = "Value";
        dataSource.ItemSource = array;

        dataSource.FormatText ((NSObject item, TKDataSourceGroup group) => {
            DSItem dsItem = (DSItem)item;
            return new NSString(string.Format("{0} has {1} points", dsItem.Name, dsItem.Value));
        });

        var tableView = new UITableView (this.View.Bounds);
        tableView.DataSource = dataSource;
        this.View.AddSubview (tableView);
    }
}

You can modify descriptor collections by using the corresponding add and remove methods. You can reaplly all descriptors when data has changed by calling the reloadData method.