├── .gitignore ├── .travis.yml ├── BZGFormViewController.podspec ├── BZGFormViewController ├── BZGFormCell.h ├── BZGFormCell.m ├── BZGFormViewController.h ├── BZGFormViewController.m ├── BZGInfoCell.h ├── BZGInfoCell.m ├── BZGKeyboardControl.h ├── BZGKeyboardControl.m ├── BZGPhoneTextFieldCell.h ├── BZGPhoneTextFieldCell.m ├── BZGTextFieldCell.h ├── BZGTextFieldCell.m ├── Constants.h ├── NSError+BZGFormViewController.h └── NSError+BZGFormViewController.m ├── BZGFormViewControllerTests ├── BZGFormCellSpecs.m ├── BZGFormViewControllerSpecs.m ├── BZGInfoCellSpecs.m ├── BZGKeyboardControlSpecs.m ├── BZGPhoneTextFieldCellSpec.m └── BZGTextFieldCellSpecs.m ├── LICENSE ├── README.md ├── Screenshots ├── SignupForm.gif ├── SignupForm.png ├── SignupForm@2x.gif └── SignupForm@2x.png └── SignupForm ├── Podfile ├── Podfile.lock ├── SignupForm.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcshareddata │ └── xcschemes │ ├── SignupForm.xcscheme │ └── SignupFormTests.xcscheme ├── SignupForm.xcworkspace └── contents.xcworkspacedata ├── SignupForm ├── AppDelegate.h ├── AppDelegate.m ├── Images.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── LaunchImage.launchimage │ │ └── Contents.json ├── LaunchImage.xib ├── SignupForm-Info.plist ├── SignupForm-Prefix.pch ├── SignupViewController.h ├── SignupViewController.m ├── en.lproj │ └── InfoPlist.strings └── main.m └── SignupFormTests ├── SignupFormTests-Info.plist ├── SignupFormTests-Prefix.pch └── en.lproj └── InfoPlist.strings /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | xcuserdata 3 | SignupForm/Pods/* 4 | *.xccheckout 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | before_install: 3 | - gem i cocoapods --no-ri --no-rdoc 4 | - brew uninstall xctool; brew install xctool --HEAD; 5 | script: 6 | - cd SignupForm 7 | - pod install 8 | - xctool test -workspace SignupForm.xcworkspace -scheme SignupFormTests -sdk iphonesimulator ONLY_ACTIVE_ARCH=NO 9 | -------------------------------------------------------------------------------- /BZGFormViewController.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'BZGFormViewController' 3 | s.version = '2.4.5' 4 | s.license = 'MIT' 5 | s.summary = 'A library for creating dynamic forms.' 6 | s.homepage = 'https://github.com/benzguo/BZGFormViewController' 7 | s.author = { 'Ben Guo' => 'benzguo@gmail.com' } 8 | s.source = { 9 | :git => 'https://github.com/benzguo/BZGFormViewController.git', 10 | :tag => "v#{s.version}" 11 | } 12 | s.dependency 'ReactiveCocoa', '~>2.3' 13 | s.dependency 'libextobjc', '~>0.4' 14 | s.dependency 'libPhoneNumber-iOS', '~>0.7' 15 | s.requires_arc = true 16 | s.platform = :ios, '6.0' 17 | s.source_files = 'BZGFormViewController/*.{h,m}' 18 | end 19 | -------------------------------------------------------------------------------- /BZGFormViewController/BZGFormCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // BZGFormCell.h 3 | // 4 | // https://github.com/benzguo/BZGFormViewController 5 | // 6 | 7 | #import 8 | #import "BZGInfoCell.h" 9 | 10 | @protocol BZGFormCellDelegate; 11 | 12 | typedef NS_ENUM(NSInteger, BZGValidationState) { 13 | BZGValidationStateNone, 14 | BZGValidationStateInvalid, 15 | BZGValidationStateWarning, 16 | BZGValidationStateValid, 17 | BZGValidationStateValidating, 18 | }; 19 | 20 | @interface BZGFormCell : UITableViewCell 21 | 22 | /// The current validation state. Default is BZGValidationStateNone. 23 | @property (assign, nonatomic) BZGValidationState validationState; 24 | 25 | // The cell's validation error 26 | @property (copy, nonatomic) NSError *validationError; 27 | 28 | /// The cell displayed when the cell's validation state is Invalid or Warning. 29 | @property (strong, nonatomic) BZGInfoCell *infoCell; 30 | 31 | /// The cell's delegate 32 | @property (strong, nonatomic) id delegate; 33 | 34 | @end 35 | 36 | @protocol BZGFormCellDelegate 37 | 38 | /** 39 | * Tells the delegate that the form cell changed its validation state. 40 | */ 41 | - (void)formCell:(BZGFormCell *)formCell didChangeValidationState:(BZGValidationState)validationState; 42 | 43 | @end 44 | -------------------------------------------------------------------------------- /BZGFormViewController/BZGFormCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // BZGFormCell.m 3 | // 4 | // https://github.com/benzguo/BZGFormViewController 5 | // 6 | 7 | #import "BZGFormCell.h" 8 | #import "Constants.h" 9 | 10 | @implementation BZGFormCell 11 | 12 | - (id)init 13 | { 14 | self = [super initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil]; 15 | if (self) { 16 | // Hide default elements 17 | self.textLabel.hidden = YES; 18 | self.detailTextLabel.hidden = YES; 19 | self.imageView.hidden = YES; 20 | 21 | self.selectionStyle = UITableViewCellSelectionStyleNone; 22 | self.backgroundColor = BZG_BACKGROUND_COLOR; 23 | 24 | _validationState = BZGValidationStateNone; 25 | } 26 | return self; 27 | } 28 | 29 | - (id)initWithStyle:(UITableViewCellStyle)style 30 | reuseIdentifier:(NSString *)reuseIdentifier 31 | { 32 | @throw [NSException exceptionWithName:NSInternalInconsistencyException 33 | reason:[NSString stringWithFormat:@"%@ is not supported. Please use init instead.", NSStringFromSelector(_cmd)] 34 | userInfo:nil]; 35 | } 36 | 37 | - (void)setValidationState:(BZGValidationState)validationState 38 | { 39 | _validationState = validationState; 40 | if (self.delegate) { 41 | [self.delegate formCell:self didChangeValidationState:validationState]; 42 | } 43 | } 44 | 45 | - (void)setValidationError:(NSError *)error 46 | { 47 | _validationError = error; 48 | [self.infoCell setText:error.localizedDescription]; 49 | } 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /BZGFormViewController/BZGFormViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // BZGFormViewController.h 3 | // 4 | // https://github.com/benzguo/BZGFormViewController 5 | // 6 | 7 | #import 8 | #import "BZGFormCell.h" 9 | #import "NSError+BZGFormViewController.h" 10 | 11 | @class BZGInfoCell, BZGTextFieldCell, BZGPhoneTextFieldCell; 12 | 13 | @interface BZGFormViewController : UIViewController 14 | 15 | /// The table view managed by the controller object. 16 | @property (nonatomic, strong) UITableView *tableView; 17 | 18 | /// The form cells used to populate the table view's form section. 19 | @property (nonatomic, strong) NSMutableArray *formCells __deprecated; 20 | 21 | /// The table view section where the form should be displayed. (Use 22 | @property (nonatomic, assign) NSUInteger formSection __deprecated; 23 | 24 | /// A property indicating whether or not the keyboard should display an input accessory view with previous, next, and done buttons. 25 | @property (nonatomic, assign) BOOL showsKeyboardControl; 26 | 27 | /// A property indicating whether or not a cell manages displaying its own validation state underneath itself with another cell. Default is YES; 28 | @property (assign, nonatomic) BOOL showsValidationCell; 29 | 30 | /** 31 | * A property indicating whether or not the form is valid. 32 | * A form is valid when all of its formCells have validation state 33 | * BZGValidationStateValid or BZGValidationStateWarning. 34 | */ 35 | @property (nonatomic, readonly) BOOL isValid; 36 | 37 | /** 38 | * Initializes a form view controller to manage a table view of a given style. 39 | * 40 | * @param style A constant that specifies the style of table view that the controller object is to manage (UITableViewStylePlain or UITableViewStyleGrouped). 41 | * @return An initialized BZGFormViewController object or nil if the object couldn’t be created. 42 | */ 43 | - (id)initWithStyle:(UITableViewStyle)style; 44 | 45 | /** 46 | * Updates the display state of the info cell below a form cell. 47 | * 48 | * @param cell an instance of BZGTextFieldCell in a BZGFormViewController's form cells 49 | */ 50 | - (void)updateInfoCellBelowFormCell:(BZGTextFieldCell *)cell; 51 | 52 | /** 53 | * Returns the next form cell. 54 | * 55 | * @param cell The starting form field cell. 56 | * @return The next form field cell or nil if no cell is found. 57 | */ 58 | - (BZGTextFieldCell *)nextFormCell:(BZGTextFieldCell *)cell; 59 | 60 | /** 61 | * Returns the previous form cell. 62 | * 63 | * @param cell The starting form field cell. 64 | * @return The previous form field cell or nil if no cell is found. 65 | */ 66 | - (BZGTextFieldCell *)previousFormCell:(BZGTextFieldCell *)cell; 67 | 68 | 69 | /** 70 | * Scrolls to and makes a destination cell the first responder. 71 | * 72 | * @param destinationCell The destination cell. 73 | */ 74 | - (void)navigateToDestinationCell:(BZGTextFieldCell *)destinationCell; 75 | 76 | /** 77 | * Returns the first invalid form field cell. 78 | * 79 | * @return The first form field cell with state 'BZGValidationStateInvalid' or nil if no cell is found. 80 | */ 81 | - (BZGTextFieldCell *)firstInvalidFormCell; 82 | 83 | /** 84 | * Returns the first warning form field cell. 85 | * 86 | * @return The first form field cell with state 'BZGValidationStateWarning' or nil if no cell is found. 87 | */ 88 | - (BZGTextFieldCell *)firstWarningFormCell; 89 | 90 | /** 91 | * Returns a two-dimensional array of all of the cells, ordered by section and row. 92 | * 93 | * @return A two-dimensional array of all of the cells, ordered by section and row. 94 | */ 95 | - (NSArray *)allFormCells; 96 | 97 | /** 98 | * Returns an array of cells in the section, ordered by row 99 | * 100 | * @return An array of cells in the section, ordered by row 101 | */ 102 | - (NSArray *)formCellsInSection:(NSInteger)section; 103 | 104 | /** 105 | * Appends the form cell at the end of the section 106 | * 107 | * @param formCell A BZGFormCell 108 | * @param section A form section 109 | */ 110 | - (void)addFormCell:(BZGFormCell *)formCell atSection:(NSInteger)section; 111 | 112 | /** 113 | * Appends the form cells at the end of the section 114 | * 115 | * @param formCells An array of BZGFormCells 116 | * @param section A form section 117 | */ 118 | - (void)addFormCells:(NSArray *)formCells atSection:(NSInteger)section; 119 | 120 | /** 121 | * Inserts the form cells into the section, starting at the specified index path 122 | * 123 | * @param formCells An array of BZGFormCells 124 | * @param indexPath An index path representing the section and row for insertion 125 | */ 126 | - (void)insertFormCells:(NSArray *)formCells atIndexPath:(NSIndexPath *)indexPath; 127 | 128 | /** 129 | * Removes the form cell at the specified index path 130 | * 131 | * @param indexPath An index path representing the section and row for removal 132 | */ 133 | - (void)removeFormCellAtIndexPath:(NSIndexPath *)indexPath; 134 | 135 | /** 136 | * Removes all form cells in the specified section 137 | * 138 | * @param section A form section 139 | */ 140 | - (void)removeFormCellsInSection:(NSInteger)section; 141 | 142 | /** 143 | * Removes all form cells from all sections 144 | */ 145 | - (void)removeAllFormCells; 146 | 147 | /** 148 | * Returns an index path for the cell in the form 149 | * 150 | * @param cell A BZGFormCell that's in the form 151 | * @return An index path representing the row and section of the cell in the form 152 | */ 153 | - (NSIndexPath *)indexPathOfCell:(BZGFormCell *)cell; 154 | 155 | 156 | @end 157 | -------------------------------------------------------------------------------- /BZGFormViewController/BZGFormViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // BZGFormViewController.m 3 | // 4 | // https://github.com/benzguo/BZGFormViewController 5 | // 6 | 7 | #import "BZGFormViewController.h" 8 | 9 | #import "BZGFormCell.h" 10 | #import "BZGInfoCell.h" 11 | #import "BZGPhoneTextFieldCell.h" 12 | #import "BZGTextFieldCell.h" 13 | #import "BZGKeyboardControl.h" 14 | #import "Constants.h" 15 | 16 | @interface BZGFormViewController () 17 | 18 | @property (nonatomic, assign) UITableViewStyle style; 19 | @property (nonatomic, assign) BOOL isValid; 20 | @property (nonatomic, strong) BZGKeyboardControl *keyboardControl; 21 | @property (nonatomic, copy) void (^didEndScrollingBlock)(); 22 | @property (nonatomic, strong) NSMutableArray *formCellsBySection; 23 | @property (nonatomic, strong) NSArray *allFormCellsFlattened; 24 | 25 | @end 26 | 27 | @implementation BZGFormViewController { 28 | @protected 29 | UIEdgeInsets _preKeyboardTableViewInsets; 30 | } 31 | 32 | - (id)init 33 | { 34 | return [self initWithStyle:UITableViewStyleGrouped]; 35 | } 36 | 37 | - (id)initWithStyle:(UITableViewStyle)style 38 | { 39 | self = [super init]; 40 | if (self) { 41 | _formCellsBySection = [NSMutableArray array]; 42 | _style = style; 43 | _showsValidationCell = YES; 44 | } 45 | return self; 46 | } 47 | 48 | - (void)dealloc 49 | { 50 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 51 | } 52 | 53 | - (void)loadView 54 | { 55 | self.tableView = [[UITableView alloc] initWithFrame:CGRectZero style:self.style]; 56 | self.tableView.delegate = self; 57 | self.tableView.dataSource = self; 58 | self.tableView.autoresizesSubviews = YES; 59 | self.tableView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; 60 | if ([self.tableView respondsToSelector:@selector(setSeparatorInset:)]) { 61 | [self.tableView setSeparatorInset:UIEdgeInsetsZero]; 62 | } 63 | self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone; 64 | self.tableView.backgroundColor = BZG_TABLEVIEW_BACKGROUND_COLOR; 65 | 66 | UIView *contentView = [[UIView alloc] initWithFrame:CGRectZero]; 67 | contentView.autoresizesSubviews = YES; 68 | contentView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; 69 | [contentView addSubview:self.tableView]; 70 | 71 | self.view = contentView; 72 | 73 | [[NSNotificationCenter defaultCenter] addObserver:self 74 | selector:@selector(keyboardWillShow:) 75 | name:UIKeyboardWillShowNotification 76 | object:nil]; 77 | 78 | [[NSNotificationCenter defaultCenter] addObserver:self 79 | selector:@selector(keyboardWillHide:) 80 | name:UIKeyboardWillHideNotification 81 | object:nil]; 82 | } 83 | 84 | #pragma mark - Showing/hiding info cells 85 | 86 | - (BZGInfoCell *)infoCellBelowFormCell:(BZGTextFieldCell *)cell 87 | { 88 | NSIndexPath *cellIndexPath = [self indexPathOfCell:cell]; 89 | NSArray *formCellsInSection = [self formCellsInSection:cellIndexPath.section]; 90 | 91 | if (cellIndexPath == nil || cellIndexPath.row + 1 >= [formCellsInSection count]) { return nil; } 92 | 93 | UITableViewCell *cellBelow = formCellsInSection[cellIndexPath.row + 1]; 94 | if ([cellBelow isKindOfClass:[BZGInfoCell class]]) { 95 | return (BZGInfoCell *)cellBelow; 96 | } 97 | 98 | return nil; 99 | } 100 | 101 | - (void)showInfoCellBelowFormCell:(BZGTextFieldCell *)cell 102 | { 103 | NSIndexPath *cellIndexPath = [self indexPathOfCell:cell]; 104 | if (cellIndexPath == nil) { return; } 105 | 106 | // if an info cell is already showing, do nothing 107 | BZGInfoCell *infoCell = [self infoCellBelowFormCell:cell]; 108 | if (infoCell) { return; } 109 | 110 | // otherwise, add the cell's info cell to the table view 111 | NSIndexPath *infoCellIndexPath = [NSIndexPath indexPathForRow:cellIndexPath.row + 1 112 | inSection:cellIndexPath.section]; 113 | [self insertFormCells:[@[cell.infoCell] mutableCopy] atIndexPath:infoCellIndexPath]; 114 | [self.tableView insertRowsAtIndexPaths:@[infoCellIndexPath] 115 | withRowAnimation:UITableViewRowAnimationAutomatic]; 116 | } 117 | 118 | - (void)removeInfoCellBelowFormCell:(BZGTextFieldCell *)cell 119 | { 120 | NSIndexPath *cellIndexPath = [self indexPathOfCell:cell]; 121 | if (cellIndexPath == nil) { return; } 122 | 123 | // if no info cell is showing, do nothing 124 | BZGInfoCell *infoCell = [self infoCellBelowFormCell:cell]; 125 | if (!infoCell) return; 126 | 127 | // otherwise, remove it 128 | NSIndexPath *infoCellIndexPath = [NSIndexPath indexPathForRow:cellIndexPath.row + 1 129 | inSection:cellIndexPath.section]; 130 | [self removeFormCellAtIndexPath:infoCellIndexPath]; 131 | [self.tableView deleteRowsAtIndexPaths:@[infoCellIndexPath] withRowAnimation:UITableViewRowAnimationAutomatic]; 132 | } 133 | 134 | - (void)updateInfoCellBelowFormCell:(BZGTextFieldCell *)cell 135 | { 136 | if (self.showsValidationCell && 137 | !cell.textField.editing && 138 | [cell.infoCell.infoLabel.text length] && 139 | (cell.validationState == BZGValidationStateInvalid || 140 | cell.validationState == BZGValidationStateWarning)) { 141 | [self showInfoCellBelowFormCell:cell]; 142 | } else { 143 | [self removeInfoCellBelowFormCell:cell]; 144 | } 145 | } 146 | 147 | - (void)setShowsValidationCell:(BOOL)showsValidationCell 148 | { 149 | _showsValidationCell = showsValidationCell; 150 | for (BZGTextFieldCell *cell in [self allFormCellsFlattened]) { 151 | if ([cell isKindOfClass:[BZGTextFieldCell class]]) { 152 | [self updateInfoCellBelowFormCell:cell]; 153 | } 154 | } 155 | } 156 | 157 | #pragma mark - Finding cells 158 | 159 | - (BZGTextFieldCell *)firstFormCellWithValidationState:(BZGValidationState)validationState 160 | { 161 | for (UITableViewCell *cell in [self allFormCellsFlattened]) { 162 | if ([cell isKindOfClass:[BZGTextFieldCell class]]) { 163 | if (((BZGTextFieldCell *)cell).validationState == validationState) { 164 | return (BZGTextFieldCell *)cell; 165 | } 166 | } 167 | } 168 | return nil; 169 | } 170 | 171 | - (BZGTextFieldCell *)firstInvalidFormCell 172 | { 173 | return [self firstFormCellWithValidationState:BZGValidationStateInvalid]; 174 | } 175 | 176 | - (BZGTextFieldCell *)firstWarningFormCell 177 | { 178 | return [self firstFormCellWithValidationState:BZGValidationStateWarning]; 179 | } 180 | 181 | - (BZGTextFieldCell *)nextFormCell:(BZGTextFieldCell *)cell 182 | { 183 | NSIndexPath *cellIndexPath = [self indexPathOfCell:cell]; 184 | if (cellIndexPath == nil) { return nil; } 185 | 186 | for (NSInteger s = cellIndexPath.section; s < [self.formCellsBySection count]; s++) { 187 | NSArray* formCellsInSection = [self formCellsInSection:s]; 188 | 189 | NSInteger startRow = (s == cellIndexPath.section) ? cellIndexPath.row + 1 : 0; 190 | for (NSInteger r = startRow; r < [formCellsInSection count]; ++r) { 191 | UITableViewCell *cell = formCellsInSection[r]; 192 | if ([cell isKindOfClass:[BZGTextFieldCell class]]) { 193 | return (BZGTextFieldCell *)cell; 194 | } 195 | } 196 | } 197 | return nil; 198 | } 199 | 200 | - (BZGTextFieldCell *)previousFormCell:(BZGTextFieldCell *)cell 201 | { 202 | NSIndexPath *cellIndexPath = [self indexPathOfCell:cell]; 203 | if (cellIndexPath == nil) { return nil; } 204 | 205 | for (NSInteger s = cellIndexPath.section; s >= 0; s--) { 206 | NSArray* formCellsInSection = [self formCellsInSection:s]; 207 | 208 | NSInteger startRow = (s == cellIndexPath.section) ? cellIndexPath.row - 1 : [formCellsInSection count] - 1; 209 | for (NSInteger r = startRow; r >= 0; r--) { 210 | UITableViewCell *cell = formCellsInSection[r]; 211 | if ([cell isKindOfClass:[BZGTextFieldCell class]]) { 212 | return (BZGTextFieldCell *)cell; 213 | } 214 | } 215 | } 216 | return nil; 217 | } 218 | 219 | #pragma mark - Table view data source 220 | 221 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 222 | { 223 | NSArray *formCells = [self formCellsInSection:section]; 224 | return formCells ? [formCells count] : 0; 225 | } 226 | 227 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView 228 | { 229 | return [self.formCellsBySection count]; 230 | } 231 | 232 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 233 | { 234 | NSArray *formCells = [self formCellsInSection:indexPath.section]; 235 | 236 | if (formCells) { 237 | return [formCells objectAtIndex:indexPath.row]; 238 | } else { 239 | return nil; 240 | } 241 | } 242 | 243 | - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath 244 | { 245 | NSArray *formCells = [self formCellsInSection:indexPath.section]; 246 | 247 | if (formCells) { 248 | UITableViewCell *cell = [formCells objectAtIndex:indexPath.row]; 249 | return cell.frame.size.height; 250 | } 251 | return 0; 252 | } 253 | 254 | #pragma mark - UITextFieldDelegate 255 | 256 | - (void)textFieldDidBeginEditing:(UITextField *)textField 257 | { 258 | BZGTextFieldCell *cell = [BZGTextFieldCell parentCellForTextField:textField]; 259 | if (!cell) { 260 | return; 261 | } 262 | if (cell.didBeginEditingBlock) { 263 | cell.didBeginEditingBlock(cell, textField.text); 264 | } 265 | 266 | NSIndexPath *indexPath = [self.tableView indexPathForCell:cell]; 267 | [self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionMiddle animated:YES]; 268 | if (self.showsKeyboardControl) { 269 | [self accesorizeTextField:textField]; 270 | } 271 | } 272 | 273 | - (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string 274 | { 275 | BOOL shouldChange = YES; 276 | BZGTextFieldCell *cell = [BZGTextFieldCell parentCellForTextField:textField]; 277 | if (!cell) { 278 | return YES; 279 | } 280 | 281 | if ([cell isMemberOfClass:[BZGPhoneTextFieldCell class]]) { 282 | BZGPhoneTextFieldCell *phoneCell = (BZGPhoneTextFieldCell *)cell; 283 | BOOL shouldChange = [phoneCell shouldChangeCharactersInRange:range replacementString:string]; 284 | [self updateInfoCellBelowFormCell:phoneCell]; 285 | return shouldChange; 286 | } 287 | 288 | NSString *newText = [textField.text stringByReplacingCharactersInRange:range withString:string]; 289 | if (cell.shouldChangeTextBlock) { 290 | shouldChange = cell.shouldChangeTextBlock(cell, newText); 291 | } 292 | 293 | [self updateInfoCellBelowFormCell:cell]; 294 | return shouldChange; 295 | } 296 | 297 | - (void)textFieldDidEndEditing:(UITextField *)textField 298 | { 299 | BZGTextFieldCell *cell = [BZGTextFieldCell parentCellForTextField:textField]; 300 | if (!cell) { 301 | return; 302 | } 303 | if (cell.didEndEditingBlock) { 304 | cell.didEndEditingBlock(cell, textField.text); 305 | } 306 | 307 | [self updateInfoCellBelowFormCell:cell]; 308 | } 309 | 310 | - (BOOL)textFieldShouldReturn:(UITextField *)textField 311 | { 312 | BOOL shouldReturn = YES; 313 | BZGTextFieldCell *cell = [BZGTextFieldCell parentCellForTextField:textField]; 314 | if (!cell) { 315 | return YES; 316 | } 317 | 318 | if (cell.shouldReturnBlock) { 319 | shouldReturn = cell.shouldReturnBlock(cell, textField.text); 320 | } 321 | 322 | BZGTextFieldCell *nextCell = [self nextFormCell:cell]; 323 | if (!nextCell) { 324 | [cell resignFirstResponder]; 325 | } 326 | else { 327 | [nextCell becomeFirstResponder]; 328 | } 329 | 330 | [self updateInfoCellBelowFormCell:cell]; 331 | return shouldReturn; 332 | } 333 | 334 | #pragma mark - BZGFormCellDelegate 335 | 336 | - (void)formCell:(BZGFormCell *)formCell didChangeValidationState:(BZGValidationState)validationState 337 | { 338 | BOOL isValid = YES; 339 | for (BZGFormCell *cell in [self allFormCellsFlattened]) { 340 | if ([cell isKindOfClass:[BZGFormCell class]]) { 341 | isValid = isValid && 342 | (cell.validationState == BZGValidationStateValid || 343 | cell.validationState == BZGValidationStateWarning); 344 | } 345 | } 346 | self.isValid = isValid; 347 | } 348 | 349 | #pragma mark - Keyboard notifications 350 | 351 | - (void)keyboardWillShow:(NSNotification *)notification 352 | { 353 | CGSize keyboardSize = [[[notification userInfo] objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size; 354 | 355 | // Save the original contentInset so that it can be reset when the keyboard hides 356 | _preKeyboardTableViewInsets = self.tableView.contentInset; 357 | 358 | // Base the new inset off of the original 359 | UIEdgeInsets contentInsets = _preKeyboardTableViewInsets; 360 | 361 | if ((floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_7_1) 362 | || UIInterfaceOrientationIsPortrait([[UIApplication sharedApplication] statusBarOrientation])) { 363 | contentInsets.bottom += keyboardSize.height; 364 | } else { 365 | contentInsets.bottom += keyboardSize.width; 366 | } 367 | 368 | self.tableView.contentInset = contentInsets; 369 | self.tableView.scrollIndicatorInsets = contentInsets; 370 | } 371 | 372 | 373 | - (void)keyboardWillHide:(NSNotification *)notification 374 | { 375 | NSNumber *rate = notification.userInfo[UIKeyboardAnimationDurationUserInfoKey]; 376 | [UIView animateWithDuration:rate.floatValue animations:^{ 377 | self.tableView.contentInset = _preKeyboardTableViewInsets; 378 | self.tableView.scrollIndicatorInsets = _preKeyboardTableViewInsets; 379 | 380 | // Reset: These insets should only exist for the lifetime of the visible keyboard 381 | _preKeyboardTableViewInsets = UIEdgeInsetsZero; 382 | }]; 383 | } 384 | 385 | 386 | 387 | #pragma mark - BZGKeyboardControl Methods 388 | 389 | - (void)accesorizeTextField:(UITextField *)textField 390 | { 391 | BZGTextFieldCell *cell = [BZGTextFieldCell parentCellForTextField:textField]; 392 | self.keyboardControl.previousCell = [self previousFormCell:cell]; 393 | self.keyboardControl.currentCell = cell; 394 | self.keyboardControl.nextCell = [self nextFormCell:cell]; 395 | textField.inputAccessoryView = self.keyboardControl; 396 | } 397 | 398 | - (BZGKeyboardControl *)keyboardControl 399 | { 400 | if (!_keyboardControl) { 401 | _keyboardControl = [[BZGKeyboardControl alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(self.view.frame), BZG_KEYBOARD_CONTROL_HEIGHT)]; 402 | _keyboardControl.previousButton.target = self; 403 | _keyboardControl.previousButton.action = @selector(navigateToPreviousCell:); 404 | _keyboardControl.nextButton.target = self; 405 | _keyboardControl.nextButton.action = @selector(navigateToNextCell); 406 | _keyboardControl.doneButton.target = self; 407 | _keyboardControl.doneButton.action = @selector(doneButtonPressed); 408 | } 409 | return _keyboardControl; 410 | } 411 | 412 | - (void)navigateToPreviousCell: (id)sender 413 | { 414 | BZGTextFieldCell *previousCell = self.keyboardControl.previousCell; 415 | [self navigateToDestinationCell:previousCell]; 416 | } 417 | 418 | - (void)navigateToNextCell 419 | { 420 | BZGTextFieldCell *nextCell = self.keyboardControl.nextCell; 421 | [self navigateToDestinationCell:nextCell]; 422 | } 423 | 424 | - (void)navigateToDestinationCell:(BZGTextFieldCell *)destinationCell 425 | { 426 | if ([[self.tableView visibleCells] containsObject:destinationCell]) { 427 | [destinationCell becomeFirstResponder]; 428 | } 429 | else { 430 | NSIndexPath *cellIndexPath = [self indexPathOfCell:destinationCell]; 431 | self.didEndScrollingBlock = ^{ 432 | [destinationCell becomeFirstResponder]; 433 | }; 434 | [self.tableView scrollToRowAtIndexPath:cellIndexPath atScrollPosition:UITableViewScrollPositionMiddle animated:YES]; 435 | } 436 | } 437 | 438 | - (void)doneButtonPressed 439 | { 440 | [self.keyboardControl.currentCell resignFirstResponder]; 441 | } 442 | 443 | #pragma mark - UIScrollView Methods 444 | 445 | - (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView 446 | { 447 | if (scrollView == self.tableView) { 448 | if (self.didEndScrollingBlock) { 449 | self.didEndScrollingBlock(); 450 | self.didEndScrollingBlock = nil; 451 | } 452 | } 453 | } 454 | 455 | #pragma mark - formCells Methods 456 | 457 | - (void)setFormCells:(NSMutableArray *)formCells 458 | { 459 | [self removeAllFormCells]; 460 | [self addFormCells:formCells atSection:self.formSection]; 461 | } 462 | 463 | - (NSArray *)formCells 464 | { 465 | return [self formCellsInSection:self.formSection]; 466 | } 467 | 468 | - (NSArray *)allFormCells 469 | { 470 | return [self.formCellsBySection copy]; 471 | } 472 | 473 | - (NSArray *)allFormCellsFlattened 474 | { 475 | NSMutableArray *flattenedCellArray = [[NSMutableArray alloc] init]; 476 | for (NSArray *cellArray in self.allFormCells) { 477 | [flattenedCellArray addObjectsFromArray:cellArray]; 478 | } 479 | return flattenedCellArray; 480 | } 481 | 482 | - (void)prepareCell:(BZGFormCell *)cell 483 | { 484 | if ([cell isKindOfClass:[BZGFormCell class]]) { 485 | cell.delegate = self; 486 | 487 | if ([cell isKindOfClass:[BZGTextFieldCell class]]) { 488 | ((BZGTextFieldCell *)cell).textField.delegate = self; 489 | } 490 | } else if (![cell isKindOfClass:[BZGInfoCell class]]) { 491 | [NSException raise:NSInternalInconsistencyException 492 | format:@"BZGFormViewController only accepts cells that subclass BZGFormCell or BZGInfoCell"]; 493 | } 494 | } 495 | 496 | - (NSArray *)formCellsInSection:(NSInteger)section 497 | { 498 | return [[self mutableFormCellsInSection:section] copy]; 499 | } 500 | 501 | - (NSMutableArray *)mutableFormCellsInSection:(NSInteger)section 502 | { 503 | if ([self.formCellsBySection count] > section) { 504 | return [self.formCellsBySection objectAtIndex:section]; 505 | } else { 506 | return [NSMutableArray array]; 507 | } 508 | } 509 | 510 | - (void)addFormCell:(BZGFormCell *)formCell atSection:(NSInteger)section 511 | { 512 | [self addFormCells:@[formCell] atSection:section]; 513 | } 514 | 515 | - (void)addFormCells:(NSArray *)formCells atSection:(NSInteger)section 516 | { 517 | NSInteger formCellCount = [[self formCellsInSection:section] count]; 518 | [self insertFormCells:formCells atIndexPath:[NSIndexPath indexPathForRow:formCellCount inSection:section]]; 519 | } 520 | 521 | - (void)insertFormCells:(NSArray *)formCells atIndexPath:(NSIndexPath *)indexPath 522 | { 523 | self.allFormCellsFlattened = nil; 524 | for (BZGFormCell *cell in formCells) { 525 | [self prepareCell:cell]; 526 | } 527 | 528 | while (indexPath.section + 1 > [self.formCellsBySection count]) { 529 | [self.formCellsBySection addObject:[NSMutableArray array]]; 530 | } 531 | 532 | NSIndexSet *indexSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(indexPath.row, [formCells count])]; 533 | [[self.formCellsBySection objectAtIndex:indexPath.section] insertObjects:formCells atIndexes:indexSet]; 534 | } 535 | 536 | - (void)removeFormCellAtIndexPath:(NSIndexPath *)indexPath 537 | { 538 | self.allFormCellsFlattened = nil; 539 | NSMutableArray *formCells = [self mutableFormCellsInSection:indexPath.section]; 540 | if (formCells) { 541 | [formCells removeObjectAtIndex:indexPath.row]; 542 | } 543 | } 544 | 545 | - (void)removeFormCellsInSection:(NSInteger)section 546 | { 547 | self.allFormCellsFlattened = nil; 548 | if ([self.formCellsBySection count] > section) { 549 | self.formCellsBySection[section] = [NSMutableArray array]; 550 | } 551 | } 552 | 553 | - (void)removeAllFormCells 554 | { 555 | self.allFormCellsFlattened = nil; 556 | self.formCellsBySection = [NSMutableArray array]; 557 | } 558 | 559 | - (NSIndexPath *)indexPathOfCell:(BZGFormCell *)cell 560 | { 561 | for (NSArray *section in self.formCellsBySection) { 562 | for (BZGFormCell *sectionCell in section) { 563 | if (cell == sectionCell) { 564 | return [NSIndexPath indexPathForRow:[section indexOfObject:cell] 565 | inSection:[self.formCellsBySection indexOfObject:section]]; 566 | } 567 | } 568 | } 569 | return nil; 570 | } 571 | 572 | @end 573 | -------------------------------------------------------------------------------- /BZGFormViewController/BZGInfoCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // BZGInfoCell.h 3 | // 4 | // https://github.com/benzguo/BZGFormViewController 5 | // 6 | 7 | #import 8 | 9 | @interface BZGInfoCell : UITableViewCell 10 | 11 | /// The cell's label. 12 | @property (nonatomic, strong) UILabel *infoLabel; 13 | 14 | /** 15 | * Initializes a BZGInfoCell with the given text. 16 | */ 17 | - (id)initWithText:(NSString *)text; 18 | 19 | /** 20 | * Sets the cell's info text, resizing the cell and label as necessary. 21 | */ 22 | - (void)setText:(NSString *)text; 23 | 24 | /** 25 | * Sets the block executed when the cell is tapped. 26 | */ 27 | - (void)setTapGestureBlock:(void(^)())block; 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /BZGFormViewController/BZGInfoCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // BZGInfoCell.m 3 | // 4 | // https://github.com/benzguo/BZGFormViewController 5 | // 6 | 7 | #import "BZGInfoCell.h" 8 | #import "Constants.h" 9 | 10 | @interface BZGInfoCell () { 11 | void (^_tapGestureBlock) (); 12 | } 13 | 14 | @property (strong, nonatomic) UITapGestureRecognizer *tapGestureRecognizer; 15 | 16 | @end 17 | 18 | @implementation BZGInfoCell 19 | 20 | - (id)init 21 | { 22 | self = [super init]; 23 | if (self) { 24 | [self setup]; 25 | } 26 | return self; 27 | } 28 | 29 | - (id)initWithText:(NSString *)text 30 | { 31 | self = [super initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil]; 32 | if (self) { 33 | [self setup]; 34 | self.infoLabel.text = text; 35 | [self updateSize]; 36 | } 37 | return self; 38 | } 39 | 40 | - (void)setText:(NSString *)text 41 | { 42 | self.infoLabel.text = text; 43 | [self updateSize]; 44 | } 45 | 46 | - (void)setTapGestureBlock:(void(^)())block 47 | { 48 | _tapGestureBlock = block; 49 | } 50 | 51 | - (void)setup 52 | { 53 | // Hide default components 54 | self.textLabel.hidden = YES; 55 | self.detailTextLabel.hidden = YES; 56 | self.imageView.hidden = YES; 57 | 58 | self.selectionStyle = UITableViewCellSelectionStyleNone; 59 | self.backgroundColor = BZG_INFO_BACKGROUND_COLOR; 60 | 61 | self.infoLabel = [[UILabel alloc] initWithFrame:self.bounds]; 62 | self.infoLabel.font = BZG_INFO_LABEL_FONT; 63 | self.infoLabel.adjustsFontSizeToFitWidth = NO; 64 | self.infoLabel.numberOfLines = 0; 65 | self.infoLabel.textAlignment = NSTextAlignmentCenter; 66 | self.infoLabel.backgroundColor = [UIColor clearColor]; 67 | self.infoLabel.text = @""; 68 | self.infoLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth; 69 | 70 | self.tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapGestureAction)]; 71 | [self addGestureRecognizer:self.tapGestureRecognizer]; 72 | } 73 | 74 | - (void)tapGestureAction 75 | { 76 | if (_tapGestureBlock) { 77 | _tapGestureBlock(); 78 | } 79 | } 80 | 81 | - (void)updateSize 82 | { 83 | CGFloat verticalPadding = 10; 84 | CGSize currentSize = self.infoLabel.frame.size; 85 | CGFloat currentHeight = currentSize.height; 86 | CGFloat heightThatFits = [self.infoLabel sizeThatFits:currentSize].height; 87 | CGFloat adjustedHeightThatFits = heightThatFits + verticalPadding*2; 88 | CGFloat newHeight = currentHeight; 89 | CGFloat yPadding = 0; 90 | if (adjustedHeightThatFits > currentHeight) { 91 | newHeight = adjustedHeightThatFits; 92 | yPadding = 0; 93 | } 94 | [self.infoLabel setFrame:CGRectMake(0, 95 | yPadding, 96 | self.frame.size.width, 97 | newHeight)]; 98 | [self addSubview:self.infoLabel]; 99 | [self setFrame:CGRectMake(self.frame.origin.x, 100 | self.frame.origin.y, 101 | self.frame.size.width, 102 | newHeight)]; 103 | } 104 | 105 | @end 106 | -------------------------------------------------------------------------------- /BZGFormViewController/BZGKeyboardControl.h: -------------------------------------------------------------------------------- 1 | // 2 | // BZGKeyboardControl.h 3 | // 4 | // https://github.com/benzguo/BZGFormViewController 5 | // 6 | 7 | #import 8 | 9 | @class BZGTextFieldCell; 10 | 11 | @interface BZGKeyboardControl : UIView 12 | 13 | @property (nonatomic, strong) UIBarButtonItem *previousButton; 14 | @property (nonatomic, strong) UIBarButtonItem *nextButton; 15 | @property (nonatomic, strong) UIBarButtonItem *doneButton; 16 | @property (nonatomic, strong) BZGTextFieldCell *previousCell; 17 | @property (nonatomic, strong) BZGTextFieldCell *currentCell; 18 | @property (nonatomic, strong) BZGTextFieldCell *nextCell; 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /BZGFormViewController/BZGKeyboardControl.m: -------------------------------------------------------------------------------- 1 | // 2 | // BZGKeyboardControl.m 3 | // 4 | // https://github.com/benzguo/BZGFormViewController 5 | // 6 | 7 | #import "BZGKeyboardControl.h" 8 | 9 | const CGFloat BZGKeyboardControlButtonSpacing = 22; 10 | 11 | @implementation BZGKeyboardControl 12 | 13 | - (id)initWithFrame:(CGRect)frame { 14 | self = [super initWithFrame:frame]; 15 | if (self) { 16 | self.previousButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:105 17 | target:nil 18 | action:nil]; 19 | self.nextButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:106 20 | target:nil 21 | action:nil]; 22 | 23 | self.doneButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone 24 | target:nil 25 | action:nil]; 26 | 27 | self.previousButton.enabled = NO; 28 | self.nextButton.enabled = NO; 29 | self.previousButton.tintColor = [UIColor blackColor]; 30 | self.nextButton.tintColor = [UIColor blackColor]; 31 | self.doneButton.tintColor = [UIColor blackColor]; 32 | 33 | UIBarButtonItem *buttonSpacing = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace 34 | target:self 35 | action:nil]; 36 | buttonSpacing.width = BZGKeyboardControlButtonSpacing; 37 | UIBarButtonItem *flexibleSpace = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace 38 | target:self 39 | action:nil]; 40 | UIToolbar *toolbar = [[UIToolbar alloc] initWithFrame:self.frame]; 41 | toolbar.autoresizingMask = UIViewAutoresizingFlexibleWidth; 42 | [toolbar setItems:@[self.previousButton, buttonSpacing, self.nextButton, flexibleSpace, self.doneButton]]; 43 | [self addSubview:toolbar]; 44 | } 45 | return self; 46 | } 47 | 48 | 49 | - (void)setPreviousCell:(BZGTextFieldCell *)previousCell { 50 | _previousCell = previousCell; 51 | self.previousButton.enabled = !!previousCell; 52 | } 53 | 54 | 55 | - (void)setNextCell:(BZGTextFieldCell *)nextCell { 56 | _nextCell = nextCell; 57 | self.nextButton.enabled = !!nextCell; 58 | } 59 | 60 | @end 61 | -------------------------------------------------------------------------------- /BZGFormViewController/BZGPhoneTextFieldCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // BZGPhoneTextFieldCell.h 3 | // 4 | // https://github.com/benzguo/BZGFormViewController 5 | // 6 | 7 | #import "BZGTextFieldCell.h" 8 | #import 9 | 10 | @interface BZGPhoneTextFieldCell : BZGTextFieldCell 11 | 12 | /// The text to display in the info cell when the phone number is invalid 13 | @property (strong, nonatomic) NSString *invalidText; 14 | 15 | - (BOOL)shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /BZGFormViewController/BZGPhoneTextFieldCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // BZGPhoneTextFieldCell.m 3 | // 4 | // https://github.com/benzguo/BZGFormViewController 5 | // 6 | 7 | #import "BZGPhoneTextFieldCell.h" 8 | #import "NSError+BZGFormViewController.h" 9 | 10 | #import 11 | #import 12 | 13 | @interface BZGPhoneTextFieldCell () 14 | 15 | // Phone number formatting 16 | @property (strong, nonatomic) NBAsYouTypeFormatter *phoneFormatter; 17 | @property (strong, nonatomic) NSString *regionCode; 18 | @property (strong, nonatomic) NBPhoneNumberUtil *phoneUtil; 19 | 20 | @end 21 | 22 | @implementation BZGPhoneTextFieldCell 23 | 24 | - (id)init 25 | { 26 | self = [super init]; 27 | if (self) { 28 | NSLocale *locale = [NSLocale currentLocale]; 29 | self.regionCode = [[locale localeIdentifier] substringFromIndex:3]; 30 | self.phoneFormatter = [[NBAsYouTypeFormatter alloc] initWithRegionCode:self.regionCode]; 31 | self.phoneUtil = [NBPhoneNumberUtil sharedInstance]; 32 | self.invalidText = @"Please enter a valid phone number"; 33 | self.textField.keyboardType = UIKeyboardTypePhonePad; 34 | } 35 | return self; 36 | } 37 | 38 | -(void)addGestureRecognizer:(UIGestureRecognizer *)gestureRecognizer 39 | { 40 | // Disable long press 41 | if ([gestureRecognizer isKindOfClass:[UILongPressGestureRecognizer class]]) 42 | { 43 | gestureRecognizer.enabled = NO; 44 | } 45 | [super addGestureRecognizer:gestureRecognizer]; 46 | return; 47 | } 48 | 49 | - (BOOL)shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string 50 | { 51 | NSCharacterSet *digitSet = [NSCharacterSet characterSetWithCharactersInString:@"0123456789"]; 52 | // Entered one number 53 | if (string.length == 1 && 54 | [string rangeOfCharacterFromSet:digitSet].length != 0 && 55 | range.location == self.textField.text.length) { 56 | self.textField.text = [self.phoneFormatter inputDigit:string]; 57 | } 58 | // Backspace 59 | else if (string.length == 0) { 60 | self.textField.text = [self.phoneFormatter removeLastDigit]; 61 | } 62 | // Validate text 63 | NSError *error = nil; 64 | NBPhoneNumber *phoneNumber = [self.phoneUtil parse:self.textField.text 65 | defaultRegion:self.regionCode 66 | error:&error]; 67 | if (!error) { 68 | BOOL isPossibleNumber = NO; 69 | if ([self.regionCode isEqualToString:@"US"]) { 70 | // Don't allow 7-digit local format 71 | NSCharacterSet *nonDigitCharacterSet = [[NSCharacterSet characterSetWithCharactersInString:@"1234567890"] invertedSet]; 72 | NSString *strippedPhoneString = [[self.textField.text componentsSeparatedByCharactersInSet:nonDigitCharacterSet] componentsJoinedByString:@""]; 73 | isPossibleNumber = [self.phoneUtil isPossibleNumber:phoneNumber error:&error] && strippedPhoneString.length >= 10; 74 | } 75 | else { 76 | isPossibleNumber = [self.phoneUtil isPossibleNumber:phoneNumber error:&error]; 77 | } 78 | if (error || !isPossibleNumber) { 79 | self.validationState = BZGValidationStateInvalid; 80 | self.validationError = [NSError bzg_errorWithDescription:self.invalidText]; 81 | } else { 82 | self.validationState = BZGValidationStateValid; 83 | self.validationError = nil; 84 | } 85 | } 86 | else { 87 | self.validationState = BZGValidationStateInvalid; 88 | self.validationError = [NSError bzg_errorWithDescription:self.invalidText]; 89 | } 90 | 91 | return NO; 92 | } 93 | 94 | @end 95 | -------------------------------------------------------------------------------- /BZGFormViewController/BZGTextFieldCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // BZGTextFieldCell.h 3 | // 4 | // https://github.com/benzguo/BZGFormViewController 5 | // 6 | 7 | #import 8 | #import "BZGFormCell.h" 9 | 10 | @interface BZGTextFieldCell : BZGFormCell 11 | 12 | @property (strong, nonatomic) UILabel *label; 13 | @property (strong, nonatomic) UITextField *textField; 14 | 15 | /// The color of the text field's text when the cell's state is not invalid. 16 | @property (strong, nonatomic) UIColor *textFieldNormalColor; 17 | 18 | /// The color of the text field's text when the cell's state is invalid. 19 | @property (strong, nonatomic) UIColor *textFieldInvalidColor; 20 | 21 | @property (strong, nonatomic) UIActivityIndicatorView *activityIndicatorView; 22 | 23 | /// A value indicating whether or not the cell shows a checkmark when valid. Default is YES. 24 | @property (assign, nonatomic) BOOL showsCheckmarkWhenValid; 25 | 26 | /// A value indicating whether or not the cell displays its validation state while being edited. Default is NO. 27 | @property (assign, nonatomic) BOOL showsValidationWhileEditing; 28 | 29 | /// The block called when the text field's text begins editing. 30 | @property (copy, nonatomic) void (^didBeginEditingBlock)(BZGTextFieldCell *cell, NSString *text); 31 | 32 | /** 33 | * The block called before the text field's text changes. 34 | * The block's newText parameter will be the text field's text after changing. Return NO if the text shouldn't change. 35 | */ 36 | @property (copy, nonatomic) BOOL (^shouldChangeTextBlock)(BZGTextFieldCell *cell, NSString *newText); 37 | 38 | /// The block called when the text field's text ends editing. 39 | @property (copy, nonatomic) void (^didEndEditingBlock)(BZGTextFieldCell *cell, NSString *text); 40 | 41 | /// The block called before the text field returns. Return NO if the text field shouldn't return. 42 | @property (copy, nonatomic) BOOL (^shouldReturnBlock)(BZGTextFieldCell *cell, NSString *text); 43 | 44 | /** 45 | * Returns the parent BZGTextFieldCell for the given text field. If no cell is found, returns nil. 46 | * 47 | * @param textField A UITextField instance that may or may not belong to this BZGTextFieldCell instance. 48 | */ 49 | + (BZGTextFieldCell *)parentCellForTextField:(UITextField *)textField; 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /BZGFormViewController/BZGTextFieldCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // BZGTextFieldCell.m 3 | // 4 | // https://github.com/benzguo/BZGFormViewController 5 | // 6 | 7 | #import 8 | #import 9 | 10 | #import "BZGTextFieldCell.h" 11 | #import "BZGInfoCell.h" 12 | #import "Constants.h" 13 | 14 | @implementation BZGTextFieldCell 15 | 16 | - (id)init 17 | { 18 | self = [super init]; 19 | if (self) { 20 | self.showsCheckmarkWhenValid = YES; 21 | self.showsValidationWhileEditing = NO; 22 | self.infoCell = [[BZGInfoCell alloc] init]; 23 | 24 | [self configureActivityIndicatorView]; 25 | [self configureTextField]; 26 | [self configureLabel]; 27 | [self configureTap]; 28 | [self configureBindings]; 29 | 30 | [[NSNotificationCenter defaultCenter] addObserver:self 31 | selector:@selector(textFieldTextDidEndEditing:) 32 | name:UITextFieldTextDidEndEditingNotification 33 | object:nil]; 34 | [[NSNotificationCenter defaultCenter] addObserver:self 35 | selector:@selector(textFieldTextDidChange:) 36 | name:UITextFieldTextDidChangeNotification 37 | object:nil]; 38 | } 39 | return self; 40 | } 41 | 42 | - (void)dealloc 43 | { 44 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 45 | } 46 | 47 | - (void)configureTextField 48 | { 49 | CGFloat textFieldX = self.bounds.size.width * 0.35; 50 | CGFloat textFieldY = 0; 51 | if ([[[UIDevice currentDevice] systemVersion] floatValue] < 7.0) { 52 | textFieldY = 12; 53 | } 54 | self.textField.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter; 55 | CGRect textFieldFrame = CGRectMake(textFieldX, 56 | textFieldY, 57 | self.bounds.size.width - textFieldX - self.activityIndicatorView.frame.size.width, 58 | self.bounds.size.height); 59 | self.textField = [[UITextField alloc] initWithFrame:textFieldFrame]; 60 | self.textField.autoresizingMask = UIViewAutoresizingFlexibleWidth; 61 | self.textField.autocorrectionType = UITextAutocorrectionTypeNo; 62 | self.textField.autocapitalizationType = UITextAutocapitalizationTypeNone; 63 | self.textFieldNormalColor = BZG_TEXTFIELD_NORMAL_COLOR; 64 | self.textFieldInvalidColor = BZG_TEXTFIELD_INVALID_COLOR; 65 | self.textField.font = BZG_TEXTFIELD_FONT; 66 | self.textField.backgroundColor = [UIColor clearColor]; 67 | [self addSubview:self.textField]; 68 | } 69 | 70 | - (void)configureLabel 71 | { 72 | CGFloat labelX = 10; 73 | if ([[[UIDevice currentDevice] systemVersion] floatValue] < 7.0) { 74 | labelX = 15; 75 | } 76 | CGRect labelFrame = CGRectMake(labelX, 77 | 0, 78 | self.textField.frame.origin.x - labelX, 79 | self.bounds.size.height); 80 | self.label = [[UILabel alloc] initWithFrame:labelFrame]; 81 | self.label.font = BZG_TEXTFIELD_LABEL_FONT; 82 | self.label.textColor = BZG_TEXTFIELD_LABEL_COLOR; 83 | self.label.backgroundColor = [UIColor clearColor]; 84 | [self addSubview:self.label]; 85 | } 86 | 87 | - (void)configureActivityIndicatorView 88 | { 89 | CGFloat activityIndicatorWidth = self.bounds.size.height*0.7; 90 | CGRect activityIndicatorFrame = CGRectMake(self.bounds.size.width - activityIndicatorWidth, 91 | 0, 92 | activityIndicatorWidth, 93 | self.bounds.size.height); 94 | self.activityIndicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray]; 95 | [self.activityIndicatorView setFrame:activityIndicatorFrame]; 96 | self.activityIndicatorView.hidesWhenStopped = NO; 97 | self.activityIndicatorView.hidden = YES; 98 | [self addSubview:self.activityIndicatorView]; 99 | } 100 | 101 | - (void)configureTap { 102 | UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(becomeFirstResponder)]; 103 | [self.contentView addGestureRecognizer:tap]; 104 | } 105 | 106 | - (void)configureBindings 107 | { 108 | @weakify(self); 109 | 110 | RAC(self.textField, textColor) = 111 | [RACObserve(self, validationState) map:^UIColor *(NSNumber *validationState) { 112 | @strongify(self); 113 | if (self.textField.editing && 114 | !self.showsValidationWhileEditing) { 115 | return self.textFieldNormalColor; 116 | } 117 | switch (validationState.integerValue) { 118 | case BZGValidationStateInvalid: 119 | return self.textFieldInvalidColor; 120 | break; 121 | case BZGValidationStateValid: 122 | case BZGValidationStateValidating: 123 | case BZGValidationStateWarning: 124 | case BZGValidationStateNone: 125 | default: 126 | return self.textFieldNormalColor; 127 | break; 128 | } 129 | }]; 130 | 131 | RAC(self.activityIndicatorView, hidden) = 132 | [RACObserve(self, validationState) map:^NSNumber *(NSNumber *validationState) { 133 | @strongify(self); 134 | if (validationState.integerValue == BZGValidationStateValidating) { 135 | [self.activityIndicatorView startAnimating]; 136 | return @NO; 137 | } else { 138 | [self.activityIndicatorView stopAnimating]; 139 | return @YES; 140 | } 141 | }]; 142 | 143 | RAC(self, accessoryType) = 144 | [RACObserve(self, validationState) map:^NSNumber *(NSNumber *validationState) { 145 | @strongify(self); 146 | if (validationState.integerValue == BZGValidationStateValid && 147 | (!self.textField.editing || self.showsValidationWhileEditing) && 148 | self.showsCheckmarkWhenValid) { 149 | return @(UITableViewCellAccessoryCheckmark); 150 | } else { 151 | return @(UITableViewCellAccessoryNone); 152 | } 153 | }]; 154 | } 155 | 156 | + (BZGTextFieldCell *)parentCellForTextField:(UITextField *)textField 157 | { 158 | UIView *view = textField; 159 | while ((view = view.superview)) { 160 | if ([view isKindOfClass:[BZGTextFieldCell class]]) break; 161 | } 162 | return (BZGTextFieldCell *)view; 163 | } 164 | 165 | - (void)setShowsCheckmarkWhenValid:(BOOL)showsCheckmarkWhenValid 166 | { 167 | _showsCheckmarkWhenValid = showsCheckmarkWhenValid; 168 | // Force RACObserve to trigger 169 | self.validationState = self.validationState; 170 | } 171 | 172 | #pragma mark - UITextField notification selectors 173 | // I'm using these notifications to flush the validation state signal. 174 | // It works, but seems hacky. Is there a better way? 175 | 176 | - (void)textFieldTextDidChange:(NSNotification *)notification 177 | { 178 | UITextField *textField = (UITextField *)notification.object; 179 | if ([textField isEqual:self.textField]) { 180 | self.validationState = self.validationState; 181 | 182 | // Secure text fields clear on begin editing on iOS6+. 183 | // If it seems like the text field has been cleared, 184 | // invoke the text change delegate method again to ensure proper validation. 185 | if (textField.secureTextEntry && textField.text.length <= 1) { 186 | [self.textField.delegate textField:self.textField 187 | shouldChangeCharactersInRange:NSMakeRange(0, textField.text.length) 188 | replacementString:textField.text]; 189 | } 190 | } 191 | } 192 | 193 | - (void)textFieldTextDidEndEditing:(NSNotification *)notification 194 | { 195 | UITextField *textField = (UITextField *)notification.object; 196 | if ([textField isEqual:self.textField]) { 197 | self.validationState = self.validationState; 198 | } 199 | } 200 | 201 | - (BOOL)becomeFirstResponder 202 | { 203 | return [self.textField becomeFirstResponder]; 204 | } 205 | 206 | - (BOOL)resignFirstResponder 207 | { 208 | [super resignFirstResponder]; 209 | return [self.textField resignFirstResponder]; 210 | } 211 | 212 | @end 213 | -------------------------------------------------------------------------------- /BZGFormViewController/Constants.h: -------------------------------------------------------------------------------- 1 | #ifndef BZGFormViewController_Constants_h 2 | #define BZGFormViewController_Constants_h 3 | 4 | #define BZG_BLACK_COLOR [UIColor colorWithRed:38/255.0f green:39/255.0f blue:41/255.0f alpha:1.0f] 5 | #define BZG_RED_COLOR [UIColor colorWithRed:233/255.0f green:26/255.0f blue:26/255.0f alpha:1.0f] 6 | #define BZG_BLUE_COLOR [UIColor colorWithRed:19/255.0 green:144/255.0 blue:255/255.0 alpha:1.0] 7 | #define BZG_LIGHT_GRAY_COLOR [UIColor colorWithRed:231/255.0f green:235/255.0f blue:238/255.0f alpha:1.0f] 8 | #define BZG_TABLEVIEW_BACKGROUND_COLOR [UIColor colorWithRed:235/255.0f green:235/255.0f blue:241/255.0f alpha:1.0f] 9 | #define BZG_STANDARD_FONT [UIFont fontWithName:@"HelveticaNeue" size:15] 10 | #define BZG_SMALL_FONT [UIFont fontWithName:@"HelveticaNeue" size:14] 11 | #define BZG_BOLD_FONT [UIFont fontWithName:@"HelveticaNeue-Medium" size:15] 12 | 13 | #define BZG_BACKGROUND_COLOR [UIColor whiteColor] 14 | 15 | #define BZG_TEXTFIELD_LABEL_COLOR BZG_BLACK_COLOR 16 | #define BZG_TEXTFIELD_LABEL_FONT BZG_BOLD_FONT 17 | #define BZG_TEXTFIELD_NORMAL_COLOR BZG_BLACK_COLOR 18 | #define BZG_TEXTFIELD_INVALID_COLOR BZG_RED_COLOR 19 | #define BZG_TEXTFIELD_FONT BZG_STANDARD_FONT 20 | 21 | #define BZG_INFO_LABEL_COLOR BZG_BLACK_COLOR 22 | #define BZG_INFO_LABEL_FONT BZG_SMALL_FONT 23 | #define BZG_INFO_BACKGROUND_COLOR BZG_TABLEVIEW_BACKGROUND_COLOR 24 | 25 | #define BZG_KEYBOARD_CONTROL_HEIGHT 44 26 | 27 | #endif 28 | -------------------------------------------------------------------------------- /BZGFormViewController/NSError+BZGFormViewController.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface NSError (BZGFormViewController) 4 | 5 | + (NSError *)bzg_errorWithDescription:(NSString *)description; 6 | 7 | @end 8 | -------------------------------------------------------------------------------- /BZGFormViewController/NSError+BZGFormViewController.m: -------------------------------------------------------------------------------- 1 | #import "NSError+BZGFormViewController.h" 2 | 3 | @implementation NSError (BZGFormViewController) 4 | 5 | + (NSError *)bzg_errorWithDescription:(NSString *)description 6 | { 7 | NSError *error = [NSError errorWithDomain:@"BZGFormViewController" code:1337 userInfo:@{NSLocalizedDescriptionKey: description ?: @""}]; 8 | return error; 9 | } 10 | 11 | @end 12 | -------------------------------------------------------------------------------- /BZGFormViewControllerTests/BZGFormCellSpecs.m: -------------------------------------------------------------------------------- 1 | // 2 | // BZGFormCellSpecs.m 3 | // 4 | // https://github.com/benzguo/BZGFormViewController 5 | // 6 | 7 | #import "BZGFormCell.h" 8 | 9 | BZGFormCell *cell; 10 | 11 | SpecBegin(BZGFormCell) 12 | 13 | before(^{ 14 | cell = [[BZGFormCell alloc] init]; 15 | }); 16 | 17 | after(^{ 18 | cell = nil; 19 | }); 20 | 21 | describe(@"Initialization", ^{ 22 | it(@"should initialize with correct defaults", ^{ 23 | expect(cell.validationState).to.equal(BZGValidationStateNone); 24 | expect(cell.delegate).to.equal(nil); 25 | }); 26 | }); 27 | 28 | describe(@"validationState", ^{ 29 | it(@"should invoke formCell:didChangeValidationState: with the correct arguments when the cell's validation state changes to valid", ^{ 30 | id mockDelegate = mockProtocol(@protocol(BZGFormCellDelegate)); 31 | cell.delegate = mockDelegate; 32 | cell.validationState = BZGValidationStateValid; 33 | [verify(mockDelegate) formCell:cell didChangeValidationState:BZGValidationStateValid]; 34 | }); 35 | 36 | it(@"should invoke formCell:didChangeValidationState: with the correct arguments when the cell's validation state changes to invalid", ^{ 37 | id mockDelegate = mockProtocol(@protocol(BZGFormCellDelegate)); 38 | cell.delegate = mockDelegate; 39 | cell.validationState = BZGValidationStateInvalid; 40 | [verify(mockDelegate) formCell:cell didChangeValidationState:BZGValidationStateInvalid]; 41 | }); 42 | }); 43 | 44 | SpecEnd -------------------------------------------------------------------------------- /BZGFormViewControllerTests/BZGFormViewControllerSpecs.m: -------------------------------------------------------------------------------- 1 | // 2 | // BZGFormViewControllerSpecs.m 3 | // 4 | // https://github.com/benzguo/BZGFormViewController 5 | // 6 | 7 | #import "BZGFormViewController.h" 8 | #import "BZGTextFieldCell.h" 9 | 10 | @interface BZGFormViewController () 11 | 12 | - (NSArray *)allFormCells; 13 | 14 | @property (nonatomic, strong) NSMutableArray *formCellsBySection; 15 | @property (nonatomic, strong) NSArray *allFormCellsFlattened; 16 | 17 | @end 18 | 19 | UIWindow *window; 20 | BZGFormViewController *formViewController; 21 | BZGTextFieldCell *cell1; 22 | BZGTextFieldCell *cell2; 23 | BZGTextFieldCell *cell3; 24 | 25 | SpecBegin(BZGFormViewController) 26 | 27 | beforeEach(^{ 28 | window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; 29 | formViewController = [OCMockObject partialMockForObject:[[BZGFormViewController alloc] initWithStyle:UITableViewStyleGrouped]]; 30 | 31 | cell1 = [OCMockObject partialMockForObject:[[BZGTextFieldCell alloc] init]]; 32 | cell2 = [OCMockObject partialMockForObject:[[BZGTextFieldCell alloc] init]]; 33 | cell3 = [OCMockObject partialMockForObject:[[BZGTextFieldCell alloc] init]]; 34 | [[(id)cell1 stub] resignFirstResponder]; 35 | [[(id)cell1 stub] becomeFirstResponder]; 36 | [[(id)cell2 stub] resignFirstResponder]; 37 | [[(id)cell2 stub] becomeFirstResponder]; 38 | [[(id)cell3 stub] resignFirstResponder]; 39 | [[(id)cell3 stub] becomeFirstResponder]; 40 | 41 | [formViewController addFormCells:@[cell1, cell2] atSection:1]; 42 | [formViewController addFormCells:@[cell3] atSection:2]; 43 | [formViewController.tableView reloadData]; 44 | window.rootViewController = formViewController; 45 | [window makeKeyAndVisible]; 46 | }); 47 | 48 | afterEach(^{ 49 | formViewController = nil; 50 | }); 51 | 52 | 53 | // Remove this one formSections and formCells 54 | #pragma clang diagnostic push 55 | #pragma clang diagnostic ignored "-Wdeprecated-declarations" 56 | 57 | describe(@"Initialization", ^{ 58 | it(@"should initialize with no form cell sections", ^{ 59 | formViewController = [[BZGFormViewController alloc] initWithStyle:UITableViewStyleGrouped]; 60 | expect(formViewController.formCells).to.haveCountOf(0); 61 | expect(formViewController.formSection).to.equal(0); 62 | expect([[formViewController allFormCells] count]).to.equal(0); 63 | }); 64 | }); 65 | 66 | describe(@"Setting form cells", ^{ 67 | context(@"deprecated formSection, formCells methods", ^{ 68 | it(@"should use formSection to put the formCells into the two-dimensional formCellsBySection array", ^{ 69 | NSMutableArray *formCells = [NSMutableArray arrayWithArray:@[cell1, cell2, cell3]]; 70 | formViewController.formSection = 2; 71 | formViewController.formCells = formCells; 72 | 73 | expect((formViewController.formCells)).to.equal(formCells); 74 | expect(([formViewController allFormCells])).to.equal(@[@[], @[], formCells]); 75 | 76 | expect(([formViewController formCellsInSection:0])).to.haveCountOf(0); 77 | expect(([formViewController formCellsInSection:1])).to.haveCountOf(0); 78 | expect(([formViewController formCellsInSection:2])).to.equal(formCells); 79 | expect(([formViewController formCellsInSection:3])).to.haveCountOf(0); 80 | 81 | formViewController.formSection = 1; 82 | formViewController.formCells = formCells; 83 | 84 | expect((formViewController.formCells)).to.equal(formCells); 85 | expect(([formViewController allFormCells])).to.equal(@[@[], formCells]); 86 | 87 | expect(([formViewController formCellsInSection:0])).to.haveCountOf(0); 88 | expect(([formViewController formCellsInSection:1])).to.equal(formCells); 89 | expect(([formViewController formCellsInSection:2])).to.haveCountOf(0); 90 | expect(([formViewController formCellsInSection:3])).to.haveCountOf(0); 91 | }); 92 | }); 93 | 94 | #pragma clang diagnostic pop 95 | 96 | context(@"adding form cells into specific sections", ^{ 97 | it(@"should set the form view controller's form cells", ^{ 98 | expect([formViewController.tableView numberOfRowsInSection:0]).to.equal(0); 99 | expect([formViewController.tableView numberOfRowsInSection:1]).to.equal(2); 100 | expect([formViewController.tableView numberOfRowsInSection:2]).to.equal(1); 101 | }); 102 | }); 103 | }); 104 | 105 | describe(@"previousFormCell:", ^{ 106 | it(@"should return the correct previous cell", ^{ 107 | expect([formViewController previousFormCell:cell3]).to.equal(cell2); 108 | expect([formViewController previousFormCell:cell2]).to.equal(cell1); 109 | expect([formViewController previousFormCell:cell1]).to.equal(nil); 110 | }); 111 | }); 112 | 113 | describe(@"nextFormCell:", ^{ 114 | it(@"should return the correct next cell", ^{ 115 | expect([formViewController nextFormCell:cell1]).to.equal(cell2); 116 | expect([formViewController nextFormCell:cell2]).to.equal(cell3); 117 | expect([formViewController nextFormCell:cell3]).to.equal(nil); 118 | }); 119 | }); 120 | 121 | describe(@"firstInvalidFormCell", ^{ 122 | it(@"should return the correct first invalid cell", ^{ 123 | expect([formViewController firstInvalidFormCell]).to.equal(nil); 124 | cell2.validationState = BZGValidationStateInvalid; 125 | cell3.validationState = BZGValidationStateInvalid; 126 | expect([formViewController firstInvalidFormCell]).to.equal(cell2); 127 | cell1.validationState = BZGValidationStateInvalid; 128 | expect([formViewController firstInvalidFormCell]).to.equal(cell1); 129 | }); 130 | }); 131 | 132 | describe(@"firstWarningFormCell", ^{ 133 | it(@"should return the correct first warning cell", ^{ 134 | expect([formViewController firstInvalidFormCell]).to.equal(nil); 135 | cell2.validationState = BZGValidationStateWarning; 136 | cell3.validationState = BZGValidationStateWarning; 137 | expect([formViewController firstWarningFormCell]).to.equal(cell2); 138 | cell1.validationState = BZGValidationStateWarning; 139 | expect([formViewController firstWarningFormCell]).to.equal(cell1); 140 | }); 141 | }); 142 | 143 | describe(@"updateInfoCellBelowFormCell", ^{ 144 | it(@"should show an info cell when the field has state BZGValidationStateInvalid", ^{ 145 | [cell1.infoCell setText:@"cell1 info text"]; 146 | cell1.validationState = BZGValidationStateInvalid; 147 | [formViewController updateInfoCellBelowFormCell:cell1]; 148 | expect([formViewController.tableView numberOfRowsInSection:1]).to.equal(3); 149 | NSIndexPath *infoCellIndexPath = [NSIndexPath indexPathForRow:1 inSection:1]; 150 | UITableViewCell *infoCell = [formViewController.tableView cellForRowAtIndexPath:infoCellIndexPath]; 151 | expect(infoCell).to.beKindOf([BZGInfoCell class]); 152 | expect(((BZGInfoCell *)infoCell).infoLabel.text).to.equal(@"cell1 info text"); 153 | }); 154 | 155 | it(@"should show an info cell when the field has state BZGValidationStateWarning", ^{ 156 | [cell3.infoCell setText:@"cell3 info text"]; 157 | cell3.validationState = BZGValidationStateWarning; 158 | [formViewController updateInfoCellBelowFormCell:cell3]; 159 | expect([formViewController.tableView numberOfRowsInSection:2]).to.equal(2); 160 | NSIndexPath *infoCellIndexPath = [NSIndexPath indexPathForRow:1 inSection:2]; 161 | UITableViewCell *infoCell = [formViewController.tableView cellForRowAtIndexPath:infoCellIndexPath]; 162 | expect(infoCell).to.beKindOf([BZGInfoCell class]); 163 | expect(((BZGInfoCell *)infoCell).infoLabel.text).to.equal(@"cell3 info text"); 164 | }); 165 | 166 | it(@"should never show an info cell if text has not been provided", ^{ 167 | [cell3.infoCell setText:@""]; 168 | cell3.validationState = BZGValidationStateWarning; 169 | [formViewController updateInfoCellBelowFormCell:cell3]; 170 | expect([formViewController.tableView numberOfRowsInSection:2]).to.equal(1); 171 | [cell3.infoCell setText:nil]; 172 | cell3.validationState = BZGValidationStateInvalid; 173 | [formViewController updateInfoCellBelowFormCell:cell3]; 174 | expect([formViewController.tableView numberOfRowsInSection:2]).to.equal(1); 175 | }); 176 | 177 | it(@"should not show an info cell when the field has state BZGValidationStateValid", ^{ 178 | [cell1.infoCell setText:@"cell1 info text"]; 179 | cell1.validationState = BZGValidationStateValid; 180 | [formViewController updateInfoCellBelowFormCell:cell1]; 181 | expect([formViewController.tableView numberOfRowsInSection:1]).to.equal(2); 182 | }); 183 | 184 | it(@"should not show any info cells when the formViewController has flag showsValidationCell=NO", ^{ 185 | formViewController.showsValidationCell = NO; 186 | [cell1.infoCell setText:@"cell1 info text"]; 187 | cell1.validationState = BZGValidationStateInvalid; 188 | [formViewController updateInfoCellBelowFormCell:cell1]; 189 | expect([formViewController.tableView numberOfRowsInSection:1]).to.equal(2); 190 | }); 191 | 192 | it(@"should update the info cell's text", ^{ 193 | cell2.validationState = BZGValidationStateInvalid; 194 | [cell2.infoCell setText:@"cell2 info text"]; 195 | [formViewController updateInfoCellBelowFormCell:cell2]; 196 | expect([formViewController.tableView numberOfRowsInSection:1]).to.equal(3); 197 | NSIndexPath *infoCellIndexPath = [NSIndexPath indexPathForRow:2 inSection:1]; 198 | UITableViewCell *infoCell = [formViewController.tableView cellForRowAtIndexPath:infoCellIndexPath]; 199 | expect(infoCell).to.beKindOf([BZGInfoCell class]); 200 | expect(((BZGInfoCell *)infoCell).infoLabel.text).to.equal(@"cell2 info text"); 201 | 202 | [cell2.infoCell setText:@"cell2 info text changed"]; 203 | [formViewController updateInfoCellBelowFormCell:cell2]; 204 | expect([formViewController.tableView numberOfRowsInSection:1]).to.equal(3); 205 | infoCellIndexPath = [NSIndexPath indexPathForRow:2 inSection:1]; 206 | infoCell = [formViewController.tableView cellForRowAtIndexPath:infoCellIndexPath]; 207 | expect(infoCell).to.beKindOf([BZGInfoCell class]); 208 | expect(((BZGInfoCell *)infoCell).infoLabel.text).to.equal(@"cell2 info text changed"); 209 | }); 210 | }); 211 | 212 | describe(@"UITextFieldDelegate blocks", ^{ 213 | it(@"should call the didBeginEditingBlock when the text field begins editing", ^{ 214 | cell1.textField.text = @"cell1 textfield text"; 215 | cell1.didBeginEditingBlock = ^(BZGTextFieldCell *cell, NSString *text) { 216 | cell.infoCell.textLabel.text = text; 217 | }; 218 | [formViewController textFieldDidBeginEditing:cell1.textField]; 219 | expect(cell1.infoCell.textLabel.text).to.equal(@"cell1 textfield text"); 220 | }); 221 | 222 | it(@"should call the shouldChangeText block when the text field's text changes", ^{ 223 | cell1.textField.text = @"foo"; 224 | cell1.shouldChangeTextBlock = ^BOOL(BZGTextFieldCell *cell, NSString *text) { 225 | cell.infoCell.textLabel.text = text; 226 | return YES; 227 | }; 228 | 229 | expect(cell1.infoCell.textLabel.text).toNot.equal(@"foo"); 230 | [formViewController textField:cell1.textField shouldChangeCharactersInRange:NSMakeRange(0, 0) replacementString:@""]; 231 | expect(cell1.infoCell.textLabel.text).to.equal(@"foo"); 232 | }); 233 | 234 | it(@"should call the didEndEditing block when the text field ends editing", ^{ 235 | cell1.textField.text = @"foo"; 236 | cell1.didEndEditingBlock = ^(BZGTextFieldCell *cell, NSString *text) { 237 | cell.infoCell.textLabel.text = text; 238 | }; 239 | 240 | expect(cell1.infoCell.textLabel.text).toNot.equal(@"foo"); 241 | [formViewController textFieldDidEndEditing:cell1.textField]; 242 | expect(cell1.infoCell.textLabel.text).to.equal(@"foo"); 243 | }); 244 | 245 | it(@"should call the shouldReturn block when the text field returns", ^{ 246 | cell1.textField.text = @"foo"; 247 | cell1.shouldReturnBlock = ^BOOL(BZGTextFieldCell *cell, NSString *text) { 248 | cell.infoCell.textLabel.text = text; 249 | return YES; 250 | }; 251 | 252 | expect(cell1.infoCell.textLabel.text).toNot.equal(@"foo"); 253 | [formViewController textFieldShouldReturn:cell1.textField]; 254 | [cell1.textField sendActionsForControlEvents:UIControlEventAllEditingEvents]; 255 | expect(cell1.infoCell.textLabel.text).to.equal(@"foo"); 256 | }); 257 | }); 258 | 259 | describe(@"isValid", ^{ 260 | it(@"should be valid when all the cells are valid", ^{ 261 | cell1.validationState = BZGValidationStateValid; 262 | cell2.validationState = BZGValidationStateValid; 263 | cell3.validationState = BZGValidationStateValid; 264 | expect(formViewController.isValid).to.equal(YES); 265 | }); 266 | 267 | it(@"should be valid when one cell is warning", ^{ 268 | cell1.validationState = BZGValidationStateWarning; 269 | cell2.validationState = BZGValidationStateValid; 270 | cell3.validationState = BZGValidationStateValid; 271 | expect(formViewController.isValid).to.equal(YES); 272 | }); 273 | 274 | it(@"should be invalid when one cell is invalid", ^{ 275 | cell1.validationState = BZGValidationStateValid; 276 | cell2.validationState = BZGValidationStateInvalid; 277 | cell3.validationState = BZGValidationStateValid; 278 | expect(formViewController.isValid).to.equal(NO); 279 | }); 280 | 281 | it(@"should be invalid when one cell is validating", ^{ 282 | cell1.validationState = BZGValidationStateValid; 283 | cell2.validationState = BZGValidationStateValid; 284 | cell3.validationState = BZGValidationStateValidating; 285 | expect(formViewController.isValid).to.equal(NO); 286 | }); 287 | }); 288 | 289 | describe(@"prepareCell:", ^{ 290 | it(@"should set the cell's delegate to the BZGFormViewController", ^{ 291 | it(@"should set the cell's UITextFieldDelegate to the form view controller", ^{ 292 | [formViewController addFormCell:cell1 atSection:0]; 293 | expect(cell1.textField.delegate).to.equal(formViewController); 294 | }); 295 | 296 | it(@"should set the cell's BZGFormCellDelegate to the form view controller", ^{ 297 | [formViewController addFormCell:cell1 atSection:0]; 298 | expect(cell1.delegate).to.equal(formViewController); 299 | }); 300 | 301 | it(@"should raise an exception if the cell is not a BZGFormCell or BZGInfoCell", ^{ 302 | expect(^{ 303 | [formViewController addFormCell:(BZGFormCell *)[[UITableViewCell alloc] init] atSection:0]; 304 | }).to.raise(nil); 305 | }); 306 | }); 307 | }); 308 | 309 | describe(@"insertFormCells:atIndexPath:", ^{ 310 | it(@"should insert the form cells into the section", ^{ 311 | BZGFormCell *cell4 = [[BZGFormCell alloc] init]; 312 | 313 | [formViewController insertFormCells:@[cell3, cell4] atIndexPath:[NSIndexPath indexPathForRow:1 inSection:1]]; 314 | expect([formViewController formCellsInSection:1]).to.equal(@[cell1, cell3, cell4, cell2]); 315 | }); 316 | }); 317 | 318 | describe(@"removeFormCellAtIndexPath:", ^{ 319 | it(@"should remove the form cell at the provided index path", ^{ 320 | [formViewController removeFormCellAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:1]]; 321 | 322 | expect([formViewController formCellsInSection:1]).to.equal(@[cell2]); 323 | expect([formViewController formCellsInSection:2]).to.equal(@[cell3]); 324 | }); 325 | }); 326 | 327 | describe(@"removeFormCellsInSection:", ^{ 328 | it(@"should remove the form cell at the providedindex path", ^{ 329 | [formViewController removeFormCellsInSection:1]; 330 | 331 | expect([formViewController formCellsInSection:1]).to.equal(@[]); 332 | }); 333 | }); 334 | 335 | describe(@"removeAllFormCells", ^{ 336 | it(@"should remove the form cell at the providedindex path", ^{ 337 | [formViewController removeAllFormCells]; 338 | 339 | expect([formViewController allFormCells]).to.equal(@[]); 340 | }); 341 | }); 342 | 343 | describe(@"indexPathOfCell:", ^{ 344 | it(@"should return an NSIndexPath representing the row and section that the cell is in", ^{ 345 | expect([formViewController indexPathOfCell:cell3]).to.equal([NSIndexPath indexPathForRow:0 inSection:2]); 346 | }); 347 | 348 | it(@"should return nil if the cell is not in the formViewController", ^{ 349 | expect([formViewController indexPathOfCell:[[BZGFormCell alloc] init]]).to.beNil(); 350 | }); 351 | }); 352 | 353 | describe(@"setShowsValidationCell", ^{ 354 | it(@"should updateInfoCellBelowFormCell for all cells", ^{ 355 | for (NSNumber *boolean in @[@(YES), @(NO)]) { 356 | for (BZGTextFieldCell *cell in @[cell1, cell2, cell3]) { 357 | [[(id)formViewController expect] updateInfoCellBelowFormCell:cell]; 358 | } 359 | BOOL booleanPrimitive = [boolean boolValue]; 360 | formViewController.showsValidationCell = booleanPrimitive; 361 | expect(formViewController.showsValidationCell).to.equal(booleanPrimitive); 362 | } 363 | }); 364 | }); 365 | 366 | describe(@"Keyboard notifications", ^{ 367 | describe(@"tableView.contentInset", ^{ 368 | __block UIEdgeInsets existingInsets; 369 | __block UIEdgeInsets expectedInsets; 370 | __block UIEdgeInsets insetsWithKeyboard; 371 | __block CGRect keyboardRect; 372 | __block CGSize keyboardSize; 373 | __block NSDictionary *notificationUserInfo; 374 | 375 | before(^{ 376 | existingInsets = UIEdgeInsetsMake(1.f, 1.f, 1.f, 1.f); 377 | keyboardSize = CGSizeMake(320.f, 270.f); 378 | keyboardRect = CGRectMake(0.f, 0.f, keyboardSize.width, keyboardSize.height); 379 | notificationUserInfo = @{ 380 | UIKeyboardFrameBeginUserInfoKey : [NSValue valueWithCGRect:keyboardRect], 381 | UIKeyboardAnimationDurationUserInfoKey : [NSNumber numberWithInt:0] 382 | }; 383 | insetsWithKeyboard = UIEdgeInsetsMake(existingInsets.top, 384 | existingInsets.left, 385 | existingInsets.bottom + keyboardSize.height, 386 | existingInsets.right); 387 | }); 388 | 389 | context(@"UIKeyboardWillShowNotification", ^{ 390 | before(^{ 391 | expectedInsets = insetsWithKeyboard; 392 | 393 | formViewController.tableView.contentInset = existingInsets; 394 | 395 | [[NSNotificationCenter defaultCenter] postNotificationName:UIKeyboardWillShowNotification 396 | object:nil 397 | userInfo:notificationUserInfo]; 398 | }); 399 | 400 | it(@"should maintain the current insets, adding the height of the keyboard", ^{ 401 | expect(formViewController.tableView.contentInset).to.equal(expectedInsets); 402 | }); 403 | }); 404 | 405 | context(@"UIKeyboardWillHideNotification", ^{ 406 | before(^{ 407 | expectedInsets = existingInsets; 408 | 409 | formViewController.tableView.contentInset = existingInsets; 410 | 411 | // Trigger the show (this sets up the with-keyboard-insets 412 | [[NSNotificationCenter defaultCenter] postNotificationName:UIKeyboardWillShowNotification 413 | object:nil 414 | userInfo:notificationUserInfo]; 415 | 416 | // "Hide" the keyboard 417 | [[NSNotificationCenter defaultCenter] postNotificationName:UIKeyboardWillHideNotification 418 | object:nil 419 | userInfo:notificationUserInfo]; 420 | }); 421 | 422 | it(@"should set the insets back to their original value", ^{ 423 | expect(formViewController.tableView.contentInset).to.equal(expectedInsets); 424 | }); 425 | }); 426 | }); 427 | 428 | describe(@"tableView.scrollIndicatorInsets", ^{ 429 | __block UIEdgeInsets existingInsets; 430 | __block UIEdgeInsets expectedInsets; 431 | __block UIEdgeInsets insetsWithKeyboard; 432 | __block CGRect keyboardRect; 433 | __block CGSize keyboardSize; 434 | __block NSDictionary *notificationUserInfo; 435 | 436 | before(^{ 437 | existingInsets = UIEdgeInsetsMake(1.f, 1.f, 1.f, 1.f); 438 | keyboardSize = CGSizeMake(320.f, 270.f); 439 | keyboardRect = CGRectMake(0.f, 0.f, keyboardSize.width, keyboardSize.height); 440 | notificationUserInfo = @{ 441 | UIKeyboardFrameBeginUserInfoKey : [NSValue valueWithCGRect:keyboardRect], 442 | UIKeyboardAnimationDurationUserInfoKey : [NSNumber numberWithInt:0] 443 | }; 444 | insetsWithKeyboard = UIEdgeInsetsMake(existingInsets.top, 445 | existingInsets.left, 446 | existingInsets.bottom + keyboardSize.height, 447 | existingInsets.right); 448 | }); 449 | 450 | context(@"UIKeyboardWillShowNotification", ^{ 451 | before(^{ 452 | expectedInsets = insetsWithKeyboard; 453 | 454 | formViewController.tableView.contentInset = existingInsets; 455 | 456 | [[NSNotificationCenter defaultCenter] postNotificationName:UIKeyboardWillShowNotification 457 | object:nil 458 | userInfo:notificationUserInfo]; 459 | }); 460 | 461 | it(@"should maintain the current insets, adding the height of the keyboard", ^{ 462 | expect(formViewController.tableView.scrollIndicatorInsets).to.equal(expectedInsets); 463 | }); 464 | }); 465 | 466 | context(@"UIKeyboardWillHideNotification", ^{ 467 | before(^{ 468 | expectedInsets = existingInsets; 469 | 470 | formViewController.tableView.contentInset = existingInsets; 471 | 472 | // Trigger the show (this sets up the with-keyboard-insets 473 | [[NSNotificationCenter defaultCenter] postNotificationName:UIKeyboardWillShowNotification 474 | object:nil 475 | userInfo:notificationUserInfo]; 476 | 477 | // "Hide" the keyboard 478 | [[NSNotificationCenter defaultCenter] postNotificationName:UIKeyboardWillHideNotification 479 | object:nil 480 | userInfo:notificationUserInfo]; 481 | }); 482 | 483 | it(@"should set the insets back to their original value", ^{ 484 | expect(formViewController.tableView.scrollIndicatorInsets).to.equal(expectedInsets); 485 | }); 486 | }); 487 | }); 488 | }); 489 | 490 | SpecEnd 491 | -------------------------------------------------------------------------------- /BZGFormViewControllerTests/BZGInfoCellSpecs.m: -------------------------------------------------------------------------------- 1 | // 2 | // BZGInfoCellSpecs.m 3 | // 4 | // https://github.com/benzguo/BZGFormViewController 5 | // 6 | 7 | #import "BZGInfoCell.h" 8 | 9 | SpecBegin(BZGInfoCell) 10 | 11 | describe(@"Initialization", ^{ 12 | it(@"should correctly initialize with text", ^{ 13 | BZGInfoCell *cell = [[BZGInfoCell alloc] initWithText:@"foo"]; 14 | expect(cell.selectionStyle).to.equal(UITableViewCellSelectionStyleNone); 15 | expect(cell.infoLabel.text).to.equal(@"foo"); 16 | }); 17 | }); 18 | 19 | describe(@"Resizing", ^{ 20 | it(@"should correctly resize to fit large text", ^{ 21 | BZGInfoCell *cell1 = [[BZGInfoCell alloc] initWithText:@"foo"]; 22 | expect(cell1.infoLabel.text).to.equal(@"foo"); 23 | 24 | NSString *longText = @"foo foo foo foo foo foo foo foo. foo foo foo foo foo foo foo foo. fooo foo foo foo foo foo foo foo."; 25 | BZGInfoCell *cell2 = [[BZGInfoCell alloc] initWithText:longText]; 26 | expect(cell1.frame.size.height).to.beLessThan(cell2.frame.size.height); 27 | }); 28 | }); 29 | 30 | describe(@"Setting text", ^{ 31 | it(@"should correctly update when setting text", ^{ 32 | BZGInfoCell *cell = [[BZGInfoCell alloc] init]; 33 | expect(cell.infoLabel.text).to.equal(@""); 34 | [cell setText:@"foo"]; 35 | expect(cell.infoLabel.text).to.equal(@"foo"); 36 | CGFloat height1 = cell.frame.size.height; 37 | 38 | NSString *longText = @"foo foo foo foo foo foo foo foo. foo foo foo foo foo foo foo foo. fooo foo foo foo foo foo foo foo."; 39 | [cell setText:longText]; 40 | expect(cell.infoLabel.text).to.equal(longText); 41 | CGFloat height2 = cell.frame.size.height; 42 | expect(height1).to.beLessThan(height2); 43 | }); 44 | }); 45 | 46 | SpecEnd -------------------------------------------------------------------------------- /BZGFormViewControllerTests/BZGKeyboardControlSpecs.m: -------------------------------------------------------------------------------- 1 | // 2 | // BZGKeyboardControlSpecs.m 3 | // 4 | // https://github.com/benzguo/BZGFormViewController 5 | // 6 | 7 | #import "BZGKeyboardControl.h" 8 | #import "BZGTextFieldCell.h" 9 | 10 | BZGKeyboardControl *keyboardControl; 11 | BZGTextFieldCell *cell1; 12 | BZGTextFieldCell *cell2; 13 | 14 | SpecBegin(BZGKeyboardControl) 15 | 16 | before(^{ 17 | keyboardControl = [[BZGKeyboardControl alloc] initWithFrame:CGRectMake(0, 0, 320, 44)]; 18 | cell1 = [[BZGTextFieldCell alloc] init]; 19 | cell2 = [[BZGTextFieldCell alloc] init]; 20 | }); 21 | 22 | after(^{ 23 | keyboardControl = nil; 24 | cell1 = nil; 25 | cell2 = nil; 26 | }); 27 | 28 | 29 | describe(@"Initialization", ^{ 30 | it(@"should initialize with correct defaults", ^{ 31 | expect(keyboardControl.previousButton).toNot.beNil(); 32 | expect(keyboardControl.nextButton).toNot.beNil(); 33 | expect(keyboardControl.doneButton).toNot.beNil(); 34 | expect(keyboardControl.previousButton.enabled).to.beFalsy(); 35 | expect(keyboardControl.nextButton.enabled).to.beFalsy(); 36 | expect(keyboardControl.previousButton.tintColor).to.equal([UIColor blackColor]); 37 | expect(keyboardControl.nextButton.tintColor).to.equal([UIColor blackColor]); 38 | expect(keyboardControl.doneButton.tintColor).to.equal([UIColor blackColor]); 39 | }); 40 | }); 41 | 42 | describe(@"Button States", ^{ 43 | 44 | it(@"should have both buttons enabled if it has a previous cell and next cell", ^{ 45 | keyboardControl.previousCell = cell1; 46 | keyboardControl.nextCell = cell2; 47 | expect(keyboardControl.previousButton.enabled).to.beTruthy(); 48 | expect(keyboardControl.nextButton.enabled).to.beTruthy(); 49 | }); 50 | 51 | it(@"should have only the previous button enabled if it has a previous cell and a nil next cell", ^{ 52 | keyboardControl.previousCell = cell1; 53 | keyboardControl.nextCell = nil; 54 | expect(keyboardControl.previousButton.enabled).to.beTruthy(); 55 | expect(keyboardControl.nextButton.enabled).to.beFalsy(); 56 | }); 57 | 58 | it(@"should have only the next button enabled if it has a nil previous cell and a next cell", ^{ 59 | keyboardControl.previousCell = nil; 60 | keyboardControl.nextCell = cell2; 61 | expect(keyboardControl.previousButton.enabled).to.beFalsy(); 62 | expect(keyboardControl.nextButton.enabled).to.beTruthy(); 63 | }); 64 | 65 | it(@"should have both buttons disabled if it has a nil previous cell and a nil next cell", ^{ 66 | keyboardControl.previousCell = nil; 67 | keyboardControl.nextCell = nil; 68 | expect(keyboardControl.previousButton.enabled).to.beFalsy(); 69 | expect(keyboardControl.nextButton.enabled).to.beFalsy(); 70 | }); 71 | 72 | }); 73 | 74 | SpecEnd -------------------------------------------------------------------------------- /BZGFormViewControllerTests/BZGPhoneTextFieldCellSpec.m: -------------------------------------------------------------------------------- 1 | #import "BZGPhoneTextFieldCell.h" 2 | 3 | #import 4 | #import 5 | 6 | #import "NSError+BZGFormViewController.h" 7 | 8 | @interface BZGPhoneTextFieldCell () 9 | 10 | @property (strong, nonatomic) NBAsYouTypeFormatter *phoneFormatter; 11 | @property (strong, nonatomic) NSString *regionCode; 12 | @property (strong, nonatomic) NBPhoneNumberUtil *phoneUtil; 13 | 14 | @end 15 | 16 | SpecBegin(BZGPhoneTextFieldCell) 17 | 18 | describe(@"initialization", ^{ 19 | it(@"should have default invalid text", ^{ 20 | BZGPhoneTextFieldCell *cell = [BZGPhoneTextFieldCell new]; 21 | expect(cell.invalidText.length).toNot.equal(0); 22 | }); 23 | }); 24 | 25 | describe(@"shouldChangeCharactersInRange:replacementString", ^{ 26 | __block BZGPhoneTextFieldCell *cell; 27 | 28 | void(^enterPhoneNumber)(NSString *) = ^(NSString *phoneNumber) { 29 | for (NSInteger i = 0; i 1) { 80 | return [super tableView:tableView numberOfRowsInSection:section]; 81 | } else { 82 | return 1; 83 | } 84 | } 85 | 86 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 87 | { 88 | if (indexPath.section > 1) { 89 | return [super tableView:tableView cellForRowAtIndexPath:indexPath]; 90 | } else { 91 | return self.otherCell; 92 | } 93 | } 94 | ``` 95 | 96 | ## Contributing 97 | Please write tests and make sure existing tests pass. Tests can be run from the demo project in `/SignupForm`. See the [Roadmap](https://github.com/benzguo/BZGFormViewController/wiki/Roadmap) for planned improvements. 98 | 99 | -------------------------------------------------------------------------------- /Screenshots/SignupForm.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0thernet/BZGFormViewController/ebedeb4bed2b48c002766e8a3ee9f47a29d801ae/Screenshots/SignupForm.gif -------------------------------------------------------------------------------- /Screenshots/SignupForm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0thernet/BZGFormViewController/ebedeb4bed2b48c002766e8a3ee9f47a29d801ae/Screenshots/SignupForm.png -------------------------------------------------------------------------------- /Screenshots/SignupForm@2x.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0thernet/BZGFormViewController/ebedeb4bed2b48c002766e8a3ee9f47a29d801ae/Screenshots/SignupForm@2x.gif -------------------------------------------------------------------------------- /Screenshots/SignupForm@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0thernet/BZGFormViewController/ebedeb4bed2b48c002766e8a3ee9f47a29d801ae/Screenshots/SignupForm@2x.png -------------------------------------------------------------------------------- /SignupForm/Podfile: -------------------------------------------------------------------------------- 1 | source 'https://github.com/CocoaPods/Specs.git' 2 | platform :ios, '5.0' 3 | 4 | pod 'BZGMailgunEmailValidation' 5 | 6 | # BZGFormViewController 7 | pod 'ReactiveCocoa', '~>2.3' 8 | pod 'libextobjc', '~>0.4' 9 | pod 'libPhoneNumber-iOS', '~>0.7' 10 | 11 | target :SignupFormTests do 12 | pod 'Specta', '~>0.2.1' 13 | pod 'Expecta', '~>0.3.0' 14 | pod 'OCMockito', '~>1.1.0' 15 | pod 'OCMock', '~>2.2.3' 16 | end 17 | -------------------------------------------------------------------------------- /SignupForm/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - BZGMailgunEmailValidation (1.1.0) 3 | - Expecta (0.3.0) 4 | - libextobjc (0.4): 5 | - libextobjc/EXTADT (= 0.4) 6 | - libextobjc/EXTConcreteProtocol (= 0.4) 7 | - libextobjc/EXTKeyPathCoding (= 0.4) 8 | - libextobjc/EXTNil (= 0.4) 9 | - libextobjc/EXTSafeCategory (= 0.4) 10 | - libextobjc/EXTScope (= 0.4) 11 | - libextobjc/EXTSelectorChecking (= 0.4) 12 | - libextobjc/EXTSynthesize (= 0.4) 13 | - libextobjc/NSInvocation+EXT (= 0.4) 14 | - libextobjc/NSMethodSignature+EXT (= 0.4) 15 | - libextobjc/RuntimeExtensions (= 0.4) 16 | - libextobjc/UmbrellaHeader (= 0.4) 17 | - libextobjc/EXTADT (0.4): 18 | - libextobjc/RuntimeExtensions 19 | - libextobjc/EXTConcreteProtocol (0.4): 20 | - libextobjc/RuntimeExtensions 21 | - libextobjc/EXTKeyPathCoding (0.4): 22 | - libextobjc/RuntimeExtensions 23 | - libextobjc/EXTNil (0.4): 24 | - libextobjc/RuntimeExtensions 25 | - libextobjc/EXTSafeCategory (0.4): 26 | - libextobjc/RuntimeExtensions 27 | - libextobjc/EXTScope (0.4): 28 | - libextobjc/RuntimeExtensions 29 | - libextobjc/EXTSelectorChecking (0.4): 30 | - libextobjc/RuntimeExtensions 31 | - libextobjc/EXTSynthesize (0.4): 32 | - libextobjc/RuntimeExtensions 33 | - libextobjc/NSInvocation+EXT (0.4): 34 | - libextobjc/RuntimeExtensions 35 | - libextobjc/NSMethodSignature+EXT (0.4): 36 | - libextobjc/RuntimeExtensions 37 | - libextobjc/RuntimeExtensions (0.4) 38 | - libextobjc/UmbrellaHeader (0.4) 39 | - libPhoneNumber-iOS (0.7.2) 40 | - OCHamcrest (3.0.1) 41 | - OCMock (2.2.4) 42 | - OCMockito (1.1.0): 43 | - OCHamcrest (~> 3.0) 44 | - ReactiveCocoa (2.3): 45 | - ReactiveCocoa/UI (= 2.3) 46 | - ReactiveCocoa/Core (2.3): 47 | - ReactiveCocoa/no-arc 48 | - ReactiveCocoa/no-arc (2.3) 49 | - ReactiveCocoa/UI (2.3): 50 | - ReactiveCocoa/Core 51 | - Specta (0.2.1) 52 | 53 | DEPENDENCIES: 54 | - BZGMailgunEmailValidation 55 | - Expecta (~> 0.3.0) 56 | - libextobjc (~> 0.4) 57 | - libPhoneNumber-iOS (~> 0.7) 58 | - OCMock (~> 2.2.3) 59 | - OCMockito (~> 1.1.0) 60 | - ReactiveCocoa (~> 2.3) 61 | - Specta (~> 0.2.1) 62 | 63 | SPEC CHECKSUMS: 64 | BZGMailgunEmailValidation: abe6c4b55cf27a0ae89f00e0c78fd4ac82637ed0 65 | Expecta: ce8a51b9fad15a2cd573b291cb2909aaed865350 66 | libextobjc: ba42e4111f433273a886cd54f0ddaddb7f62f82f 67 | libPhoneNumber-iOS: 207db9b19395d5ba8898398626620568282fa2bc 68 | OCHamcrest: 207233b7d7a44dadd66aca398947cc7e029c9be5 69 | OCMock: 6db79185520e24f9f299548f2b8b07e41d881bd5 70 | OCMockito: bf7def7b3939f794b1e7b44420f611250b1f2781 71 | ReactiveCocoa: 2066583826c17dde8e90c41e489f682048cc57c9 72 | Specta: 9141310f46b1f68b676650ff2854e1ed0b74163a 73 | 74 | COCOAPODS: 0.35.0 75 | -------------------------------------------------------------------------------- /SignupForm/SignupForm.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 30709D4AA1994EF090749611 /* libPods.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 90F90CF32A864DEC854302D2 /* libPods.a */; }; 11 | 3A1F56A718F388B40090E732 /* BZGKeyboardControl.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A1F56A618F388B40090E732 /* BZGKeyboardControl.m */; }; 12 | 3A1F56A918F432F90090E732 /* BZGKeyboardControlSpecs.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A1F56A818F432F90090E732 /* BZGKeyboardControlSpecs.m */; }; 13 | 3A7A8B281A145A120039AAD7 /* LaunchImage.xib in Resources */ = {isa = PBXBuildFile; fileRef = 3A7A8B271A145A120039AAD7 /* LaunchImage.xib */; }; 14 | 799AEBE944DD4B758103BA7E /* libPods-SignupFormTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 51A99866C8474A4D9FA92F85 /* libPods-SignupFormTests.a */; }; 15 | 8CE100451965E4DD00287E0B /* NSError+BZGFormViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 8CE100441965E4DD00287E0B /* NSError+BZGFormViewController.m */; }; 16 | EB102241190B078E00F973F0 /* BZGPhoneTextFieldCell.m in Sources */ = {isa = PBXBuildFile; fileRef = EB102240190B078E00F973F0 /* BZGPhoneTextFieldCell.m */; }; 17 | EB18C91A18DE628500F23064 /* BZGFormCell.m in Sources */ = {isa = PBXBuildFile; fileRef = EB18C91918DE628500F23064 /* BZGFormCell.m */; }; 18 | EB18C91C18DE62BC00F23064 /* BZGFormCellSpecs.m in Sources */ = {isa = PBXBuildFile; fileRef = EB18C91B18DE62BC00F23064 /* BZGFormCellSpecs.m */; }; 19 | EB59ED58180B2D9200DAA992 /* BZGTextFieldCellSpecs.m in Sources */ = {isa = PBXBuildFile; fileRef = EB59ED55180B2D9200DAA992 /* BZGTextFieldCellSpecs.m */; }; 20 | EB59ED63180B2DB100DAA992 /* BZGTextFieldCell.m in Sources */ = {isa = PBXBuildFile; fileRef = EB59ED5D180B2DB100DAA992 /* BZGTextFieldCell.m */; }; 21 | EB59ED64180B2DB100DAA992 /* BZGInfoCell.m in Sources */ = {isa = PBXBuildFile; fileRef = EB59ED5F180B2DB100DAA992 /* BZGInfoCell.m */; }; 22 | EB59ED6C180B2F0800DAA992 /* BZGFormViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = EB59ED6B180B2F0800DAA992 /* BZGFormViewController.m */; }; 23 | EB59ED6F180B313000DAA992 /* SignupViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = EB59ED6E180B313000DAA992 /* SignupViewController.m */; }; 24 | EB59ED71180B377900DAA992 /* BZGInfoCellSpecs.m in Sources */ = {isa = PBXBuildFile; fileRef = EB59ED70180B377900DAA992 /* BZGInfoCellSpecs.m */; }; 25 | EB59ED73180B378A00DAA992 /* BZGFormViewControllerSpecs.m in Sources */ = {isa = PBXBuildFile; fileRef = EB59ED72180B378A00DAA992 /* BZGFormViewControllerSpecs.m */; }; 26 | EB83D521180B2CA9001037F5 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EB83D520180B2CA9001037F5 /* Foundation.framework */; }; 27 | EB83D523180B2CA9001037F5 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EB83D522180B2CA9001037F5 /* CoreGraphics.framework */; }; 28 | EB83D525180B2CA9001037F5 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EB83D524180B2CA9001037F5 /* UIKit.framework */; }; 29 | EB83D52B180B2CA9001037F5 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = EB83D529180B2CA9001037F5 /* InfoPlist.strings */; }; 30 | EB83D52D180B2CA9001037F5 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = EB83D52C180B2CA9001037F5 /* main.m */; }; 31 | EB83D531180B2CA9001037F5 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = EB83D530180B2CA9001037F5 /* AppDelegate.m */; }; 32 | EB83D533180B2CA9001037F5 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = EB83D532180B2CA9001037F5 /* Images.xcassets */; }; 33 | EB83D53A180B2CA9001037F5 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EB83D539180B2CA9001037F5 /* XCTest.framework */; }; 34 | EB83D53B180B2CA9001037F5 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EB83D520180B2CA9001037F5 /* Foundation.framework */; }; 35 | EB83D53C180B2CA9001037F5 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EB83D524180B2CA9001037F5 /* UIKit.framework */; }; 36 | EB83D544180B2CA9001037F5 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = EB83D542180B2CA9001037F5 /* InfoPlist.strings */; }; 37 | EB9A0BED190B18CF0085B5A8 /* BZGPhoneTextFieldCellSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = EB9A0BEC190B18CF0085B5A8 /* BZGPhoneTextFieldCellSpec.m */; }; 38 | /* End PBXBuildFile section */ 39 | 40 | /* Begin PBXContainerItemProxy section */ 41 | EB83D53D180B2CA9001037F5 /* PBXContainerItemProxy */ = { 42 | isa = PBXContainerItemProxy; 43 | containerPortal = EB83D515180B2CA9001037F5 /* Project object */; 44 | proxyType = 1; 45 | remoteGlobalIDString = EB83D51C180B2CA9001037F5; 46 | remoteInfo = SignupForm; 47 | }; 48 | /* End PBXContainerItemProxy section */ 49 | 50 | /* Begin PBXFileReference section */ 51 | 2AE1260CFD537C6C57DF2D2E /* Pods-SignupFormTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SignupFormTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-SignupFormTests/Pods-SignupFormTests.release.xcconfig"; sourceTree = ""; }; 52 | 3A1F56A518F388B40090E732 /* BZGKeyboardControl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BZGKeyboardControl.h; path = ../../BZGFormViewController/BZGKeyboardControl.h; sourceTree = ""; }; 53 | 3A1F56A618F388B40090E732 /* BZGKeyboardControl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BZGKeyboardControl.m; path = ../../BZGFormViewController/BZGKeyboardControl.m; sourceTree = ""; }; 54 | 3A1F56A818F432F90090E732 /* BZGKeyboardControlSpecs.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BZGKeyboardControlSpecs.m; path = ../../BZGFormViewControllerTests/BZGKeyboardControlSpecs.m; sourceTree = ""; }; 55 | 3A7A8B271A145A120039AAD7 /* LaunchImage.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = LaunchImage.xib; sourceTree = ""; }; 56 | 51A99866C8474A4D9FA92F85 /* libPods-SignupFormTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-SignupFormTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 57 | 598A47BFA37AFF1A218D4E27 /* Pods-SignupFormTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SignupFormTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SignupFormTests/Pods-SignupFormTests.debug.xcconfig"; sourceTree = ""; }; 58 | 637FF53EA385C8AADC43AB6C /* Pods.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.debug.xcconfig; path = "Pods/Target Support Files/Pods/Pods.debug.xcconfig"; sourceTree = ""; }; 59 | 75D592C1258E247EE1D17150 /* Pods.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.release.xcconfig; path = "Pods/Target Support Files/Pods/Pods.release.xcconfig"; sourceTree = ""; }; 60 | 8CE100431965E4DD00287E0B /* NSError+BZGFormViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSError+BZGFormViewController.h"; path = "../../BZGFormViewController/NSError+BZGFormViewController.h"; sourceTree = ""; }; 61 | 8CE100441965E4DD00287E0B /* NSError+BZGFormViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSError+BZGFormViewController.m"; path = "../../BZGFormViewController/NSError+BZGFormViewController.m"; sourceTree = ""; }; 62 | 90F90CF32A864DEC854302D2 /* libPods.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libPods.a; sourceTree = BUILT_PRODUCTS_DIR; }; 63 | EB10223F190B078E00F973F0 /* BZGPhoneTextFieldCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BZGPhoneTextFieldCell.h; path = ../../BZGFormViewController/BZGPhoneTextFieldCell.h; sourceTree = ""; }; 64 | EB102240190B078E00F973F0 /* BZGPhoneTextFieldCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BZGPhoneTextFieldCell.m; path = ../../BZGFormViewController/BZGPhoneTextFieldCell.m; sourceTree = ""; }; 65 | EB18C91818DE628500F23064 /* BZGFormCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BZGFormCell.h; path = ../../BZGFormViewController/BZGFormCell.h; sourceTree = ""; }; 66 | EB18C91918DE628500F23064 /* BZGFormCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BZGFormCell.m; path = ../../BZGFormViewController/BZGFormCell.m; sourceTree = ""; }; 67 | EB18C91B18DE62BC00F23064 /* BZGFormCellSpecs.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BZGFormCellSpecs.m; path = ../../BZGFormViewControllerTests/BZGFormCellSpecs.m; sourceTree = ""; }; 68 | EB59ED55180B2D9200DAA992 /* BZGTextFieldCellSpecs.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BZGTextFieldCellSpecs.m; path = ../../BZGFormViewControllerTests/BZGTextFieldCellSpecs.m; sourceTree = ""; }; 69 | EB59ED5C180B2DB100DAA992 /* BZGTextFieldCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BZGTextFieldCell.h; path = ../../BZGFormViewController/BZGTextFieldCell.h; sourceTree = ""; }; 70 | EB59ED5D180B2DB100DAA992 /* BZGTextFieldCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BZGTextFieldCell.m; path = ../../BZGFormViewController/BZGTextFieldCell.m; sourceTree = ""; }; 71 | EB59ED5E180B2DB100DAA992 /* BZGInfoCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BZGInfoCell.h; path = ../../BZGFormViewController/BZGInfoCell.h; sourceTree = ""; }; 72 | EB59ED5F180B2DB100DAA992 /* BZGInfoCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BZGInfoCell.m; path = ../../BZGFormViewController/BZGInfoCell.m; sourceTree = ""; }; 73 | EB59ED6A180B2F0800DAA992 /* BZGFormViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BZGFormViewController.h; path = ../../BZGFormViewController/BZGFormViewController.h; sourceTree = ""; }; 74 | EB59ED6B180B2F0800DAA992 /* BZGFormViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BZGFormViewController.m; path = ../../BZGFormViewController/BZGFormViewController.m; sourceTree = ""; }; 75 | EB59ED6D180B313000DAA992 /* SignupViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SignupViewController.h; sourceTree = ""; }; 76 | EB59ED6E180B313000DAA992 /* SignupViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SignupViewController.m; sourceTree = ""; }; 77 | EB59ED70180B377900DAA992 /* BZGInfoCellSpecs.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BZGInfoCellSpecs.m; path = ../../BZGFormViewControllerTests/BZGInfoCellSpecs.m; sourceTree = ""; }; 78 | EB59ED72180B378A00DAA992 /* BZGFormViewControllerSpecs.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BZGFormViewControllerSpecs.m; path = ../../BZGFormViewControllerTests/BZGFormViewControllerSpecs.m; sourceTree = ""; }; 79 | EB83D51D180B2CA9001037F5 /* SignupForm.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SignupForm.app; sourceTree = BUILT_PRODUCTS_DIR; }; 80 | EB83D520180B2CA9001037F5 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 81 | EB83D522180B2CA9001037F5 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 82 | EB83D524180B2CA9001037F5 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 83 | EB83D528180B2CA9001037F5 /* SignupForm-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "SignupForm-Info.plist"; sourceTree = ""; }; 84 | EB83D52A180B2CA9001037F5 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 85 | EB83D52C180B2CA9001037F5 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 86 | EB83D52E180B2CA9001037F5 /* SignupForm-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SignupForm-Prefix.pch"; sourceTree = ""; }; 87 | EB83D52F180B2CA9001037F5 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 88 | EB83D530180B2CA9001037F5 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 89 | EB83D532180B2CA9001037F5 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 90 | EB83D538180B2CA9001037F5 /* SignupFormTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SignupFormTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 91 | EB83D539180B2CA9001037F5 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; 92 | EB83D541180B2CA9001037F5 /* SignupFormTests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "SignupFormTests-Info.plist"; sourceTree = ""; }; 93 | EB83D543180B2CA9001037F5 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 94 | EB876CDE18D78408009544F9 /* SignupFormTests-Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "SignupFormTests-Prefix.pch"; sourceTree = ""; }; 95 | EB9A0BEC190B18CF0085B5A8 /* BZGPhoneTextFieldCellSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BZGPhoneTextFieldCellSpec.m; path = ../../BZGFormViewControllerTests/BZGPhoneTextFieldCellSpec.m; sourceTree = ""; }; 96 | EBDC9F3F18763ACF002EB25F /* Constants.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Constants.h; path = ../../BZGFormViewController/Constants.h; sourceTree = ""; }; 97 | /* End PBXFileReference section */ 98 | 99 | /* Begin PBXFrameworksBuildPhase section */ 100 | EB83D51A180B2CA9001037F5 /* Frameworks */ = { 101 | isa = PBXFrameworksBuildPhase; 102 | buildActionMask = 2147483647; 103 | files = ( 104 | EB83D523180B2CA9001037F5 /* CoreGraphics.framework in Frameworks */, 105 | EB83D525180B2CA9001037F5 /* UIKit.framework in Frameworks */, 106 | EB83D521180B2CA9001037F5 /* Foundation.framework in Frameworks */, 107 | 30709D4AA1994EF090749611 /* libPods.a in Frameworks */, 108 | ); 109 | runOnlyForDeploymentPostprocessing = 0; 110 | }; 111 | EB83D535180B2CA9001037F5 /* Frameworks */ = { 112 | isa = PBXFrameworksBuildPhase; 113 | buildActionMask = 2147483647; 114 | files = ( 115 | EB83D53A180B2CA9001037F5 /* XCTest.framework in Frameworks */, 116 | EB83D53C180B2CA9001037F5 /* UIKit.framework in Frameworks */, 117 | EB83D53B180B2CA9001037F5 /* Foundation.framework in Frameworks */, 118 | 799AEBE944DD4B758103BA7E /* libPods-SignupFormTests.a in Frameworks */, 119 | ); 120 | runOnlyForDeploymentPostprocessing = 0; 121 | }; 122 | /* End PBXFrameworksBuildPhase section */ 123 | 124 | /* Begin PBXGroup section */ 125 | 86185DBC2499DA471C1B1266 /* Pods */ = { 126 | isa = PBXGroup; 127 | children = ( 128 | 637FF53EA385C8AADC43AB6C /* Pods.debug.xcconfig */, 129 | 75D592C1258E247EE1D17150 /* Pods.release.xcconfig */, 130 | 598A47BFA37AFF1A218D4E27 /* Pods-SignupFormTests.debug.xcconfig */, 131 | 2AE1260CFD537C6C57DF2D2E /* Pods-SignupFormTests.release.xcconfig */, 132 | ); 133 | name = Pods; 134 | sourceTree = ""; 135 | }; 136 | EB59ED66180B2DB900DAA992 /* BZGFormViewController */ = { 137 | isa = PBXGroup; 138 | children = ( 139 | 3A1F56A518F388B40090E732 /* BZGKeyboardControl.h */, 140 | 3A1F56A618F388B40090E732 /* BZGKeyboardControl.m */, 141 | EB18C91818DE628500F23064 /* BZGFormCell.h */, 142 | EB18C91918DE628500F23064 /* BZGFormCell.m */, 143 | EB59ED5C180B2DB100DAA992 /* BZGTextFieldCell.h */, 144 | EB59ED5D180B2DB100DAA992 /* BZGTextFieldCell.m */, 145 | EB10223F190B078E00F973F0 /* BZGPhoneTextFieldCell.h */, 146 | EB102240190B078E00F973F0 /* BZGPhoneTextFieldCell.m */, 147 | EB59ED5E180B2DB100DAA992 /* BZGInfoCell.h */, 148 | EB59ED5F180B2DB100DAA992 /* BZGInfoCell.m */, 149 | EB59ED6A180B2F0800DAA992 /* BZGFormViewController.h */, 150 | EB59ED6B180B2F0800DAA992 /* BZGFormViewController.m */, 151 | EBDC9F3F18763ACF002EB25F /* Constants.h */, 152 | 8CE100431965E4DD00287E0B /* NSError+BZGFormViewController.h */, 153 | 8CE100441965E4DD00287E0B /* NSError+BZGFormViewController.m */, 154 | ); 155 | name = BZGFormViewController; 156 | sourceTree = ""; 157 | }; 158 | EB83D514180B2CA9001037F5 = { 159 | isa = PBXGroup; 160 | children = ( 161 | EB83D526180B2CA9001037F5 /* SignupForm */, 162 | EB83D53F180B2CA9001037F5 /* SignupFormTests */, 163 | EB83D51F180B2CA9001037F5 /* Frameworks */, 164 | EB83D51E180B2CA9001037F5 /* Products */, 165 | 86185DBC2499DA471C1B1266 /* Pods */, 166 | ); 167 | sourceTree = ""; 168 | }; 169 | EB83D51E180B2CA9001037F5 /* Products */ = { 170 | isa = PBXGroup; 171 | children = ( 172 | EB83D51D180B2CA9001037F5 /* SignupForm.app */, 173 | EB83D538180B2CA9001037F5 /* SignupFormTests.xctest */, 174 | ); 175 | name = Products; 176 | sourceTree = ""; 177 | }; 178 | EB83D51F180B2CA9001037F5 /* Frameworks */ = { 179 | isa = PBXGroup; 180 | children = ( 181 | EB83D520180B2CA9001037F5 /* Foundation.framework */, 182 | EB83D522180B2CA9001037F5 /* CoreGraphics.framework */, 183 | EB83D524180B2CA9001037F5 /* UIKit.framework */, 184 | EB83D539180B2CA9001037F5 /* XCTest.framework */, 185 | 90F90CF32A864DEC854302D2 /* libPods.a */, 186 | 51A99866C8474A4D9FA92F85 /* libPods-SignupFormTests.a */, 187 | ); 188 | name = Frameworks; 189 | sourceTree = ""; 190 | }; 191 | EB83D526180B2CA9001037F5 /* SignupForm */ = { 192 | isa = PBXGroup; 193 | children = ( 194 | EB59ED66180B2DB900DAA992 /* BZGFormViewController */, 195 | EB83D52F180B2CA9001037F5 /* AppDelegate.h */, 196 | EB83D530180B2CA9001037F5 /* AppDelegate.m */, 197 | EB59ED6D180B313000DAA992 /* SignupViewController.h */, 198 | EB59ED6E180B313000DAA992 /* SignupViewController.m */, 199 | EB83D532180B2CA9001037F5 /* Images.xcassets */, 200 | EB83D527180B2CA9001037F5 /* Supporting Files */, 201 | ); 202 | path = SignupForm; 203 | sourceTree = ""; 204 | }; 205 | EB83D527180B2CA9001037F5 /* Supporting Files */ = { 206 | isa = PBXGroup; 207 | children = ( 208 | EB83D528180B2CA9001037F5 /* SignupForm-Info.plist */, 209 | 3A7A8B271A145A120039AAD7 /* LaunchImage.xib */, 210 | EB83D529180B2CA9001037F5 /* InfoPlist.strings */, 211 | EB83D52C180B2CA9001037F5 /* main.m */, 212 | EB83D52E180B2CA9001037F5 /* SignupForm-Prefix.pch */, 213 | ); 214 | name = "Supporting Files"; 215 | sourceTree = ""; 216 | }; 217 | EB83D53F180B2CA9001037F5 /* SignupFormTests */ = { 218 | isa = PBXGroup; 219 | children = ( 220 | EB18C91B18DE62BC00F23064 /* BZGFormCellSpecs.m */, 221 | 3A1F56A818F432F90090E732 /* BZGKeyboardControlSpecs.m */, 222 | EB59ED55180B2D9200DAA992 /* BZGTextFieldCellSpecs.m */, 223 | EB9A0BEC190B18CF0085B5A8 /* BZGPhoneTextFieldCellSpec.m */, 224 | EB59ED70180B377900DAA992 /* BZGInfoCellSpecs.m */, 225 | EB59ED72180B378A00DAA992 /* BZGFormViewControllerSpecs.m */, 226 | EB83D540180B2CA9001037F5 /* Supporting Files */, 227 | ); 228 | path = SignupFormTests; 229 | sourceTree = ""; 230 | }; 231 | EB83D540180B2CA9001037F5 /* Supporting Files */ = { 232 | isa = PBXGroup; 233 | children = ( 234 | EB83D541180B2CA9001037F5 /* SignupFormTests-Info.plist */, 235 | EB83D542180B2CA9001037F5 /* InfoPlist.strings */, 236 | EB876CDE18D78408009544F9 /* SignupFormTests-Prefix.pch */, 237 | ); 238 | name = "Supporting Files"; 239 | sourceTree = ""; 240 | }; 241 | /* End PBXGroup section */ 242 | 243 | /* Begin PBXNativeTarget section */ 244 | EB83D51C180B2CA9001037F5 /* SignupForm */ = { 245 | isa = PBXNativeTarget; 246 | buildConfigurationList = EB83D549180B2CA9001037F5 /* Build configuration list for PBXNativeTarget "SignupForm" */; 247 | buildPhases = ( 248 | 7DD26754F29C4870A2544F40 /* Check Pods Manifest.lock */, 249 | EB83D519180B2CA9001037F5 /* Sources */, 250 | EB83D51A180B2CA9001037F5 /* Frameworks */, 251 | EB83D51B180B2CA9001037F5 /* Resources */, 252 | E54C409287014DF3BB675971 /* Copy Pods Resources */, 253 | ); 254 | buildRules = ( 255 | ); 256 | dependencies = ( 257 | ); 258 | name = SignupForm; 259 | productName = SignupForm; 260 | productReference = EB83D51D180B2CA9001037F5 /* SignupForm.app */; 261 | productType = "com.apple.product-type.application"; 262 | }; 263 | EB83D537180B2CA9001037F5 /* SignupFormTests */ = { 264 | isa = PBXNativeTarget; 265 | buildConfigurationList = EB83D54C180B2CA9001037F5 /* Build configuration list for PBXNativeTarget "SignupFormTests" */; 266 | buildPhases = ( 267 | CFFC48223DCA4B078FCDC2C5 /* Check Pods Manifest.lock */, 268 | EB83D534180B2CA9001037F5 /* Sources */, 269 | EB83D535180B2CA9001037F5 /* Frameworks */, 270 | EB83D536180B2CA9001037F5 /* Resources */, 271 | 38FD35EDAD0346D082EDFA0D /* Copy Pods Resources */, 272 | ); 273 | buildRules = ( 274 | ); 275 | dependencies = ( 276 | EB83D53E180B2CA9001037F5 /* PBXTargetDependency */, 277 | ); 278 | name = SignupFormTests; 279 | productName = SignupFormTests; 280 | productReference = EB83D538180B2CA9001037F5 /* SignupFormTests.xctest */; 281 | productType = "com.apple.product-type.bundle.unit-test"; 282 | }; 283 | /* End PBXNativeTarget section */ 284 | 285 | /* Begin PBXProject section */ 286 | EB83D515180B2CA9001037F5 /* Project object */ = { 287 | isa = PBXProject; 288 | attributes = { 289 | LastUpgradeCheck = 0510; 290 | ORGANIZATIONNAME = benzguo; 291 | TargetAttributes = { 292 | EB83D537180B2CA9001037F5 = { 293 | TestTargetID = EB83D51C180B2CA9001037F5; 294 | }; 295 | }; 296 | }; 297 | buildConfigurationList = EB83D518180B2CA9001037F5 /* Build configuration list for PBXProject "SignupForm" */; 298 | compatibilityVersion = "Xcode 3.2"; 299 | developmentRegion = English; 300 | hasScannedForEncodings = 0; 301 | knownRegions = ( 302 | en, 303 | ); 304 | mainGroup = EB83D514180B2CA9001037F5; 305 | productRefGroup = EB83D51E180B2CA9001037F5 /* Products */; 306 | projectDirPath = ""; 307 | projectRoot = ""; 308 | targets = ( 309 | EB83D51C180B2CA9001037F5 /* SignupForm */, 310 | EB83D537180B2CA9001037F5 /* SignupFormTests */, 311 | ); 312 | }; 313 | /* End PBXProject section */ 314 | 315 | /* Begin PBXResourcesBuildPhase section */ 316 | EB83D51B180B2CA9001037F5 /* Resources */ = { 317 | isa = PBXResourcesBuildPhase; 318 | buildActionMask = 2147483647; 319 | files = ( 320 | EB83D52B180B2CA9001037F5 /* InfoPlist.strings in Resources */, 321 | EB83D533180B2CA9001037F5 /* Images.xcassets in Resources */, 322 | 3A7A8B281A145A120039AAD7 /* LaunchImage.xib in Resources */, 323 | ); 324 | runOnlyForDeploymentPostprocessing = 0; 325 | }; 326 | EB83D536180B2CA9001037F5 /* Resources */ = { 327 | isa = PBXResourcesBuildPhase; 328 | buildActionMask = 2147483647; 329 | files = ( 330 | EB83D544180B2CA9001037F5 /* InfoPlist.strings in Resources */, 331 | ); 332 | runOnlyForDeploymentPostprocessing = 0; 333 | }; 334 | /* End PBXResourcesBuildPhase section */ 335 | 336 | /* Begin PBXShellScriptBuildPhase section */ 337 | 38FD35EDAD0346D082EDFA0D /* Copy Pods Resources */ = { 338 | isa = PBXShellScriptBuildPhase; 339 | buildActionMask = 2147483647; 340 | files = ( 341 | ); 342 | inputPaths = ( 343 | ); 344 | name = "Copy Pods Resources"; 345 | outputPaths = ( 346 | ); 347 | runOnlyForDeploymentPostprocessing = 0; 348 | shellPath = /bin/sh; 349 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-SignupFormTests/Pods-SignupFormTests-resources.sh\"\n"; 350 | showEnvVarsInLog = 0; 351 | }; 352 | 7DD26754F29C4870A2544F40 /* Check Pods Manifest.lock */ = { 353 | isa = PBXShellScriptBuildPhase; 354 | buildActionMask = 2147483647; 355 | files = ( 356 | ); 357 | inputPaths = ( 358 | ); 359 | name = "Check Pods Manifest.lock"; 360 | outputPaths = ( 361 | ); 362 | runOnlyForDeploymentPostprocessing = 0; 363 | shellPath = /bin/sh; 364 | shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; 365 | showEnvVarsInLog = 0; 366 | }; 367 | CFFC48223DCA4B078FCDC2C5 /* Check Pods Manifest.lock */ = { 368 | isa = PBXShellScriptBuildPhase; 369 | buildActionMask = 2147483647; 370 | files = ( 371 | ); 372 | inputPaths = ( 373 | ); 374 | name = "Check Pods Manifest.lock"; 375 | outputPaths = ( 376 | ); 377 | runOnlyForDeploymentPostprocessing = 0; 378 | shellPath = /bin/sh; 379 | shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; 380 | showEnvVarsInLog = 0; 381 | }; 382 | E54C409287014DF3BB675971 /* Copy Pods Resources */ = { 383 | isa = PBXShellScriptBuildPhase; 384 | buildActionMask = 2147483647; 385 | files = ( 386 | ); 387 | inputPaths = ( 388 | ); 389 | name = "Copy Pods Resources"; 390 | outputPaths = ( 391 | ); 392 | runOnlyForDeploymentPostprocessing = 0; 393 | shellPath = /bin/sh; 394 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-resources.sh\"\n"; 395 | showEnvVarsInLog = 0; 396 | }; 397 | /* End PBXShellScriptBuildPhase section */ 398 | 399 | /* Begin PBXSourcesBuildPhase section */ 400 | EB83D519180B2CA9001037F5 /* Sources */ = { 401 | isa = PBXSourcesBuildPhase; 402 | buildActionMask = 2147483647; 403 | files = ( 404 | EB18C91A18DE628500F23064 /* BZGFormCell.m in Sources */, 405 | EB59ED6C180B2F0800DAA992 /* BZGFormViewController.m in Sources */, 406 | EB59ED63180B2DB100DAA992 /* BZGTextFieldCell.m in Sources */, 407 | EB59ED64180B2DB100DAA992 /* BZGInfoCell.m in Sources */, 408 | 8CE100451965E4DD00287E0B /* NSError+BZGFormViewController.m in Sources */, 409 | 3A1F56A718F388B40090E732 /* BZGKeyboardControl.m in Sources */, 410 | EB83D531180B2CA9001037F5 /* AppDelegate.m in Sources */, 411 | EB83D52D180B2CA9001037F5 /* main.m in Sources */, 412 | EB59ED6F180B313000DAA992 /* SignupViewController.m in Sources */, 413 | EB102241190B078E00F973F0 /* BZGPhoneTextFieldCell.m in Sources */, 414 | ); 415 | runOnlyForDeploymentPostprocessing = 0; 416 | }; 417 | EB83D534180B2CA9001037F5 /* Sources */ = { 418 | isa = PBXSourcesBuildPhase; 419 | buildActionMask = 2147483647; 420 | files = ( 421 | 3A1F56A918F432F90090E732 /* BZGKeyboardControlSpecs.m in Sources */, 422 | EB59ED58180B2D9200DAA992 /* BZGTextFieldCellSpecs.m in Sources */, 423 | EB59ED73180B378A00DAA992 /* BZGFormViewControllerSpecs.m in Sources */, 424 | EB18C91C18DE62BC00F23064 /* BZGFormCellSpecs.m in Sources */, 425 | EB59ED71180B377900DAA992 /* BZGInfoCellSpecs.m in Sources */, 426 | EB9A0BED190B18CF0085B5A8 /* BZGPhoneTextFieldCellSpec.m in Sources */, 427 | ); 428 | runOnlyForDeploymentPostprocessing = 0; 429 | }; 430 | /* End PBXSourcesBuildPhase section */ 431 | 432 | /* Begin PBXTargetDependency section */ 433 | EB83D53E180B2CA9001037F5 /* PBXTargetDependency */ = { 434 | isa = PBXTargetDependency; 435 | target = EB83D51C180B2CA9001037F5 /* SignupForm */; 436 | targetProxy = EB83D53D180B2CA9001037F5 /* PBXContainerItemProxy */; 437 | }; 438 | /* End PBXTargetDependency section */ 439 | 440 | /* Begin PBXVariantGroup section */ 441 | EB83D529180B2CA9001037F5 /* InfoPlist.strings */ = { 442 | isa = PBXVariantGroup; 443 | children = ( 444 | EB83D52A180B2CA9001037F5 /* en */, 445 | ); 446 | name = InfoPlist.strings; 447 | sourceTree = ""; 448 | }; 449 | EB83D542180B2CA9001037F5 /* InfoPlist.strings */ = { 450 | isa = PBXVariantGroup; 451 | children = ( 452 | EB83D543180B2CA9001037F5 /* en */, 453 | ); 454 | name = InfoPlist.strings; 455 | sourceTree = ""; 456 | }; 457 | /* End PBXVariantGroup section */ 458 | 459 | /* Begin XCBuildConfiguration section */ 460 | EB83D547180B2CA9001037F5 /* Debug */ = { 461 | isa = XCBuildConfiguration; 462 | buildSettings = { 463 | ALWAYS_SEARCH_USER_PATHS = NO; 464 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 465 | CLANG_CXX_LIBRARY = "libc++"; 466 | CLANG_ENABLE_MODULES = YES; 467 | CLANG_ENABLE_OBJC_ARC = YES; 468 | CLANG_WARN_BOOL_CONVERSION = YES; 469 | CLANG_WARN_CONSTANT_CONVERSION = YES; 470 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 471 | CLANG_WARN_EMPTY_BODY = YES; 472 | CLANG_WARN_ENUM_CONVERSION = YES; 473 | CLANG_WARN_INT_CONVERSION = YES; 474 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 475 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 476 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 477 | COPY_PHASE_STRIP = NO; 478 | GCC_C_LANGUAGE_STANDARD = gnu99; 479 | GCC_DYNAMIC_NO_PIC = NO; 480 | GCC_OPTIMIZATION_LEVEL = 0; 481 | GCC_PREPROCESSOR_DEFINITIONS = ( 482 | "DEBUG=1", 483 | "$(inherited)", 484 | ); 485 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 486 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 487 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 488 | GCC_WARN_UNDECLARED_SELECTOR = YES; 489 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 490 | GCC_WARN_UNUSED_FUNCTION = YES; 491 | GCC_WARN_UNUSED_VARIABLE = YES; 492 | IPHONEOS_DEPLOYMENT_TARGET = 6.0; 493 | ONLY_ACTIVE_ARCH = YES; 494 | SDKROOT = iphoneos; 495 | }; 496 | name = Debug; 497 | }; 498 | EB83D548180B2CA9001037F5 /* Release */ = { 499 | isa = XCBuildConfiguration; 500 | buildSettings = { 501 | ALWAYS_SEARCH_USER_PATHS = NO; 502 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 503 | CLANG_CXX_LIBRARY = "libc++"; 504 | CLANG_ENABLE_MODULES = YES; 505 | CLANG_ENABLE_OBJC_ARC = YES; 506 | CLANG_WARN_BOOL_CONVERSION = YES; 507 | CLANG_WARN_CONSTANT_CONVERSION = YES; 508 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 509 | CLANG_WARN_EMPTY_BODY = YES; 510 | CLANG_WARN_ENUM_CONVERSION = YES; 511 | CLANG_WARN_INT_CONVERSION = YES; 512 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 513 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 514 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 515 | COPY_PHASE_STRIP = YES; 516 | ENABLE_NS_ASSERTIONS = NO; 517 | GCC_C_LANGUAGE_STANDARD = gnu99; 518 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 519 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 520 | GCC_WARN_UNDECLARED_SELECTOR = YES; 521 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 522 | GCC_WARN_UNUSED_FUNCTION = YES; 523 | GCC_WARN_UNUSED_VARIABLE = YES; 524 | IPHONEOS_DEPLOYMENT_TARGET = 6.0; 525 | SDKROOT = iphoneos; 526 | VALIDATE_PRODUCT = YES; 527 | }; 528 | name = Release; 529 | }; 530 | EB83D54A180B2CA9001037F5 /* Debug */ = { 531 | isa = XCBuildConfiguration; 532 | baseConfigurationReference = 637FF53EA385C8AADC43AB6C /* Pods.debug.xcconfig */; 533 | buildSettings = { 534 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 535 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 536 | GCC_GENERATE_TEST_COVERAGE_FILES = YES; 537 | GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = YES; 538 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 539 | GCC_PREFIX_HEADER = "SignupForm/SignupForm-Prefix.pch"; 540 | INFOPLIST_FILE = "SignupForm/SignupForm-Info.plist"; 541 | IPHONEOS_DEPLOYMENT_TARGET = 7.1; 542 | PRODUCT_NAME = "$(TARGET_NAME)"; 543 | TARGETED_DEVICE_FAMILY = "1,2"; 544 | WRAPPER_EXTENSION = app; 545 | }; 546 | name = Debug; 547 | }; 548 | EB83D54B180B2CA9001037F5 /* Release */ = { 549 | isa = XCBuildConfiguration; 550 | baseConfigurationReference = 75D592C1258E247EE1D17150 /* Pods.release.xcconfig */; 551 | buildSettings = { 552 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 553 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 554 | GCC_GENERATE_TEST_COVERAGE_FILES = YES; 555 | GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = YES; 556 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 557 | GCC_PREFIX_HEADER = "SignupForm/SignupForm-Prefix.pch"; 558 | INFOPLIST_FILE = "SignupForm/SignupForm-Info.plist"; 559 | IPHONEOS_DEPLOYMENT_TARGET = 7.1; 560 | PRODUCT_NAME = "$(TARGET_NAME)"; 561 | TARGETED_DEVICE_FAMILY = "1,2"; 562 | WRAPPER_EXTENSION = app; 563 | }; 564 | name = Release; 565 | }; 566 | EB83D54D180B2CA9001037F5 /* Debug */ = { 567 | isa = XCBuildConfiguration; 568 | baseConfigurationReference = 598A47BFA37AFF1A218D4E27 /* Pods-SignupFormTests.debug.xcconfig */; 569 | buildSettings = { 570 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/SignupForm.app/SignupForm"; 571 | CLANG_ENABLE_MODULES = NO; 572 | FRAMEWORK_SEARCH_PATHS = ( 573 | "$(SDKROOT)/Developer/Library/Frameworks", 574 | "$(inherited)", 575 | "$(DEVELOPER_FRAMEWORKS_DIR)", 576 | ); 577 | GCC_GENERATE_TEST_COVERAGE_FILES = YES; 578 | GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = YES; 579 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 580 | GCC_PREFIX_HEADER = "SignupFormTests/SignupFormTests-Prefix.pch"; 581 | GCC_PREPROCESSOR_DEFINITIONS = ( 582 | "DEBUG=1", 583 | "$(inherited)", 584 | ); 585 | INFOPLIST_FILE = "SignupFormTests/SignupFormTests-Info.plist"; 586 | PRODUCT_NAME = "$(TARGET_NAME)"; 587 | TEST_HOST = "$(BUNDLE_LOADER)"; 588 | WRAPPER_EXTENSION = xctest; 589 | }; 590 | name = Debug; 591 | }; 592 | EB83D54E180B2CA9001037F5 /* Release */ = { 593 | isa = XCBuildConfiguration; 594 | baseConfigurationReference = 2AE1260CFD537C6C57DF2D2E /* Pods-SignupFormTests.release.xcconfig */; 595 | buildSettings = { 596 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/SignupForm.app/SignupForm"; 597 | CLANG_ENABLE_MODULES = NO; 598 | FRAMEWORK_SEARCH_PATHS = ( 599 | "$(SDKROOT)/Developer/Library/Frameworks", 600 | "$(inherited)", 601 | "$(DEVELOPER_FRAMEWORKS_DIR)", 602 | ); 603 | GCC_GENERATE_TEST_COVERAGE_FILES = YES; 604 | GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = YES; 605 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 606 | GCC_PREFIX_HEADER = "SignupFormTests/SignupFormTests-Prefix.pch"; 607 | INFOPLIST_FILE = "SignupFormTests/SignupFormTests-Info.plist"; 608 | PRODUCT_NAME = "$(TARGET_NAME)"; 609 | TEST_HOST = "$(BUNDLE_LOADER)"; 610 | WRAPPER_EXTENSION = xctest; 611 | }; 612 | name = Release; 613 | }; 614 | /* End XCBuildConfiguration section */ 615 | 616 | /* Begin XCConfigurationList section */ 617 | EB83D518180B2CA9001037F5 /* Build configuration list for PBXProject "SignupForm" */ = { 618 | isa = XCConfigurationList; 619 | buildConfigurations = ( 620 | EB83D547180B2CA9001037F5 /* Debug */, 621 | EB83D548180B2CA9001037F5 /* Release */, 622 | ); 623 | defaultConfigurationIsVisible = 0; 624 | defaultConfigurationName = Release; 625 | }; 626 | EB83D549180B2CA9001037F5 /* Build configuration list for PBXNativeTarget "SignupForm" */ = { 627 | isa = XCConfigurationList; 628 | buildConfigurations = ( 629 | EB83D54A180B2CA9001037F5 /* Debug */, 630 | EB83D54B180B2CA9001037F5 /* Release */, 631 | ); 632 | defaultConfigurationIsVisible = 0; 633 | defaultConfigurationName = Release; 634 | }; 635 | EB83D54C180B2CA9001037F5 /* Build configuration list for PBXNativeTarget "SignupFormTests" */ = { 636 | isa = XCConfigurationList; 637 | buildConfigurations = ( 638 | EB83D54D180B2CA9001037F5 /* Debug */, 639 | EB83D54E180B2CA9001037F5 /* Release */, 640 | ); 641 | defaultConfigurationIsVisible = 0; 642 | defaultConfigurationName = Release; 643 | }; 644 | /* End XCConfigurationList section */ 645 | }; 646 | rootObject = EB83D515180B2CA9001037F5 /* Project object */; 647 | } 648 | -------------------------------------------------------------------------------- /SignupForm/SignupForm.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SignupForm/SignupForm.xcodeproj/xcshareddata/xcschemes/SignupForm.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 47 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 65 | 66 | 75 | 76 | 82 | 83 | 84 | 85 | 86 | 87 | 93 | 94 | 100 | 101 | 102 | 103 | 105 | 106 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /SignupForm/SignupForm.xcodeproj/xcshareddata/xcschemes/SignupFormTests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 14 | 15 | 17 | 23 | 24 | 25 | 26 | 27 | 36 | 37 | 38 | 39 | 45 | 46 | 48 | 49 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /SignupForm/SignupForm.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /SignupForm/SignupForm/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // 4 | // https://github.com/benzguo/BZGFormViewController 5 | // 6 | 7 | #import 8 | 9 | @interface AppDelegate : UIResponder 10 | 11 | @property (strong, nonatomic) UIWindow *window; 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /SignupForm/SignupForm/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // 4 | // https://github.com/benzguo/BZGFormViewController 5 | // 6 | 7 | #import "AppDelegate.h" 8 | #import "SignupViewController.h" 9 | 10 | @implementation AppDelegate 11 | 12 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 13 | { 14 | self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; 15 | SignupViewController *signupVC = [[SignupViewController alloc] initWithStyle:UITableViewStylePlain]; 16 | UINavigationController *nc = [[UINavigationController alloc] initWithRootViewController:signupVC]; 17 | nc.navigationBar.translucent = NO; 18 | self.window.rootViewController = nc; 19 | [self.window makeKeyAndVisible]; 20 | return YES; 21 | } 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /SignupForm/SignupForm/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "40x40", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "60x60", 16 | "scale" : "2x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /SignupForm/SignupForm/Images.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "orientation" : "portrait", 5 | "idiom" : "iphone", 6 | "extent" : "full-screen", 7 | "minimum-system-version" : "7.0", 8 | "scale" : "2x" 9 | }, 10 | { 11 | "orientation" : "portrait", 12 | "idiom" : "iphone", 13 | "subtype" : "retina4", 14 | "extent" : "full-screen", 15 | "minimum-system-version" : "7.0", 16 | "scale" : "2x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /SignupForm/SignupForm/LaunchImage.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /SignupForm/SignupForm/SignupForm-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIdentifier 12 | com.benzguo.${PRODUCT_NAME:rfc1034identifier} 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1.0 25 | LSRequiresIPhoneOS 26 | 27 | UILaunchStoryboardName 28 | LaunchImage 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | UIViewControllerBasedStatusBarAppearance 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /SignupForm/SignupForm/SignupForm-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header 3 | // 4 | // The contents of this file are implicitly included at the beginning of every source file. 5 | // 6 | 7 | #import 8 | 9 | #ifndef __IPHONE_3_0 10 | #warning "This project uses features only available in iOS SDK 3.0 and later." 11 | #endif 12 | 13 | #ifdef __OBJC__ 14 | #import 15 | #import 16 | #endif 17 | -------------------------------------------------------------------------------- /SignupForm/SignupForm/SignupViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // SignupViewController.h 3 | // 4 | // https://github.com/benzguo/BZGFormViewController 5 | // 6 | 7 | #import "BZGFormViewController.h" 8 | 9 | @class BZGMailgunEmailValidator; 10 | 11 | @interface SignupViewController : BZGFormViewController 12 | 13 | @property (nonatomic, strong) BZGTextFieldCell *usernameCell; 14 | @property (nonatomic, strong) BZGTextFieldCell *emailCell; 15 | @property (nonatomic, strong) BZGPhoneTextFieldCell *phoneCell; 16 | @property (nonatomic, strong) BZGTextFieldCell *passwordCell; 17 | 18 | @property (nonatomic, strong) BZGMailgunEmailValidator *emailValidator; 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /SignupForm/SignupForm/SignupViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // SignupViewController.m 3 | // 4 | // https://github.com/benzguo/BZGFormViewController 5 | // 6 | 7 | #import "SignupViewController.h" 8 | #import "BZGTextFieldCell.h" 9 | #import "BZGPhoneTextFieldCell.h" 10 | #import "BZGMailgunEmailValidator.h" 11 | #import "ReactiveCocoa.h" 12 | #import "EXTScope.h" 13 | #import "Constants.h" 14 | 15 | static NSString *const MAILGUN_PUBLIC_KEY = @"pubkey-501jygdalut926-6mb1ozo8ay9crlc28"; 16 | 17 | typedef NS_ENUM(NSInteger, SignupViewControllerSection) { 18 | SignupViewControllerSectionPrimaryInfo, 19 | SignupViewControllerSectionSecondaryInfo, 20 | SignupViewControllerSectionSignUpButton, 21 | SignupViewControllerSectionCount 22 | }; 23 | 24 | @interface SignupViewController () 25 | 26 | @property (strong, nonatomic) UITableViewCell *signupCell; 27 | 28 | @end 29 | 30 | @implementation SignupViewController 31 | 32 | - (void)viewDidLoad 33 | { 34 | [super viewDidLoad]; 35 | [self configureUsernameCell]; 36 | [self configureEmailCell]; 37 | [self configurePhoneCell]; 38 | [self configurePasswordCell]; 39 | 40 | [self addFormCells:@[self.usernameCell, self.emailCell, self.passwordCell] atSection:SignupViewControllerSectionPrimaryInfo]; 41 | [self addFormCells:@[self.phoneCell] atSection:SignupViewControllerSectionSecondaryInfo]; 42 | self.emailValidator = [BZGMailgunEmailValidator validatorWithPublicKey:MAILGUN_PUBLIC_KEY]; 43 | self.showsKeyboardControl = YES; 44 | self.title = @"BZGFormViewController"; 45 | self.tableView.tableFooterView = [UIView new]; 46 | 47 | [self.usernameCell becomeFirstResponder]; 48 | } 49 | 50 | - (void)configureUsernameCell 51 | { 52 | self.usernameCell = [BZGTextFieldCell new]; 53 | self.usernameCell.label.text = @"Username"; 54 | self.usernameCell.textField.placeholder = @"username"; 55 | self.usernameCell.textField.keyboardType = UIKeyboardTypeASCIICapable; 56 | self.usernameCell.shouldChangeTextBlock = ^BOOL(BZGTextFieldCell *cell, NSString *newText) { 57 | if (newText.length < 5) { 58 | cell.validationState = BZGValidationStateInvalid; 59 | [cell.infoCell setText:@"Username must be at least 5 characters long."]; 60 | } else { 61 | cell.validationState = BZGValidationStateValid; 62 | } 63 | return YES; 64 | }; 65 | } 66 | 67 | - (void)configureEmailCell 68 | { 69 | self.emailCell = [BZGTextFieldCell new]; 70 | self.emailCell.label.text = @"Email"; 71 | self.emailCell.textField.placeholder = @"name@example.com"; 72 | self.emailCell.textField.keyboardType = UIKeyboardTypeEmailAddress; 73 | @weakify(self) 74 | self.emailCell.didEndEditingBlock = ^(BZGTextFieldCell *cell, NSString *text) { 75 | @strongify(self); 76 | if (text.length == 0) { 77 | cell.validationState = BZGValidationStateNone; 78 | [self updateInfoCellBelowFormCell:cell]; 79 | return; 80 | } 81 | cell.validationState = BZGValidationStateValidating; 82 | [self.emailValidator validateEmailAddress:self.emailCell.textField.text 83 | success:^(BOOL isValid, NSString *didYouMean) { 84 | if (isValid) { 85 | cell.validationState = BZGValidationStateValid; 86 | } else { 87 | cell.validationState = BZGValidationStateInvalid; 88 | [cell.infoCell setText:@"Email address is invalid."]; 89 | } 90 | if (didYouMean) { 91 | cell.validationState = BZGValidationStateWarning; 92 | [cell.infoCell setText:[NSString stringWithFormat:@"Did you mean %@?", didYouMean]]; 93 | @weakify(cell); 94 | @weakify(self); 95 | [cell.infoCell setTapGestureBlock:^{ 96 | @strongify(cell); 97 | @strongify(self); 98 | [cell.textField setText:didYouMean]; 99 | [self textFieldDidEndEditing:cell.textField]; 100 | }]; 101 | } else { 102 | [cell.infoCell setTapGestureBlock:nil]; 103 | } 104 | [self updateInfoCellBelowFormCell:cell]; 105 | } failure:^(NSError *error) { 106 | cell.validationState = BZGValidationStateNone; 107 | [self updateInfoCellBelowFormCell:cell]; 108 | }]; 109 | }; 110 | } 111 | 112 | - (void)configurePhoneCell 113 | { 114 | self.phoneCell = [BZGPhoneTextFieldCell new]; 115 | self.phoneCell.label.text = @"Phone"; 116 | self.phoneCell.textField.placeholder = @"(555) 555-2016"; 117 | } 118 | 119 | - (void)configurePasswordCell 120 | { 121 | self.passwordCell = [BZGTextFieldCell new]; 122 | self.passwordCell.label.text = @"Password"; 123 | self.passwordCell.textField.placeholder = @"••••••••"; 124 | self.passwordCell.textField.keyboardType = UIKeyboardTypeASCIICapable; 125 | self.passwordCell.textField.secureTextEntry = YES; 126 | self.passwordCell.shouldChangeTextBlock = ^BOOL(BZGTextFieldCell *cell, NSString *text) { 127 | cell.validationState = BZGValidationStateNone; 128 | if (text.length < 8) { 129 | cell.validationState = BZGValidationStateInvalid; 130 | [cell.infoCell setText:@"Password must be at least 8 characters long."]; 131 | } else { 132 | cell.validationState = BZGValidationStateValid; 133 | } 134 | return YES; 135 | }; 136 | } 137 | 138 | #pragma mark - UITableViewDataSource 139 | 140 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView 141 | { 142 | return SignupViewControllerSectionCount; 143 | } 144 | 145 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 146 | { 147 | if (section == SignupViewControllerSectionSignUpButton) { 148 | return 1; 149 | } else { 150 | return [super tableView:tableView numberOfRowsInSection:section]; 151 | } 152 | } 153 | 154 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 155 | { 156 | if (indexPath.section == SignupViewControllerSectionSignUpButton) { 157 | return self.signupCell; 158 | } else { 159 | return [super tableView:tableView cellForRowAtIndexPath:indexPath]; 160 | } 161 | } 162 | 163 | - (UITableViewCell *)signupCell 164 | { 165 | UITableViewCell *cell = _signupCell; 166 | if (!cell) { 167 | cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil]; 168 | cell.textLabel.text = @"Sign Up"; 169 | cell.textLabel.textAlignment = NSTextAlignmentCenter; 170 | RAC(cell, selectionStyle) = 171 | [RACObserve(self, isValid) map:^NSNumber *(NSNumber *isValid) { 172 | return isValid.boolValue ? @(UITableViewCellSelectionStyleDefault) : @(UITableViewCellSelectionStyleNone); 173 | }]; 174 | 175 | RAC(cell.textLabel, textColor) = 176 | [RACObserve(self, isValid) map:^UIColor *(NSNumber *isValid) { 177 | return isValid.boolValue ? BZG_BLUE_COLOR : [UIColor lightGrayColor]; 178 | }]; 179 | 180 | cell.selectionStyle = UITableViewCellSelectionStyleNone; 181 | cell.textLabel.textColor = [UIColor lightGrayColor]; 182 | } 183 | return cell; 184 | } 185 | 186 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath 187 | { 188 | [self.tableView deselectRowAtIndexPath:indexPath animated:YES]; 189 | } 190 | 191 | - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath 192 | { 193 | if (indexPath.section == SignupViewControllerSectionSignUpButton) { 194 | return 44; 195 | } else { 196 | return [super tableView:tableView heightForRowAtIndexPath:indexPath]; 197 | } 198 | } 199 | 200 | - (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section 201 | { 202 | if (section == SignupViewControllerSectionSecondaryInfo) { 203 | UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, tableView.frame.size.width, 50)]; 204 | label.text = @"Secondary Info"; 205 | label.textAlignment = NSTextAlignmentCenter; 206 | return label; 207 | } else { 208 | return nil; 209 | } 210 | } 211 | 212 | - (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section 213 | { 214 | if (section == SignupViewControllerSectionSecondaryInfo) { 215 | return 50; 216 | } else { 217 | return 0; 218 | } 219 | } 220 | 221 | @end 222 | -------------------------------------------------------------------------------- /SignupForm/SignupForm/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /SignupForm/SignupForm/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // SignupForm 4 | // 5 | // Created by Ben Guo on 10/13/13. 6 | // Copyright (c) 2013 benzguo. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "AppDelegate.h" 12 | 13 | int main(int argc, char * argv[]) 14 | { 15 | @autoreleasepool { 16 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /SignupForm/SignupFormTests/SignupFormTests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | com.benzguo.${PRODUCT_NAME:rfc1034identifier} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /SignupForm/SignupFormTests/SignupFormTests-Prefix.pch: -------------------------------------------------------------------------------- 1 | #ifndef SignupFormTests_Prefix_pch 2 | #define SignupFormTests_Prefix_pch 3 | 4 | #import 5 | 6 | #define HC_SHORTHAND 7 | #import 8 | 9 | #import 10 | 11 | #define EXP_SHORTHAND 12 | #import 13 | 14 | #define MOCKITO_SHORTHAND 15 | #import 16 | 17 | #import 18 | 19 | #import "Constants.h" 20 | 21 | #endif 22 | -------------------------------------------------------------------------------- /SignupForm/SignupFormTests/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | --------------------------------------------------------------------------------