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 rowNSIndexPath*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]; } | 
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  { ... 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; }return0; }   -(NSArray*)orderStatusNames {   return orderStatusNames; } | 
| @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 | 
| #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]; } | 
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 return2; }   -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {switch(section){case0:// the first section will have two cells (showing OrderDate & OrderStatus)return2; case1:// the second section will have a single cell (showing the Client field)return1;   default:return0; }}   staticNSString*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){case0:{// for the first section ...switch(index){case0:{// 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; }case1:{// 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; }case1:{// for the second sectionswitch(index){case0:{// 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; } | 
| -(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 dataif(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]; } | 
| -(void)tableView:(UITableView *)aTableView didSelectRowAtIndexPath:(NSIndexPath*)indexPath {   [self hidePicker:activePicker]; int section =[indexPath section]; int index =[indexPath indexAtPosition:1];   switch(section){case0:{switch(index){case0:{ datePickerView.date =[orderRow valueForKey:@"OrderDate"]; [self showPicker:(UIPickerView *)datePickerView]; break; }case1:{   break; }}}}} | 
| -(void)viewDidLoad { ... statePickerView =[[UIPickerView alloc] init]; [statePickerView setDataSource:self]; [statePickerView setDelegate:self]; [statePickerView setShowsSelectionIndicator:YES]; } | 
| #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 componentif(pickerView == statePickerView)return1; return0; }   -(NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component {   if(pickerView == statePickerView)return[[[DataAccess sharedInstance] orderStatusNames] count]; return0; } | 
| #pragma mark - Table view delegate   -(void)tableView:(UITableView *)aTableView didSelectRowAtIndexPath:(NSIndexPath*)indexPath { ... switch(section){case0:{switch(index){case0:{ ... }case1:{[self showPicker:statePickerView]; break; }}}}} | 
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 ;)