You are browsing the archive for Alexander.

Updating Data in iOS Client Applications

October 26, 2011 in Cocoa, iOS, iPhone, Mac, Relativity, ROFX, Uncategorized, Xcode

As promised earlier, I’m going to tell you how to change data in an iOS application and apply changes to the DA Server. As client application we will use the iOS Relativity Client we have previously created.

In the course of this session we will try to implement an editor for changing data from the Orders table. We will also use UIPickerView & UIDatePicker for changing the Order’s fields.
The source code can be obtained from here.
Let’s start!

The first thing we need to do is to add a new editor view and its controller.
After performing File->New…->New File, select the “UIViewController subclass” item. As the base class we will use the UITableViewController.
Now we need to add an edit button to the existing OrdersViewController. Clicking on this button should open an editor for the selected row.

- (void)viewDidLoad {
    [super viewDidLoad];
 
    // create & set Edit button    
    UIBarButtonItem *actionButton = 
     [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemEdit 
                                                   target:self 
                                                   action:@selector(edit:)];
    [[self navigationItem] setRightBarButtonItem:actionButton];
    [actionButton release];
    ...
}
 
- (IBAction)edit:(id)aSender {
 
    OrderEditorController *editor = 
      [[[OrderEditorController alloc] initWithStyle:UITableViewStyleGrouped] autorelease];
 
    // set OrderViewController as the delegate to be able to notify it about the results of the editing process
    [editor setDelegate:self];
 
    // find the currently selected row
    NSIndexPath *path = [self.tableView indexPathForSelectedRow];
    NSInteger idx = [path indexAtPosition:1];
    DADataTableRow *row = [orderRows objectAtIndex:idx];
 
    // and set it for our editor 
    [editor setOrderRow:row];
 
    // push the editor view to be a topmost view
    [[self navigationController] pushViewController:editor animated:YES];
}
 
// delegate the method that the editor will call for applying changes
-(void)endEditWithOk:(DADataTableRow *)row {
    // applying changes on Relativity server
    [[DataAccess sharedInstance] applyUpdatesForTable:[row table]];
    // reload view for synchronizing client-side data
    [[self tableView] reloadData];
}
 
// delegate the method that the editor will call for canceling changes
-(void)endEditWithCancel:(DADataTableRow *)row {
    // cancel the change
    [row cancel];
    // reload table
    [[self tableView] reloadData];
}

Now it is time to start implementing our OrderEditorController. The Orders table has several fields we can edit: OrderDate and OrderStatus, but we can also change the client of the order, though I don’t think we need to change the document in such a radical way ;) .

Note: There is one small problem we need to resolve first. Unfortunately, the current version of the PCTrade database doesn’t have an OrderStatus reference, so the Orders table only has its integer values (which by default are 0).
Let’s assume that we need to implement several available document statuses. A new uncompleted order usually has the “New” status. When it is completed but not payed yet, it has a “Pending” status. When we received the money, it changes to “Closed” and finally, there is the additional “Deleted” status. In the future we probably may want to track the document history, so we loose any of the orders, even if they were deleted. So our database will keep those orders, but with the “Deleted” status. In order to keep the correspondence between the available status numbers and their names, we will add a new NSArray into our DataAccess class as shown below.

/////////////////////////////
//  DataAccess.h
@interface DataAccess : NSObject <DARemoteDataAdapterDelegate, DAAsyncRequestDelegate>
{
    ...
    NSArray *orderStatusNames;
}
...
@property (readonly) NSArray *orderStatusNames;
-(NSUInteger)idForOrderStatusName:(NSString *)name;
-(NSString *)nameForOrderStatus:(NSUInteger)status;
@end
 
/////////////////////////////
//  DataAccess.m
 
- (id) init {
	self = [super init];
	if (self) {
		...
 
        orderStatusNames = [[NSArray alloc] initWithObjects:
                         @"New", 
                         @"Pending", 
                         @"Closed", 
                         @"Deleted", 
                         nil];
 
	}
	return self;
}
 
- (void) dealloc {
   ...  
   [orderStatusNames release], orderStatusNames = nil;
   [super dealloc];
}
 
 
-(NSString *)nameForOrderStatus:(NSUInteger)status {
 
    if (status >= [orderStatusNames count])
        return @"Unknown state";
 
    return [orderStatusNames objectAtIndex:status];
}
 
-(NSUInteger)idForOrderStatusName:(NSString *)name { 
 
    for (NSUInteger i = 0; i < [orderStatusNames count]; i++) {
        NSString *statusName = [orderStatusNames objectAtIndex:i];
        if ([statusName compare:name] == NSOrderedSame) 
            return i;
    }
    return 0;
}
 
-(NSArray *)orderStatusNames {
 
    return orderStatusNames;
}

Now we are ready to start the editor implementation. Please open the OrderEditorController.h file and add the following:

@interface OrderEditorController : UITableViewController {
 
    DADataTableRow *orderRow;
 
    UIBarButtonItem *saveButton;
    UIBarButtonItem *cancelButton;
 
    id delegate;
    NSDateFormatter *df; 
}
 
@property (retain) DADataTableRow *orderRow;
@property (assign) id delegate;
 
- (IBAction)save:(id)aSender;
- (IBAction)cancel:(id)aSender;
@end

Our editor should have references to the edited row, the delegate reference, the save & cancel buttons and the appropriate methods. This will implement them:

#pragma mark - View lifecycle
 
- (void)viewDidLoad {
    [super viewDidLoad];
 
    // create & add cancel button    
    cancelButton = [[UIBarButtonItem alloc] initWithTitle:@"Cancel" 
                                                    style:UIBarButtonItemStylePlain 
                                                   target:self action:@selector(cancel:)];
    self.navigationItem.leftBarButtonItem = cancelButton;
 
    // create & add save button    
    saveButton = [[UIBarButtonItem alloc] initWithTitle:@"Save" 
                                                  style:UIBarButtonItemStyleDone 
                                                 target:self action:@selector(save:)];
    self.navigationItem.rightBarButtonItem = saveButton;
 
    // we will also need this formatter for converting NSDate to NSString 
    df = [[NSDateFormatter alloc] init];
    [df setDateStyle:NSDateFormatterMediumStyle];
}
 
- (IBAction)save:(id)aSender {
 
    if ([delegate respondsToSelector:@selector(endEditWithOk:)])
        [delegate endEditWithOk:orderRow];
 
    [self.navigationController popViewControllerAnimated:YES];
 
}
 
- (IBAction)cancel:(id)aSender {
 
    if ([delegate respondsToSelector:@selector(endEditWithCancel:)])
        [delegate endEditWithCancel:orderRow];
 
    [self.navigationController popViewControllerAnimated:YES];
}

In the majority of cases, we can represent the editor view as the table view, where each cell represents the field name at the left side and the current value at the right side. We can also group certain fields together based on general meaning or application logic. To do that we have to initialize our OrderEditorController with the UITableViewStyleGrouped style. When the user taps on a certain cell, we will need to allow him to edit that value.

So now we need to configure our table view as shown below:

 
#pragma mark - Table view data source
 
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    // we want to have two groups, 
    // one for OrderDate & OrderStatus, and one for the Client field 
    return 2;
}
 
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    switch (section) {
        case 0:
            // the first section will have two cells (showing OrderDate & OrderStatus)
            return 2;
        case 1:
            // the second section will have a single cell (showing the Client field)
            return 1;
 
        default:
            return 0;
    }
}
 
static NSString *TABLE_CELL_ID = @"OrderEditorCell";
 
- (UITableViewCell *)tableView:(UITableView *)tableView 
         cellForRowAtIndexPath:(NSIndexPath *)indexPath {
 
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:TABLE_CELL_ID];
    if (cell == nil) {
      cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 
                                     reuseIdentifier:TABLE_CELL_ID] autorelease];
    }
 
    // Configure the cell...
    int section = [indexPath section];
    int index = [indexPath indexAtPosition:1];
    switch (section) {
      case 0: { // for the first section ...
        switch (index) {
          case 0: { // configure the cell to show the OrderDate field
                    [[cell textLabel] setText:@"Order Date:"];
                    NSDate *value = [orderRow valueForKey:@"OrderDate"];
                    NSString *valueAsString = value ? [df stringFromDate:value] : @"Click to select";
                    [[cell detailTextLabel] setText:valueAsString];
                    break;
          }
          case 1: { // configure the cell to show the OrderStatus field
                    [[cell textLabel] setText:@"Order Status:"];
                    NSNumber *value = [orderRow valueForKey:@"OrderStatus"];
                    NSString *valueAsString = value ? 
                         [[DataAccess sharedInstance] nameForOrderStatus:[value intValue]] : 
                         @"Click to select";
                    [[cell detailTextLabel] setText:valueAsString];
                    break;
          }
        }
        break;
      }
      case 1: { // for the second section
        switch (index) {
          case 0: { // configure the cell to show the Client field
                    [[cell textLabel] setText:@"Client:"];
                    NSString *value = [orderRow valueForKey:@"CustomerName"];
                    NSString *valueAsString = value ? value : @"Click to select";
                    [[cell detailTextLabel] setText:valueAsString];
                    break;
          }
        }
        break;
     }
     default:
       break;
    }
    [cell setNeedsDisplay];
	return cell;
}

Now, when you compile and run the project, you should see something like the following:

Next we can start to implement the editing for the OrderDate and OrderStatus fields. For editing dates we can use the really good NSDatePicker control with the appropriate configuration. For editing the OrderStatus we can use our custom picker populated with available statuses.
I also want the picker view to appear at the bottom of the current editor using animation, like the standard Contacts application does. Having spent some time with Google, I’ve found an interesting Data Cell sample with the required functionality. I’ve just slightly adjusted it for our purposes, namely to support animation for custom pickers.

- (void)viewDidLoad {
 
  ...
  // create & configure our DatePicker
  datePickerView = [[UIDatePicker alloc] init];
  [datePickerView setDatePickerMode:UIDatePickerModeDate];
  [datePickerView addTarget:self 
                     action:@selector(pickerDidChange) 
           forControlEvents:UIControlEventValueChanged];
 
  pickerDoneButton = [[UIBarButtonItem alloc] initWithTitle:@"Done" 
                                                      style:UIBarButtonItemStyleDone 
                                                     target:self action:@selector(pickerDone)];
  pickerCancelButton = [[UIBarButtonItem alloc] initWithTitle:@"Cancel" 
                                                        style:UIBarButtonItemStyleDone 
                                                       target:self action:@selector(pickerCancel)];
}
 
#pragma mark - Common picker routines
-(void)showPicker:(UIPickerView *)picker {
 
    if (!picker)
        return;
 
    if (activePicker) 
        [self hidePicker:activePicker];
 
    activePicker = picker;
 
    if (picker.superview == nil) {
        [self.view.window addSubview:picker];
 
        // size up the picker view to our screen and compute the start/end 
        // frame the origin for our slide-up animation
        // compute the start frame
        CGRect screenRect = [[UIScreen mainScreen] applicationFrame];
        CGSize pickerSize = [picker sizeThatFits:CGSizeZero];
        CGRect startRect = CGRectMake(0.0,
                                      screenRect.origin.y + screenRect.size.height,
                                      pickerSize.width, pickerSize.height);
        picker.frame = startRect;
 
        // compute the end frame
        CGRect pickerRect = CGRectMake(0.0,
                                       screenRect.origin.y + screenRect.size.height - pickerSize.height,
                                       pickerSize.width,
                                       pickerSize.height);
        // start the slide-up animation
        [UIView beginAnimations:nil context:NULL];
        [UIView setAnimationDuration:0.3];
 
        // we need to perform some post operations after the animation is complete
        [UIView setAnimationDelegate:self];
 
        picker.frame = pickerRect;
 
        // shrink the table's vertical size to make room for the date picker
        CGRect newFrame = self.tableView.frame;
        newFrame.size.height -= picker.frame.size.height;
        self.tableView.frame = newFrame;
        [UIView commitAnimations];
 
        // add the "Done" & "Cancel" buttons to the nav bar
        self.navigationItem.rightBarButtonItem = pickerDoneButton;
        self.navigationItem.leftBarButtonItem = pickerCancelButton;
    }
}
 
-(void)removePicker:(UIPickerView *)picker {
 
    if (!picker)
        return;
 
    if (picker.superview != nil) {
        [picker removeFromSuperview];    
    }
}
 
-(void)hidePicker:(UIPickerView *)picker {
 
    if (!picker)
        return;
 
    activePicker = nil;
 
    CGRect screenRect = [[UIScreen mainScreen] applicationFrame];
    CGRect endFrame = picker.frame;
    endFrame.origin.y = screenRect.origin.y + screenRect.size.height;
 
    // start the slide-down animation
    [UIView beginAnimations:nil context:picker];
    [UIView setAnimationDuration:0.3];
 
    // we need to perform some post operations after the animation is complete
    [UIView setAnimationDelegate:self];
    [UIView setAnimationDidStopSelector:@selector(animationDidStop:finished:context:)];
 
    picker.frame = endFrame;
    [UIView commitAnimations];
 
    // grow the table back again in vertical size to make room for the date picker
    CGRect newFrame = self.tableView.frame;
    newFrame.size.height += picker.frame.size.height;
    self.tableView.frame = newFrame;
 
    // remove the "Done" button and restore the nav bar
    self.navigationItem.rightBarButtonItem = saveButton;
    self.navigationItem.leftBarButtonItem = cancelButton;
 
    // deselect the current table row
    //NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
    //[self.tableView deselectRowAtIndexPath:indexPath animated:YES];
 
}
 
-(void)animationDidStop:(NSString *)animationID 
               finished:(NSNumber *)finished 
                context:(void *)context {
    [self removePicker:context];
}
 
// called when we click the Done button
-(void)pickerDone {
 
    // change the data
    if (activePicker == (UIPickerView *)datePickerView)
        [orderRow setValue:datePickerView.date forKey:@"OrderDate"];
 
    // hide picker
    [self hidePicker:activePicker];
    // reload table view to show changes
    [[self tableView] reloadData];
}
 
// called when we click on the Done button
-(void)pickerCancel {
    // just hide the picker
    [self hidePicker: activePicker];
    [[self tableView] reloadData];
}
 
// the following method will sync the current picker value with the value in the table view
- (void)pickerDidChange {
 
    NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
    UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
 
    if (activePicker == (UIPickerView *)datePickerView)
        cell.detailTextLabel.text = [df stringFromDate:datePickerView.date];
}

Now we need to launch the UIDatePicker when a user taps on the OrderDate field:

- (void)tableView:(UITableView *)aTableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
 
    [self hidePicker:activePicker];
    int section = [indexPath section];
    int index = [indexPath indexAtPosition:1];
 
    switch (section) {
      case 0: {
        switch (index) {
          case 0: {
            datePickerView.date = [orderRow valueForKey:@"OrderDate"];
            [self showPicker:(UIPickerView *)datePickerView];
            break;
          }
          case 1: {
 
            break;
	  }
        }
      }
    }
}

When you compile and run the application, you should get something like the following:

Finally, we need to implement our custom UIPickerView, which is quite simple. At the beginning we need to create a custom UIPickerView and set its data source and delegate properties:

- (void)viewDidLoad {
    ...    
    statePickerView = [[UIPickerView alloc] init];
    [statePickerView setDataSource:self];
    [statePickerView setDelegate:self];
    [statePickerView setShowsSelectionIndicator:YES];
}

Then we need to add a support UIPickerViewDataSource protocol to our editor class and implement several methods, as shown below:

 
#pragma mark - Custom Picker routines
 
// callback method for filling content for our custom statuses picker
- (NSString *)pickerView:(UIPickerView *)pickerView 
             titleForRow:(NSInteger)row 
            forComponent:(NSInteger)component {
 
    if (pickerView == statePickerView)
        return [[DataAccess sharedInstance] nameForOrderStatus:row];
    return @"";
}
 
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView{
 
    // it should be a simple picker with a single component
    if (pickerView == statePickerView)
        return 1;
    return 0;
}
 
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component {
 
    if (pickerView == statePickerView)
        return [[[DataAccess sharedInstance] orderStatusNames] count];
    return 0;
}

Change the tableView:didSelectRowAtIndexPath: method to show our state picker:

#pragma mark - Table view delegate
 
- (void)tableView:(UITableView *)aTableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
   ...    
    switch (section) {
      case 0: {
        switch (index) {
          case 0: {
            ...
          }
          case 1: {
            [self showPicker:statePickerView];
            break;
          }
        }
      }
    }
}

That’s all. Now when you tap on the status cell of the editor, you should get something like this:

You can choose a new status, then click the done button and finally save the current order.

I’m slightly worried that you may find this blog post a little confusing and not easy to reproduce. We wrote quite a lot of code here, and I deliberately skipped some minor details, trying to make it easier, but you can download the whole source code from here and play with the project yourself.

Thank you for your attention ;)

Creating a Simple iOS Client for Relativity Server

September 23, 2011 in iOS, Relativity, Xcode

Today I want to talk about how to create a simple iPhone client application for Relativity server using the DataAbstract for Xcode templates. Though the focus will be on Xcode 4, the same mechanisms can be applied to Xcode 3.

Going through a set of actions, we will use a DataAbstract iOS Application template to create an application with several views representing a Master/Detail relation.

Let’s start with the first step.

Creating a new project using a DataAbstract iOS Application template

Open your Xcode application and perform the File>New>New project… command.
In the opened sheet, choose the RemObjects Software category from the iOS templates section and select the Data Abstract iOS Application template.

Specify the product name and don’t forget to set the Talk to Relativity Server option.

Finally, select the destination folder for your new project and press the Create button. Your new project should now be open:

Let’s take a few seconds to review what you have at the moment. You should have a compilable application (with some warnings – we will review them a bit later) that has an AppDelegate class and a RootViewController with its xib. There should also be a DataAccess class, which will be responsible for all manipulation with data. The new project also has all the necessary linker flags and properly configured paths as shown in the screen-shot below:

Implementation of the iOS client

Now you can compile the application, after which you will see a set of warnings:

These warnings were generated by the template system to highlight places that require adjusting or proper implementation to get your application working. Let’s walk through all of them and eliminate them one by one.

1. Adjusting the connection configuration

First, let’s change some defaults in the generated DataAccess class to specify the Relativity server URL, domain and schema. For this example, let’s assume that you have the Relativity server running on a Windows computer with the IP 192.168.0.150.

Open the DataAccess.m file and specify the proper URL for the Relativity server, domain and schema.
You can comment the warning there, since we are done with it (as shown below).

//#warning Define your server address and Relativity domain name, below.
#define SERVER_URL		@"http://192.168.0.150:7099/bin"
#define SERVICE_NAME		@"DataService" // Do not change when using Relativity server
 
#define RELATIVITY_DOMAIN	@"PCTrade Sample"
#define RELATIVITY_SCHEMA	@"PCTrade"

2. Defining tables you want to work with

Since the DataAccess class encapsulates all functionality needed for working with data, you need to define your tables there. Let’s define them as retain properties. See the code below:

@interface DataAccess : NSObject <DARemoteDataAdapterDelegate, DAAsyncRequestDelegate>
{
    ...    
    DADataTable *clientsTable;
    DADataTable *ordersTable;
    DADataTable *orderDetailsTable;
}
 
@property (retain) DADataTable *clientsTable;
@property (retain) DADataTable *ordersTable;
@property (retain) DADataTable *orderDetailsTable;

Don’t forget to add the proper @syntesize directive and release the properties in the dealloc method in the DataAccess.m file.

#pragma mark -
#pragma mark Properties
@synthesize clientsTable, ordersTable, orderDetailsTable;
...
- (void) dealloc {
    ...
    [clientsTable release], clientsTable = nil;
    [ordersTable release], ordersTable = nil;
    [orderDetailsTable release], orderDetailsTable = nil;
 
	[super dealloc];
}

3. Implementing the initial loading of data

Now let’s implement the method for the initial loading of data from the Relativity server.
Locate the downloadData method of the DataAccess class and add the following code:

Note: Our templates contain various vital tips and explanations on how to use a particular method. Please carefully review this kind of information, it can be very useful.

- (void)downloadData {
	//#warning Implement this method to download any data your application needs on first start
 
	NSArray *tableNames = [NSArray arrayWithObjects: 
                           CLIENTS_TABLE_NAME, 
                           ORDERS_TABLE_NAME, 
                           ORDERDETAILS_TABLE_NAME, nil];
 
	NSDictionary *tables = [rda getDataTables:tableNames];
 
	[self setClientsTable:[tables valueForKey:CLIENTS_TABLE_NAME]];
	[self setOrdersTable:[tables valueForKey:ORDERS_TABLE_NAME]];
	[self setOrderDetailsTable:[tables valueForKey:ORDERDETAILS_TABLE_NAME]];
}

Note that we used a synchronous call to getDataTables here, because the whole downloadData method will be executed in a background thread (i.e. asynchronously).

4. Adding briefcase support to your application

In order to allow your application to work in offline mode, you need to persist downloaded data somewhere on the device. Let’s use the DABriefcase functionality for this.
Locate the loadDataFromBriefcase: and saveDataToBriefcase: methods in the DataAccess class and implement them as shown below:

- (void)loadDataFromBriefcase:(DABriefcase *)briefcase {
 
	//#warning Implement this method to re-load your data from the briefcase when re-launched
 
	[self setClientsTable:[briefcase tableNamed:CLIENTS_TABLE_NAME]];
	[self setOrdersTable:[briefcase tableNamed:ORDERS_TABLE_NAME]];
	[self setOrderDetailsTable:[briefcase tableNamed:ORDERDETAILS_TABLE_NAME]];
}
 
- (void)saveDataToBriefcase:(DABriefcase *)briefcase {
	//#warning Implement this method to save your data to the briefcase
 
	[briefcase addTable:[self clientsTable]];
	[briefcase addTable:[self ordersTable]];
	[briefcase addTable:[self orderDetailsTable]];
 
	// Uncomment this to finalize briefcase support
	// (without this value written, DataAccess will ignore the briefcase when starting up, 
        // see threadedLoadInitialData)
	[briefcase.properties setValue:BRIEFCASE_DATA_VERSION forKey:BRIEFCASE_DATA_VERSION_KEY];
}

5. Extending the Orders table with lookup and calculated fields

Let’s look at the orders table structure. It only has a minimal set of the fields like id, client id, date and status. That’s not a lot and it would be be great to see (for example) the customer name instead of its ID and the total sum for the given order (which could be calculated from the OrderDetails table). Toachieve this, you’ll have to add lookup and calculated fields to the Orders table. Locate the setupData method of the DataAccess class and add the following implementation:

- (void)setupData {
 
	// Use this method to implement any setup that's needed on your data tables, such as
	// creating lookup or calculated fields, etc.
 
        [[self ordersTable] addCalculatedFieldName:@"OrderTotal" 
                                          dataType:datCurrency
                                            target:self
                                          selector:@selector(calculateSumForOrder:)];
 
	[[self ordersTable] addLookupFieldName:@"CustomerName" 
                                   sourceField:[[self ordersTable] fieldByName:@"Client"] 
                                   lookupTable:[self clientsTable]
                                lookupKeyField:[[self clientsTable] fieldByName:@"ClientId"] 
                             lookupResultField:[[self clientsTable] fieldByName:@"ClientName"]];
}
 
-(id)calculateSumForOrder:(DADataTableRow *)row {
 
	double result = 0;
	NSPredicate *p = [NSPredicate predicateWithFormat:@"Order == %@", [row valueForKey:@"OrderId"]];
	NSArray *rows = [[orderDetailsTable rows] filteredArrayUsingPredicate:p];
	for (DADataTableRow *orow in rows) {
		double summ = [(NSNumber *)[orow valueForKey:@"Total"] doubleValue];
		result = result + summ;
	}
	return [NSNumber numberWithDouble:result];
}

6. Checking the login and password stuff in your Application delegate

Relativity server provides secured services protected by login and password. Thus, when asking for data the first time, a SessionNotFound exception will be raised on the Relativity side to tells you that you need to login to the Relativity domain first. When you log in to it, it will be able to find your registered session and allow you to proceed. The iOS client application can catch this kind of exception and provide a login operation in the background, and, after successful login, it will repeat the last failed request. When you open the PCTradeClientAppDelegate.h file you will see that this class supports the DataAccessDelegate protocol, which has a needLogin:password: method that will be used for obtaining the proper login and password.
Since you are mostly using default settings, you can leave the already generated method untouched. It should look like this:

- (BOOL)needLogin:(NSString **)login password:(NSString **)password {
 
	//#warning Implement this method to ask the user for login information when needed
 
	// The DataAccess automatically takes care of storing login information in Settings and Key Chain,
	// and retrieving it as necessary. This method is only called when no login is stored (i.e. on first
	// run) or if the login retrieved was rejected by the server.
	// Typically, you would implement this method to bring up a UI that asks the user for his credentials.
 
	// By default, new Relativity Server domains will use "Data"/"Relativity" as login, unless
	// the login provider setup has been changed by the administrator.
 
	*login = @"Data";
	*password = @"Relativity";
 
	return YES; // return YES if the user provided login details, or NO if the user canceled
}

7. Implementing the RootViewController

You can now configure the root view to show the list of clients:

Open the RootViewController.m file and locate the myTable method that specifies the main data table for the whole current view. Since the default name myTable is a bit nondescript, let’s use the Refactoring>Rename option to rename it viewData.
Now add the following implementation:

- (DADataTable *)viewData {
 
	//#warning Implement this method to return the table this view will work on
	return [[DataAccess sharedInstance] clientsTable];
}

Also, let’s give the root view a more meaningful title.
By default, the root view is called “Root View”. Let’s change it to something more appropriate:
Add a string define with the title and use it in the viewDidLoad method as shown below:

#define VIEW_TITLE @"Our Clients"
 
- (void)viewDidLoad 
{
	[super viewDidLoad];
	[self setTitle:VIEW_TITLE];
...

Now you can compile the project and see the results in the iPhone simulator; it should look something like this:

8. Adding a detail view “Orders”

As next step, let’s implement the detail view that will show orders for each particular client.
First, add a new UITableViewController class with its xib file.

Make sure that your controller is based on the UITableViewController class.

The controller will take the DADataTableRow instance with the client info and then obtain and show a list of orders for the given client.

Our OrdersViewController.h will have something like the following:

@interface OrdersViewController : UITableViewController {
 
    NSArray *orderRows;
    DADataTableRow *clientRow;
}
 
@property (retain) DADataTableRow *clientRow;
@end

9. Obtaining an orders list for a given client

Now you need to hook on the moment when the client row will be set as the clientRow property and obtain the list of orders. Let’s write a custom setter method for this:

-(void)setClientRow:(DADataTableRow *)value {
 
    // set the new client row
    [value retain];
    [clientRow release];
    clientRow = value;
 
    // obtain the list of orders
    [orderRows release];
    orderRows = [[[DataAccess sharedInstance] ordersForClientID:[clientRow valueForKey:@"ClientId"]] 
retain];
 
    // reload data in the UI
    [[self tableView] reloadData];
    [self setTitle:[clientRow valueForKey:@"ClientName"]];
}

Now is a good time for adding the ordersForClientID: method inside our DataAccess class.
Its implementation is rather simple. Since the DataAccess already has the whole orders table, your task will be to extract only the orders for the given client.
Let’s use NPredicate for this:

- (NSArray *)ordersForClientID:(NSString *)clientID {
 
    NSPredicate *condition = [NSPredicate predicateWithFormat:@"Client == %@", clientID];
    return [ordersTable rowsFilteredUsingPredicate:condition];
}

Finally, let’s implement what you want to expose in the clients list.
Locate the method and implement it as shown below:

// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath  {
	// ToDo: Configure the cell. You may want to simply feed data from your table 
        // into a regular UITableViewCell
	// or provide your own customized cell imlementation to display the data in a richer format.
 
	static NSString *CellIdentifier = @"Cell";
	UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
	if (cell == nil) {
             cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault 
                                            reuseIdentifier:CellIdentifier] autorelease];
	}
 
	//#warning Implement this method to customize how your data is displayed
	DADataTableRow *row = [rows objectAtIndex:indexPath.row];
	[[cell textLabel] setText:[row valueForKey:@"ClientName"]];
 
	return cell;
}

10. Implementing the data presentation for the orders view

Now let’s form an appropriate representation of the data for each customer order with minimal effort, meaning that we avoid creating custom cells for this table and just use one of predefined styles provided by Apple.

Locate and open the tableView:cellForRowAtIndexPath: method of the OrdersViewController.m file and change it like this:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
 
    static NSString *CellIdentifier = @"OrdersCell";
 
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1
                                       reuseIdentifier:CellIdentifier] autorelease];
    }
 
    // Configure the cell...
    NSDateFormatter *df = [[[NSDateFormatter alloc] init] autorelease];
    [df setDateStyle:NSDateFormatterMediumStyle];
 
    NSNumberFormatter *cf = [[NSNumberFormatter alloc] init];
    [cf setNumberStyle:NSNumberFormatterCurrencyStyle];
 
    DADataTableRow *r = [orderRows objectAtIndex:indexPath.row];
    NSDate *orderDate = [r valueForKey:@"OrderDate"];
    NSNumber *orderSum = [r valueForKey:@"OrderSum"]; 
    [[cell textLabel] setText:[df stringFromDate:orderDate]];
    [[cell detailTextLabel] setText:[cf stringFromNumber:orderSum]];
    return cell;
}

Here I should note that we used UITableViewCellStyleValue1 as the cell style, which allows us to compose the main text label with the order date at the left side and the detail label with order total at the right label of the cell. Also please note that the OrderSum field is the calculated field you have added earlier.

I propose to improve the cell view for your root view a bit and show the client’s phone as detail information. Locate the tableView:cellForRowAtIndexPath: method in the RootViewController.m file and change it to something like this:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath  {
	// ToDo: Configure the cell. You may want to simply feed data from your table
        // into a regular UITableViewCell
	// or provide your own customized cell imlementation to display the data in a richer format.
 
	static NSString *CellIdentifier = @"Cell";
	UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
	if (cell == nil) {
		cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle 
                                               reuseIdentifier:CellIdentifier] autorelease];
	}
 
	//#warning Implement this method to customize how your data is displayed
	DADataTableRow *row = [rows objectAtIndex:indexPath.row];
	[[cell textLabel] setText:[row valueForKey:@"ClientName"]];
        [[cell detailTextLabel] setText:[row valueForKey:@"ContactPhone"]];
        return cell;
}

Let’s compile and test our application. It should show the “Our Clients” view with a list of clients, and when you tap on a particular client it will switch to the Orders view which will show orders for the tapped client.

Looks good, doesn’t it? But there’s still one thing to improve: for clients without any orders, it shows an empty orders view. But there is an easy way to change that.

11. Final adjustments.

Let’s define whether the given client has any orders, and if it does, let’s add a disclosure button for this cell, so you will know if there is an orders view to show for the given customer or not.

To do this, let’s add another calculated field to your clientsTable as shown below:

- (void)setupData {
	...    
    [[self clientsTable] addCalculatedFieldName:@"OrderCnt" 
                                      dataType:datInteger
                                        target:self
                                      selector:@selector(calculateOrdersCountForClient:)];
	...
}
 
-(id)calculateOrdersCountForClient:(DADataTableRow *)row {
 
	NSPredicate *p = [NSPredicate predicateWithFormat:@"Client == %@", [row valueForKey:@"ClientId"]];
	NSArray *rows = [[ordersTable rows] filteredArrayUsingPredicate:p];
	return [NSNumber numberWithInt:[rows count]];
 
}

Now, you can analyze this field value and make a decision whether to draw a disclosure button to the cell in the RootViewController.

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
	...
    if([[row valueForKey:@"OrderCnt"] intValue] > 0)
        cell.accessoryType = UITableViewCellAccessoryDetailDisclosureButton;
    else
        cell.accessoryType = UITableViewCellAccessoryNone;
 
	return cell;
}

In conclusion, you will need to move the implementation of the loading orders view from
tableView:didSelectRowAtIndexPath:
to
tableView:accessoryButtonTappedForRowWithIndexPath:.

-(void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath {
    // Pass the selected object to the new view controller.
    DADataTableRow *row = [rows objectAtIndex:indexPath.row];
    [detailViewController setClientRow:row];
 
    [self.navigationController pushViewController:detailViewController animated:YES];
}

And that’s all. Compile and run the resulting application. You should have something like the following:

In my next blog post, I’ll show you how to further improve this application and add the ability to edit and save data.

Thanks

Changes in the Data Abstract Samples Suite

August 17, 2011 in .NET, Delphi, iOS, iPad, iPhone, Mac, ROFX, Windows, Xcode

Before the next upcoming release, I would like to highlight several changes concerning our Data Abstract samples suite.

Provided sample domain with the Relativity Server:

With the next release, we will provide Relativity Server with a new sample domain named PCTrade Sample that will contain a single schema called PCTrade. This schema will use the PCTrade for SQLite connection. The default login and password for this domain are Data and Relativity, respectively.

With this sample domain, Relativity Server can now be used as the server part for most of our DA samples in addition to the existing DASampleServer.

For the Xcode samples, you can easily choose the server you want to connect to:

For the .NET & Delphi samples you will need to set the flag value to True in the sample code as shown in the picture below:

Please note that several .NET samples, like Business Processor Sample and Refresh Update Data Sample, do not support working with Relativity since they depend on server logic implementation.

New Login service for the DASample server:

From the beginning, our samples had a virtual login implementation used only for the identification of clients and their platforms. For the next release, we have added a new secure DataService and LoginService to the DASample server. In order to maintain compatibility, we left the existing DASampleService unsecure. So if you try to build a client using that service, you will not need a login. But we added the new DataService on the top of the DASampleService, and made it secure. Thus, if you request that service, you need to login first.

Grouping in DA LINQ

August 5, 2011 in .NET, ROFX, Windows

I would like to give you a preview of the next release of DataAbstract for .NET, which will provide grouping support in DA LINQ (and in DA SQL as well).
You will be able to write queries like this:

A simple GROUP BY with an aggregate function Count:

var q =
	from x in DataModule.Instance.DataAdapter.GetTable()
	group x by x.Client into g
	select new { Client = g.Key, OrderCount = g.Count() };
 
return q.ToList();

This query will be translated into the followig DA SQL query:

SELECT
	[t0].[Client], COUNT(*) AS [agg1]
FROM
	[Orders] [t0]
GROUP BY
	[t0].[Client]

The query in action:

A GROUP BY with other aggregate functions:

var q =
	from x in DataModule.Instance.DataAdapter.GetTable()
	group x by x.Provider into g
	select new
	{
		provID = g.Key,
		sumVal = g.Sum(x =&gt; x.Total),
		minVal = g.Min(x =&gt; x.Total),
		maxVal = g.Max(x =&gt; x.Total),
		avgVal = g.Average(x =&gt; x.Total)
	};
 
return q.ToList();

This query will be translated into:

SELECT
	[t0].[Provider], SUM([t0].[Total]) AS [agg1], MIN([t0].[Total]) AS [agg2],
	MAX([t0].[Total]) AS [agg3], Avg([t0].[Total]) AS [agg4]
FROM
	[OrderDetails] [t0]
GROUP BY
	[t0].[Provider]

The query in action:

A GROUP BY on several fields:

var q =
	from x in DataModule.Instance.DataAdapter.GetTable()
	group x by new { x.Product, x.Provider } into g
	select new
	{
		prodID =
		g.Key.Product,
		provID = g.Key.Provider,
		sumVal = g.Sum(x =&gt; x.Total)
	};
 
return q.ToList();

The query will be translated into:

SELECT
	[t0].[Product] AS [prodID], [t0].[Provider] AS [provID], SUM([t0].[Total]) AS [agg1]
FROM
	[OrderDetails] [t0]
GROUP BY
	[t0].[Product], [t0].[Provider]

The query in action:

Using DAArrayDataSource

July 30, 2011 in Cocoa, Mac, ROFX, Xcode

One of the most popular UI controls to represent tabular data is the NSTableView. And today I want to talk about binding RemObjects Data Abstract tables to the NSTableView.

As you might know, there are several ways to tie up data with the NSTableView.
The first one is to provide a delegate class for the NSTableView by implementing the informal NSTableViewDataSource protocol.
Another one is to use the Cocoa Bindings technology.

Let’s consider the first approach. Its advantage, from my point of view, is in its simplicity. You can manually control how the data gets into the table and from it. But you need to write the code for it and sometimes that can be boring. ;)

So let me introduce you to the DAArrayDataSource class, which will do this work (and a lot of other useful stuff) for you.

Quick start

It’s very simple to use the DAArrayDataSource. All you need to do is to create an instance of the DAArrayDataSource and set its array property and set up a proper identifier for each column of the table view in order to map the table fields to the table view columns and finally tie up the DAArrayDataSource with your NSTableView.
Take a look at the sample below:

// Create and configure the RemoteDataAdapter (I've used local Samples Server)
NSURL * url = [NSURL URLWithString:@"http://192.168.0.100:8099/bin"];
DARemoteDataAdapter *adapter = [[DARemoteDataAdapter alloc] initWithTargetURL:url];
[[adapter dataService] setServiceName:@"DASampleService"];
 
// Get the data
DADataTable *workerTable = [[adapter getDataTable:@"Workers"] retain];
 
//Create the DAArraySource
DAArrayDataSource *arrayDS = [[DAArrayDataSource alloc] init];
 
// Load data here
[arrayDS setArray:[workerTable rows]];
 
// Tie it up with the NSTableView
[arrayDS setTableView:tableView];

That’s all. Simple, isn’t it?

Note:

The fact that the DAArrayDataSource encapsulates the NSTableViewDataSource methods inside will help you in case your controller has several table views. Just use separate instances of the DAArrayDataSource for each table view to avoid messiness in your code.

Sorting data

At the same time, the DAArrayDataSource provides a set of additional yummy features.
For example, without writing a line of code, you can sort data inside your table view by clicking on the column header. Moreover, you can sort by several fields at the same time. Sounds good, doesn’t it? Consider the code below:

// Sort by the Position field first
[arrayDS setSortField:@"WorkerPosition"];    
 
// Then sort by First Name
[arrayDS setSecondarySortField:@"WorkerFirstName"];
 
// Both sorts are descending
[arrayDS setSortAscending:NO];
 
// And that's all, from here on, the data is sorted

Also, the DAArrayDataSource will persist the current rows selection between different modes of sorting. Just before applying sorting, it will store the selection and will try to restore it after applying new sorting. Thus you will never lose the records on which you are working at the moment.

Unfortunately, this won’t work if you try to reload all data and the entire “rows” array will be changed.

Editing & display changes

Anther interesting feature is that the DAArrayDataSource controls the ability of the table view to do inline editing.
If its read-only property equals NO, you can edit data and all changed rows will be shown in a BOLD font until they are applied, so you can easily see what changes you’ve just made.

Advanced view customization

In conclusion I think that the DAArrayDataSource is a nicely configurable class, allowing you to specify the font type, its color and size, as well as the row height for the whole NSTableView.
Using a delegate of the DAArrayDataSource, which should implement the DAArrayDataSourceDelegate, you can hook on various events. For example:

- (void)tableView:(NSTableView *)tableView needsTextColor:(NSColor **)text backgroundColor:(NSColor **)back forRow:(NSInteger)rowIndex { 
 
	// Getting the value 
	DADataTableRow *row = [[arrayDS array] objectAtIndex:rowIndex];
	NSString *positionValue = [row valueForKey:@"WorkerPosition"];
 
	// Conditional coloring
	if ([@"Sales Manager" compare:positionValue] == NSOrderedSame)
		*text = [NSColor redColor];
	if ([@"Sales Representative" compare:positionValue] == NSOrderedSame)
		*text = [NSColor greenColor];
	if ([@"Inside Sales Coordinator" compare:positionValue] == NSOrderedSame)
		*text = [NSColor blueColor];
}
 
-(void)tableViewSelectionDidChange:(NSTableView *)aTableView {
 
	NSMutableString *selectedPerson = [NSMutableString stringWithString:@""];
	for (DADataTableRow *row in [arrayDS selectedRows]) {
 
		[selectedPerson appendFormat:@"%@ %@ (%@)\n", 
		[row valueForKey:@"WorkerFirstName"],
		[row valueForKey:@"WorkerLastName"],
		[row valueForKey:@"WorkerID"]];
	}
	[selectedPersonLabel setStringValue:selectedPerson];
}

That’s all, hope you enjoyed it.
If you have any questions, don’t hesitate to ask, I’ll be happy to answer.
You can get the source code of the project I’ve used above here:

In my next blog post, I’m going to write about using the Cocoa Bindings approach for binding a DADataTable to various controls.

Using Cocoa Bindings for binding DADataTable to NSTableView.

February 15, 2010 in Cocoa, RemObjects, ROFX, Uncategorized, Xcode

As you might know, Cocoa offers several ways for populating NSTableView with data.
The first approach is to use the NSTableViewDataSource protocol, which we are applying widely in our samples.

The second approach I want to cover today, is using Cocoa Bindings.

What is Cocoa Bindings?

Cocoa Bindings is an amazing technology that allows us to establish a full two-way data binding without writing any “glue” code. Cocoa Bindings widely uses the MVC paradigm, where models encapsulate application data, view display and edit data and controllers mediate between model and view.

So firstly, we will need to implement our own DataTableController, which we can reuse for any future projects.

In order to avoid creating a new sample from scratch, I’ve used our DASimpleSample, from which I removed all code for filling NSTableView using the NSTableViewDataSource protocol.

Implementing our DataTableController:

As the base for our controller, we will take the NSArrayController class.

So, let’s create a new class called DataTableController, inherit it from NSArrayController, add the DADataTable instance variable, which represents our data table and expose it as the read/write property like on code-snipped below:

//  DataTableController.h
 
#import <DataAbstract/DataAbstract.h>
 
@interface DataTableController : NSArrayController
{
	DADataTable *table;
}
 
@property (retain) DADataTable *table;
 
@end

In the DataTableController.m file we need to implement the setTable method, which retains the given DADataTable and set the table rows as content property of our controller.

-(void)setTable:(DADataTable *)newTable
{
	[newTable retain];
	[table release];
	table = newTable;
	[self setContent:[table rows]];
}

Also, in order to allow our controller to add and delete rows, we need to override add and remove methods as shown below:

-(void)add:(id)object
{
	if (!table) return;
	DADataTableRow *newRow = [table addNewRow];
	[self rearrangeObjects];
	[self setSelectedObjects:[NSArray arrayWithObject:newRow]];
}
 
-(void)remove:(id)object
{
	if (!table) return;
	for (DADataTableRow *row in [self selectedObjects])
		[table removeRow:row];
	[self rearrangeObjects];
	[self selectNext:self];
}

That’s all, our controller is ready. Let’s see how to use it for binding data.

Binding DataTable:

Open MainMenu.xib in Interface Builder and add NSArrayController.

Then, having added the selected controller, go to the Identity Inspector and select DataTableController as the class of the controller.

Then open Attributes Inspector and add keys. Keys is the column name of our DataTable. Specifying keys will allow us to select appropriate values in binding columns.

Now go to our window, select the NSTableView control and bind each of its columns to our controller.

For that, you will need to select a column and, in the Binding Inspector tab in the Value section, choose Bind To: Data Table Controller, set arrangedObjects as Controller Key and put the appropriate column name in the Model Key Path combo-box. We should get something like this:

Finally, we will set a link between add/remove methods and our controller.

To do that, we have to expand the toolbar of our application, then open the HUD window for our controller by performing a right mouse button click on it and connect add: and remove: actions with the appropriate toolbar buttons. You will see something like this:

We are now finished with the visual part and can close Interface Builder. The last thing to do is to set the content of our controller to a specific table. Switch to XCode and modify the asyncRequest: didReceiveTable: method as shown below:

- (void)asyncRequest:(DAAsyncRequest *)request didReceiveTable:(DADataTable *)table
{
	workersTable = [table retain];
	// set our table...
	[tableController setTable:workersTable];
}

That’s All. Let’s compile and run our application to see the results.

We can edit/insert/delete rows, all should work as expected and all the configuration took place in Interface Builder, we just had to write one line of code.

[tableController setTable:workersTable];

In the next my blog post, I’m going to tell you more about Cocoa Bindings. We will, among other things, try to add the client-side filtering of data in the table, the highlighting of changed cells.

Left Outer Joins in DA LINQ.

January 27, 2010 in Uncategorized

In terms of improving DA LINQ, I’m happy to announce that i have just committed improved support for the LEFT OUTER JOIN in the next release of the Data Abstract for .NET.

We can compose Left Outer join query by using DefaultIfEmpty standard query operator. See the sample below:

var query =  from c in lrda.GetTable()
             where c.CustomerID == "PARIS"
             join o in lrda.GetTable()
             on c.CustomerID equals o.CustomerID into ords
             from o in ords.DefaultIfEmpty()
             select new {c.CustomerID, o.EmployeeID};

Notice that we are using an into clause to direct the matching join results into a temporary sequence ords. The DefaultIfEmpty operator will be applied onto each item of that sequence, so if the record is missing from the joined results, a default value will be provided.

The DA LINQ query above will be translated into following DA SQL statement that will be passed to the DASqlengine.

SELECT [t0].[CustomerID], [t1].[EmployeeID]
  FROM [Customers] AS [t0]
    LEFT OUTER JOIN [Orders] AS [t1] ON ([t1].[CustomerID] = [t0].[CustomerID])
    WHERE ([t0].[CustomerID] = :p0)
---
:p0='PARIS'

This improvement will be in the next release.

and it will execute against the database with the expected result:

CustomerID EmployeeID
---------- -----------
PARIS      NULL

Using NSPredicateEditor with DADataTable

January 18, 2010 in Bugs 7, Cocoa, Mac, ROFX, Xcode

Today I want to talk about NSPredicateEditor and how to use it with a DADataTable.

What is NSPredicateEditor…

Cocoa’s NSPredicateEditor class provides an excellent way to compose predicates at runtime. The main benefit is that the user needs to know nothing about predicates and their format, but with help of NSPredicateEditor can easily build quite complex conditions they need. Using NSPredicateEditor also allows to avoid any kind of typing errors, because in most of cases all the user needs is to do is just to select appropriate fields and values from popup buttons.

Here is the screenshot of Bugs 7 showing NSPredicateEditor that represents following condition: (Live == 1 AND AreaID == 4 AND OwnerID == 5) OR ID == 37222

NSPredicateEditor uses a list of NSPredicateEditorRowTemplate objects, where each row template describes certain elementary comparison predicate object.  Combining different row templates allows the NSPredicateEditor to represent compound predicates with unlimited complexity.

Usually, a row template holds three elements: left and right expressions and a condition between them. In our case, the left expression represents the column from our table and the right expression represents the value pattern for that column. Obviously, it is good to have template with most suitable editor for each column data type. DataAbstract for OS X provides proper templates for each fields that can be used in the table. In addition to standard  NSPredicateEditorRowTemplate DA brings several custom row templates that simplify building predicates for the tables.

The most interesting here is the DALookupFieldPredicateEditorRowTemplate class. This class allows to create row template for a lookup field, where the user can chose certain lookup value from list of allowed values, proposed by a popup button. It allows user to deal with meaningful values instead of just IDs. This is well shown in the figure above.

I should also mention DAFallbackPredicateEditorRowTemplate. This class describes special template for any predicate that is not supported by a given set of templates. For example if your editor tries to render a NSPredicate that comes into your application from outside and cannot understand a certain part of the compound predicate, then it will render it as “unsupported predicate” with help of DAFallbackPredicateEditorRowTemplate. This assures that the sub-predicate, while not editable in the NSPredicateEditor, will not get lost.

…and How Can You Use it?

Let’s see how to configure NSPredicateEditor instance for composing predicates for a certain custom DADataTable.

When you need to configure the editor for exposing a sub-set of columns only, you need to create array of row templates obtained from each table field you want to see in editor:

NSMutableArray *rowTemplates = [NSMutableArray arrayWithCapacity:[[da bugs] fieldCount]];
for (NSString *f in allowedFields)
{
  DAFieldDefinition *fd = [[da bugs] fieldByName:f];
  [rowTemplates addObjectsFromArray:[fd predicateEditorRowTemplates]];
}

Then you should supplement that array with additional row template for building compound predicates

NSPredicateEditorRowTemplate *compoundTemplate =
  [[[NSPredicateEditorRowTemplate alloc] initWithCompoundTypes:
    [NSArray arrayWithObjects:
      [NSNumber numberWithInt: NSAndPredicateType],
      [NSNumber numberWithInt: NSOrPredicateType],
      [NSNumber numberWithInt: NSNotPredicateType],
      nil]] autorelease];
[rowTemplates addObject:compoundTemplate];

and (optionally) add an instance of DAFallbackPredicateEditorRowTemplate for rendering unsupported predicates.

[rowTemplates addObject:[DAFallbackPredicateEditorRowTemplate rowTemplate]];

Finally, you should assign that templates array to your NSPredicateEditor instance

[editor setRowTemplates:rowTemplates];

If you want to expose all visible fields in the editor, then you can just use the convenient defaultPredicateEditorRowTemplates] method on the table, and whole configuration will be reduced to single line of code, as followings:

[editor setRowTemplates:[[da bugs] defaultPredicateEditorRowTemplates]];

That’s all. Your predicate editor is properly configured and you can use it for building any kind of predicates for given table.

You can use that editor to let the user define NSPredicates both for local filtering data in table like:

NSPredicate *p = [editor predicate];
rows = [[[myTable rows] filteredArrayUsingPredicate:p] retain];
[tableView reloadData];

or for creating Dynamic Where for server-side filtering data.

NSPredicate *p = [editor predicate];
DADynamicWhereClause *clause = [DADynamicWhereClause dynamicWhereClauseWithPredicate:p];
request = [rda beginGetDataTable:@"MyTable"
                          select:[self fieldsToSelect]
                           where:clause
                           start:NO];
[request setDelegate:self];
[request start];

I recomend to review our DAFilters sample that ships with DA/OSX, to see NSPredicateEditor and the techniques shown here in action.