// // DocumentViewController.m // AntsContract // // Created by Ray on 12/16/16. // Copyright © 2016 United Software Applications, Inc. All rights reserved. // #define NUMBERS @"0123456789.\n" #import "PageViewController.h" #import "config.h" #import "const.h" #import "SignatureListViewController.h" #import "SignatureViewController.h" #import "PDFUtils.h" #import "ImageUtils.h" #import "TextUtils.h" #import "CheckSelectorViewController.h" #import "DatePickerViewController.h" #import "RAUtils.h" #import "AppDelegate.h" typedef enum { ScreenOrientationUnknown = 0, ScreenOrientationPortrait = 1, ScreenOrientationLand = 3 }ScreenOrientation; //#import "TouchImageView.h" @interface PageViewController () @property (nonatomic,assign) ScreenOrientation orientation; @property (nonatomic,assign) ScreenOrientation currentAppOrientation; @end @implementation PageViewController - (BOOL)canBecomeFirstResponder{ return YES; } //- (IBAction)onBtnClick:(id)sender { // [self becomeFirstResponder]; // UIView* v = sender; // // UIMenuItem *detail = [[UIMenuItem alloc] initWithTitle:@"abc" action:@selector(showDetail:)]; // // [detail setValue:_id forKey:@"_id"]; // // UIMenuController *menu = [UIMenuController sharedMenuController]; // // [menu setMenuItems:[NSArray arrayWithObjects:detail, nil]]; // // [menu setTargetRect:v.frame inView:v.superview]; // [menu setMenuVisible:YES animated:YES]; //} - (ScreenOrientation)currentAppOrientation { CGSize screenSize = [UIScreen mainScreen].bounds.size; if (screenSize.width > screenSize.height) return ScreenOrientationLand; return ScreenOrientationPortrait; } - (void)viewDidLoad { [super viewDidLoad]; // // self.pdfScrollView = [[PDFScrollView alloc]initWithFrame:CGRectMake(0, 64, 768, 960)]; // self.pdfScrollView.backgroundColor= [UIColor redColor]; //self.edgesForExtendedLayout = UIRectEdgeNone; // self.pageIndex=1; // self.pdfPageView.pageIndex=self.pageIndex; self.pdfPageView.pageRef= self.pageRef; self.pdfScrollView.contentSize = self.pdfScrollView.frame.size; self.keyboard_h=0; // self.pdfScrollView.contentInset = UIEdgeInsetsMake(64, 0, 0, 0); // [self initControl]; // UIPinchGestureRecognizer *pinchGestureRecognizer = [[UIPinchGestureRecognizer alloc] // initWithTarget:self // action:@selector(handlePinch:)]; // // [self.view addGestureRecognizer:pinchGestureRecognizer]; // Do any additional setup after loading the view. self.orientation = ScreenOrientationUnknown; // self.pdfScrollView.backgroundColor = [UIColor redColor]; // [self.pdfScrollView viewWithTag:1024].backgroundColor = [UIColor orangeColor]; // self.pdfPageView.backgroundColor = [UIColor yellowColor]; // self.editControlView.backgroundColor = [UIColor colorWithRed:0.4 green:0.8 blue:0.3 alpha:0.5]; // // [self.pdfPageView removeFromSuperview]; // [self.editControlView removeFromSuperview]; // self.view.backgroundColor = [UIColor blueColor]; } -(void)viewWillAppear:(BOOL)animated { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil]; // 键盘高度变化通知,ios5.0新增的 #ifdef __IPHONE_5_0 float version = [[[UIDevice currentDevice] systemVersion] floatValue]; if (version >= 5.0) { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillChangeFrame:) name:UIKeyboardWillChangeFrameNotification object:nil]; } #endif // self.pdfScrollView.contentSize = self.pdfScrollView.frame.size; } - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; if (self.orientation != ScreenOrientationUnknown) { if (self.orientation == self.currentAppOrientation) { // 修复未完全显示时停止拖动,再进入该页面就不会生成Control控件 if (self.editControlView.subviews.count) { [self refreshControl]; } else { [self initControl]; } } else { [self rotateView]; } } else { [self initControl]; } [self.parentViewController.view layoutIfNeeded]; } - (void)viewWillLayoutSubviews { /* * 解决旋转后第二个视图覆盖部分当前视图 */ [self clearControlView]; self.pdfScrollView.scrollEnabled = NO; // root [self updateView:self.view]; // scroll [self updateView:self.pdfScrollView]; // 1024 view [self updateView:[self.pdfScrollView viewWithTag:1024]]; // pdf [self updateView:self.pdfPageView]; // NSLog(@"pdf page frame %@",[NSValue valueWithCGRect:self.pdfPageView.frame]); [self.pdfPageView setNeedsDisplay]; // edit [self updateView:self.editControlView]; // contentSize self.pdfScrollView.contentSize = [self.pdfScrollView viewWithTag:1024].bounds.size; self.pdfScrollView.contentOffset = CGPointZero; [self initControl]; [self.view setNeedsDisplay]; self.pdfScrollView.scrollEnabled = YES; } - (void)clearControlView { for (UIView * v in self.editControlView.subviews) { [v removeFromSuperview ]; } } - (void)rotateView { if (self.orientation == self.currentAppOrientation) return; [self viewWillLayoutSubviews]; self.orientation = self.currentAppOrientation; for (UIViewController *vc in self.parentViewController.childViewControllers) { [vc viewWillLayoutSubviews]; } } - (void)updateView:(UIView *)view { // return; if (!view) return; CGRect frame = [UIScreen mainScreen].bounds; if (view == self.view) { } else if (view == self.pdfScrollView) { frame.origin.y = 64; frame.size.height -= 64; self.pdfScrollView.contentSize = CGSizeZero; } else { frame.size.height -= 64; view.transform = CGAffineTransformIdentity; // 解决缩放后,旋转屏幕。产生偏差(ContentSize和ContentOffset) } view.frame = frame; view.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleBottomMargin; [view layoutIfNeeded]; [view setNeedsDisplay]; } -(void) viewWillDisappear:(BOOL)animated { [self.hotTextView endEditing:true]; [[NSNotificationCenter defaultCenter] removeObserver:self]; self.orientation = self.currentAppOrientation; } -(void) dealloc { // CFBridgingRelease(self.pageRef); } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. } //- (void) handlePinch:(UIPinchGestureRecognizer*) recognizer //{ //// recognizer.view.transform = CGAffineTransformScale(recognizer.view.transform, recognizer.scale, recognizer.scale); //// recognizer.scale = 1; // // CGSize contentsize =self.pdfScrollView.contentSize; // // self.pdfScrollView.contentSize = CGSizeMake(contentsize.width*recognizer.scale, contentsize.height*recognizer.scale); //} //+(CGRect)WindowRect2PDFRect:(CGRect)rect pdf_rect:(CGRect)pdf_rect window_size:(CGSize)window_size //{ // CGRect pdf_expand; // float offset_x = 0; // float offset_y = 0; // float scale =1; // if(pdf_rect.size.height>=pdf_rect.size.width) // { // pdf_expand = CGRectMake(0, 0, pdf_rect.size.height*window_size.width/window_size.height, pdf_rect.size.height); // offset_x = (pdf_expand.size.width-pdf_rect.size.width)/2; // scale = pdf_rect.size.height/window_size.height; // // } // else // { // pdf_expand = CGRectMake(0, 0, pdf_rect.size.width, pdf_rect.size.width*window_size.height/window_size.width); // offset_y = (pdf_expand.size.height-pdf_rect.size.height)/2; // scale = pdf_rect.size.width/window_size.width; // } // float x=rect.origin.x*scale-offset_x; // float y=rect.origin.y*scale-offset_y; // float width = rect.size.width*scale; // float height = rect.size.height*scale; // // return CGRectMake(x, y, width, height); // //} -(CGRect) scaleControl:(CGRect) frame from:(CGSize)fwindowsize to:(CGSize)twindowsize { CGRect pdfsize=CGPDFPageGetBoxRect(self.pageRef, kCGPDFMediaBox); frame=[PDFUtils WindowRect2PDFRect:frame pdf_rect:pdfsize window_size:fwindowsize]; frame= [PDFUtils PDFRect2WindowRect:frame pdf_rect:pdfsize window_size:twindowsize]; return frame; } -(void) initControl { if(self.hide_control) return; // return; [self clearControlView]; // fill all时未显示。 int count = [self.page_controlTemplate[@"count"] intValue]; for(int i=0;i0) // marker_bg = [UIColor clearColor]; // else marker_bg=UIColorFromRGB(CK_BG); for(int i=0;i=3) // 模板有action项 { //[sender setTitle:item[0][0] forState:UIControlStateNormal]; NSMutableDictionary* action = item[2]; if(checked) { NSArray* disable_arr = action[@"disable"] ; for(int d = 0 ; d 0 && [decimalString characterAtIndex:0] == '1'; if (length == 0 ) { textView.text = decimalString; return NO; } if((length >= 10 && !hasLeadingOne) ) { if(length>15) return NO; // newString=[[newString stringByReplacingOccurrencesOfString:@"-" withString:@""] mutableCopy]; // [newString insertString:@"-" atIndex:14]; // textView.text = newString; // return NO; } if((length >= 11)) { if(length>16) return NO; // newString=[[newString stringByReplacingOccurrencesOfString:@"-" withString:@""] mutableCopy]; // [newString insertString:@"-" atIndex:16]; // textView.text = newString; // return NO; } NSUInteger index = 0; NSMutableString *formattedString = [NSMutableString string]; if (hasLeadingOne) { [formattedString appendString:@"1 "]; index += 1; } if (length - index > 3) { NSString *areaCode = [decimalString substringWithRange:NSMakeRange(index, 3)]; [formattedString appendFormat:@"(%@) ",areaCode]; index += 3; } if (length - index > 3) { NSString *prefix = [decimalString substringWithRange:NSMakeRange(index, 3)]; [formattedString appendFormat:@"%@ ",prefix]; index += 3; } if (length - index > 4) { NSString *prefix = [decimalString substringWithRange:NSMakeRange(index, 4)]; [formattedString appendFormat:@"%@-",prefix]; index += 4; } NSString *remainder = [decimalString substringFromIndex:index]; [formattedString appendString:remainder]; textView.text = formattedString; return NO; } else if([tv_format isEqualToString:@"price"]) { // NSMutableString *newString = [[textView.text stringByReplacingCharactersInRange:range withString:text] mutableCopy]; // NSArray *components = [newString componentsSeparatedByCharactersInSet:[[NSCharacterSet decimalDigitCharacterSet] invertedSet]]; // NSString *decimalString = [components componentsJoinedByString:@""]; // // // // // NSString *newprice = [formatter stringFromNumber:[NSNumber numberWithInt: [decimalString intValue]]]; // // textView.text = newprice; NSCharacterSet *cs; cs = [[NSCharacterSet characterSetWithCharactersInString:NUMBERS]invertedSet]; // NSString *filtered = [[text componentsSeparatedByCharactersInSet:cs]componentsJoinedByString:@""]; BOOL canChange = [text isEqualToString:filtered]; // if(canChange) // { // // } return canChange; // return NO; } else { NSString* oldtext =textView.text; if(oldtext==nil) oldtext=@""; bool canChange=true; int max_line = [self.page_controlTemplate [[NSString stringWithFormat:@"control_%ld",index] ][@"lines"] intValue]; if(max_line==0)//默认只能有一行 max_line=1; long linecount=[TextUtils countOccurencesOfString:oldtext find:@"\n"]; if(max_line!=-1) //maxline -1表示不限制行数。 { if(linecount<=max_line-1&& [text isEqualToString:@"\n"]) { canChange = false; } } int lenth = [self.page_controlTemplate [[NSString stringWithFormat:@"control_%ld",index] ][@"length"] intValue]; if(lenth==0) return canChange; else { return canChange&& (lenth>textView.text.length ||[text isEqualToString:@""]); } } } //- (void)textViewDidBeginEditing:(UITextView *)textView //{ // self.hotTextView = textView; //} - (void)textViewDidBeginEditing:(UITextView *)textView { long index = textView.tag - CONTROL_BASE; NSString* tv_format=self.page_controlTemplate [[NSString stringWithFormat:@"control_%ld",index] ][@"format"]; if([tv_format isEqualToString:@"price"]) { NSString* prefix_str= self.page_controlTemplate [[NSString stringWithFormat:@"control_%ld",index] ][@"prefix_str"]; if(prefix_str.length==0) prefix_str = @""; NSString* text =[textView.text stringByReplacingOccurrencesOfString:prefix_str withString:@"$"]; NSNumberFormatter *formatter = [[NSNumberFormatter alloc]init]; formatter.numberStyle =kCFNumberFormatterCurrencyStyle; NSNumber* number= [formatter numberFromString:text]; if(number!=nil) textView.text = [NSString stringWithFormat:@"%.2f",[number doubleValue]]; } // NSRange r = textView.selectedRange; // // // textView.selectedRange = NSMakeRange(textView.text.length, 0); // NSRange range; // // range.location = textView.text.length; // // range.length = 0; // // textView.selectedRange = range; } - (BOOL)textViewShouldBeginEditing:(UITextView *)textView { NSLog(@"textViewShouldBeginEditing"); // long index = textView.tag - CONTROL_BASE; // // // NSMutableDictionary* control_json = self.page_controlTemplate [[NSString stringWithFormat:@"control_%ld",index] ]; // // if([control_json[@"disable"] boolValue] ) // { // return false; // } // self.hotTextView = textView;//(UITableViewCell*)textView.superview.superview; return textView.editable; } - (void)textViewDidEndEditing:(UITextView *)textView { long index = textView.tag - CONTROL_BASE; //处理 prefix 和 浮点格式化 NSString* prefix_str= self.page_controlTemplate [[NSString stringWithFormat:@"control_%ld",index] ][@"prefix_str"]; if(prefix_str.length==0) prefix_str = @""; NSString* text =textView.text;// [textView.text stringByReplacingOccurrencesOfString:prefix_str withString:@""]; NSString* value_type= self.page_controlTemplate [[NSString stringWithFormat:@"control_%ld",index] ][@"value_type"]; if([value_type isEqualToString:@"float"]) text=[NSString stringWithFormat:@"%.2f",[text doubleValue] ]; // text =[NSString stringWithFormat:@"%@%@",prefix_str,text] ; if([self.page_controlTemplate [[NSString stringWithFormat:@"control_%ld",index] ][@"format"] isEqualToString:@"price"]) { text =textView.text; NSNumberFormatter *formatter = [[NSNumberFormatter alloc]init]; formatter.numberStyle =kCFNumberFormatterCurrencyStyle; NSNumber * nprice =[NSNumber numberWithDouble: [text doubleValue]]; if(text.length==0) nprice=nil; text = [formatter stringFromNumber:nprice]; text=[text stringByReplacingOccurrencesOfString:@"$" withString:prefix_str]; // textView.text = newprice; // return NO; } textView.text = text; if(text==nil) text=@""; self.page_controlTemplate [[NSString stringWithFormat:@"control_%ld",index] ][@"value"] = text; self.page_controlTemplate [[NSString stringWithFormat:@"control_%ld",index] ][@"dirty"] =[NSNumber numberWithBool:true]; NSDictionary* action = self.page_controlTemplate [[NSString stringWithFormat:@"control_%ld",index] ][@"action"]; NSArray* keys=[action allKeys]; for(int k=0;k0) { //UIAlertControllerStyle两种类型UIAlertControllerStyleAlert类似UIAlertView //UIAlertControllerStyleActionSheet类似UIActionSheet UIAlertController *alertControl = [UIAlertController alertControllerWithTitle:@"Authorization Verification" message:@"Please enter authorization code." preferredStyle:UIAlertControllerStyleAlert]; //block代码块取代了delegate [alertControl addTextFieldWithConfigurationHandler:^(UITextField *textField) { }]; UIAlertAction *actionOne = [UIAlertAction actionWithTitle:@"Confirm" style:UIAlertActionStyleDestructive handler:^(UIAlertAction *action) { UITextField *name = alertControl.textFields.firstObject; if(![name.text isEqualToString:pwd]) { [RAUtils message_alert:@"You are not authorized to change this field" title:@"Authorization verification failed." controller:self]; return; } else { [self signature:touchImageView]; } }]; UIAlertAction *alertthree = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) { DebugLog(@"Cancel"); return; }]; [alertControl addAction:actionOne]; [alertControl addAction:alertthree]; //UIAlertControllerStyle类型为UIAlertControllerStyleAlert可以添加addTextFieldWithConfigurationHandler:^(UITextField *textField) [self presentViewController:alertControl animated:YES completion:nil]; } else { [self signature:touchImageView]; } //// menu.selector = self.selector; //// //// menu.selectordelegate = self; // // // 1.创建一个UIPopover // UIPopoverController *popover = [[UIPopoverController alloc] initWithContentViewController:[[UINavigationController alloc] initWithRootViewController:menu]]; // // // // UIPopoverPresentationController // // 2.设置尺寸 // // popover.popoverContentSize = CGSizeMake(320, 44 * 5); // // // 3.从哪里显示出来 --> 指向item // // [popover presentPopoverFromRect:self.view.frame inView:self.view permittedArrowDirections:0 animated:YES]; // // [popover presentPopoverFromBarButtonItem:item permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES]; // //// self.popover = popover; // popover pop // // [self performSegueWithIdentifier:@"selector_popover" sender:self]; // __block int tag = touchImageView.tag; // UIViewController* vc=[RAUtils getViewController :touchImageView]; // // if(self.editable==true) // { // // // ImageUploadViewController * uploadvc =[ vc.storyboard instantiateViewControllerWithIdentifier:@"ImageUploadViewController"]; // // // UIImage* img =[self.buttonImg backgroundImageForState:UIControlStateNormal];; // // if(self.img_validate) // uploadvc.img= touchImageView.image; // // uploadvc.returnValue = ^(NSString* url_down,NSString* url_up,UIImage* img) // { // // self.imgs[tag] = url_up; // // NSString* newurl=[RAUtils arr2string:self.imgs separator:@"," trim:false]; // // touchImageView.image=img; // // if(self.imgChanged) // self.imgChanged(url_down,newurl,tag,url_up); // // }; // // [vc.navigationController pushViewController:uploadvc animated:false]; // } // else // { // if(touchImageView.image==nil) // return ; // ImageViewController * imagevc =[ vc.storyboard instantiateViewControllerWithIdentifier:@"ImageViewController"]; // // // UIImage* img=touchImageView.image; // // if(self.img_validate) // imagevc.image = img;//.imageView.image = [self.buttonImg backgroundImageForState:UIControlStateNormal]; // // // uploadvc.returnValue = ^(NSString* url_down,NSString* url_up,UIImage* img) // // { // // // // [self.buttonImg setBackgroundImage:img forState:UIControlStateNormal]; // // // // if(self.imgChanged) // // self.imgChanged(url_down,url_up); // // // // }; // // [vc.navigationController pushViewController:imagevc animated:false]; // } // // bundleVC.content_data = self.bundle_item; // } #pragma mark Responding to keyboard events - (CGRect)relativeFrameForScreenWithView:(UIView *)v { UIWindow * window=[[[UIApplication sharedApplication] delegate] window]; CGRect rect=[v convertRect: v.bounds toView:window]; return rect; // BOOL iOS7 = [[[UIDevice currentDevice] systemVersion] floatValue] >= 7; // // CGFloat screenHeight = [UIScreen mainScreen].bounds.size.height; // if (!iOS7) { // screenHeight -= 20; // } // UIView *view = v; // CGFloat x = .0; // CGFloat y = .0; // while (view.frame.size.width != 320 || view.frame.size.height != screenHeight) { // x += view.frame.origin.x; // y += view.frame.origin.y; // view = view.superview; // if ([view isKindOfClass:[UIScrollView class]]) { // x -= ((UIScrollView *) view).contentOffset.x; // y -= ((UIScrollView *) view).contentOffset.y; // } // } // return CGRectMake(x, y, v.frame.size.width, v.frame.size.height); } - (void)keyboardWillChangeFrame:(NSNotification *)notification { NSLog(@"keyboardWillChangeFrame"); } - (void)keyboardWillShow:(NSNotification *)notification { NSLog(@"keyboardWillShow"); if(self.keyboard_show) return; self.keyboard_show=true; /* Reduce the size of the text view so that it's not obscured by the keyboard. Animate the resize so that it's in sync with the appearance of the keyboard. */ NSDictionary *userInfo = [notification userInfo]; NSValue* aValue = [userInfo objectForKey:UIKeyboardFrameEndUserInfoKey]; CGSize keyboardSize = [aValue CGRectValue].size; NSLog(@"keyboard height:%f",keyboardSize.height); CGRect cellrect_screen = [self relativeFrameForScreenWithView:self.hotTextView]; CGRect rect_screen = [ UIScreen mainScreen ].bounds; int cellpos = cellrect_screen.origin.y+cellrect_screen.size.height; if(cellpos>rect_screen.size.height-keyboardSize.height) { // self.resize = true; self.keyboard_h =keyboardSize.height; CGPoint contentOffsetPoint = self.pdfScrollView.contentOffset; CGSize contentSize =self.pdfScrollView.contentSize;//frame.size; CGSize size1 = self.pdfScrollView.bounds.size; NSLog(@"frame:%@ bound:%@", NSStringFromCGSize(contentSize),NSStringFromCGSize(size1) ); contentSize.height+=self.keyboard_h; contentOffsetPoint.y+=self.keyboard_h; self.pdfScrollView.contentSize =contentSize; self.pdfScrollView.contentOffset=contentOffsetPoint; } // self.pdfScrollView.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height-self.keyboard_h); // CGSize tablecontent =self.editorTable.contentSize; // tablecontent.height=tablecontent.height+self.keyboard_h; // self.editorTable.contentSize=tablecontent; // } // Animate the resize of the text view's frame in sync with the keyboard's appearance. // [self moveInputBarWithKeyboardHeight:keyboardRect.size.height withDuration:animationDuration]; } - (void)keyboardWillHide:(NSNotification *)notification { NSLog(@"keyboardWillHide"); self.keyboard_show=false; // NSDictionary* userInfo = [notification userInfo]; /* Restore the size of the text view (fill self's view). Animate the resize so that it's in sync with the disappearance of the keyboard. */ // NSValue *animationDurationValue = [userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey]; // NSTimeInterval animationDuration; // [animationDurationValue getValue:&animationDuration]; CGPoint contentOffsetPoint = self.pdfScrollView.contentOffset; CGSize contentSize =self.pdfScrollView.contentSize;//frame.size; contentSize.height-=self.keyboard_h; contentOffsetPoint.y-=self.keyboard_h; self.pdfScrollView.contentSize=contentSize; self.pdfScrollView.contentOffset=contentOffsetPoint; // CGSize tablecontent =self.pdfScrollView.contentSize; // tablecontent.height=tablecontent.height-self.keyboard_h; // self.editorTable.contentSize=tablecontent; // self.pdfScrollView.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height); self.keyboard_h= 0; // NSLog(@"before refresh %@",NSStringFromCGSize(self.editorTable.contentSize)); // if(self.resize) // { // NSTimeInterval animationDuration = 0.30f; // CGRect frame = self.view.frame; // // if(prewTag == textField.tag) //当结束编辑的View的TAG是上次的就移动 // // { //还原界面 // // moveY = prewMoveY; // frame.origin.y +=self.ioffset; // frame.size. height -=self.ioffset; // self.view.frame = frame; // // } // //self.view移回原位置 // [UIView beginAnimations:@"ResizeView" context:nil]; // [UIView setAnimationDuration:animationDuration]; // self.view.frame = frame; // [UIView commitAnimations]; // //[textField resignFirstResponder]; // self.ioffset=0; // } // [self moveInputBarWithKeyboardHeight:0.0 withDuration:animationDuration]; } @end