├── .gitignore
├── SignupForm
├── SignupForm
│ ├── en.lproj
│ │ └── InfoPlist.strings
│ ├── AppDelegate.h
│ ├── main.m
│ ├── SignupForm-Prefix.pch
│ ├── Images.xcassets
│ │ ├── AppIcon.appiconset
│ │ │ └── Contents.json
│ │ └── LaunchImage.launchimage
│ │ │ └── Contents.json
│ ├── SignupViewController.h
│ ├── AppDelegate.m
│ ├── LaunchImage.xib
│ ├── SignupForm-Info.plist
│ └── SignupViewController.m
├── SignupFormTests
│ ├── en.lproj
│ │ └── InfoPlist.strings
│ ├── SignupFormTests-Prefix.pch
│ └── SignupFormTests-Info.plist
├── SignupForm.xcworkspace
│ └── contents.xcworkspacedata
├── SignupForm.xcodeproj
│ ├── project.xcworkspace
│ │ └── contents.xcworkspacedata
│ ├── xcshareddata
│ │ └── xcschemes
│ │ │ ├── SignupFormTests.xcscheme
│ │ │ └── SignupForm.xcscheme
│ └── project.pbxproj
├── Podfile
└── Podfile.lock
├── Screenshots
├── SignupForm.gif
├── SignupForm.png
├── SignupForm@2x.gif
└── SignupForm@2x.png
├── BZGFormViewController
├── NSError+BZGFormViewController.h
├── NSError+BZGFormViewController.m
├── BZGPhoneTextFieldCell.h
├── BZGKeyboardControl.h
├── BZGInfoCell.h
├── BZGFormCell.h
├── BZGFormCell.m
├── Constants.h
├── BZGTextFieldCell.h
├── BZGKeyboardControl.m
├── BZGInfoCell.m
├── BZGPhoneTextFieldCell.m
├── BZGFormViewController.h
├── BZGTextFieldCell.m
└── BZGFormViewController.m
├── .travis.yml
├── BZGFormViewController.podspec
├── LICENSE
├── BZGFormViewControllerTests
├── BZGFormCellSpecs.m
├── BZGInfoCellSpecs.m
├── BZGKeyboardControlSpecs.m
├── BZGPhoneTextFieldCellSpec.m
├── BZGTextFieldCellSpecs.m
└── BZGFormViewControllerSpecs.m
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | xcuserdata
3 | SignupForm/Pods/*
4 | *.xccheckout
5 |
--------------------------------------------------------------------------------
/SignupForm/SignupForm/en.lproj/InfoPlist.strings:
--------------------------------------------------------------------------------
1 | /* Localized versions of Info.plist keys */
2 |
3 |
--------------------------------------------------------------------------------
/SignupForm/SignupFormTests/en.lproj/InfoPlist.strings:
--------------------------------------------------------------------------------
1 | /* Localized versions of Info.plist keys */
2 |
3 |
--------------------------------------------------------------------------------
/Screenshots/SignupForm.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0thernet/BZGFormViewController/HEAD/Screenshots/SignupForm.gif
--------------------------------------------------------------------------------
/Screenshots/SignupForm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0thernet/BZGFormViewController/HEAD/Screenshots/SignupForm.png
--------------------------------------------------------------------------------
/Screenshots/SignupForm@2x.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0thernet/BZGFormViewController/HEAD/Screenshots/SignupForm@2x.gif
--------------------------------------------------------------------------------
/Screenshots/SignupForm@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0thernet/BZGFormViewController/HEAD/Screenshots/SignupForm@2x.png
--------------------------------------------------------------------------------
/SignupForm/SignupForm.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/BZGFormViewController/NSError+BZGFormViewController.h:
--------------------------------------------------------------------------------
1 | #import
2 |
3 | @interface NSError (BZGFormViewController)
4 |
5 | + (NSError *)bzg_errorWithDescription:(NSString *)description;
6 |
7 | @end
8 |
--------------------------------------------------------------------------------
/SignupForm/SignupForm.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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/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/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 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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/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.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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2013 Ben Guo
2 |
3 | Permission is hereby granted, free of charge, to any person
4 | obtaining a copy of this software and associated documentation
5 | files (the "Software"), to deal in the Software without
6 | restriction, including without limitation the rights to use,
7 | copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the
9 | Software is furnished to do so, subject to the following
10 | conditions:
11 |
12 | The above copyright notice and this permission notice shall be
13 | included in all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/SignupForm/SignupForm/LaunchImage.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | BZGFormViewController [](https://travis-ci.org/benzguo/BZGFormViewController)
2 | =====================
3 |
4 | BZGFormViewController is a simple library for making dynamic forms.
5 |
6 | 
7 |
8 | ## Demo app
9 | Navigate to `SignupForm`, run `pod install`, and open `SignupForm.xcworkspace`. Build and run to see `BZGFormViewController` in action.
10 |
11 | ## Installation
12 |
13 | Cocoapods is the recommended method of installing `BZGFormViewController`. Add the following line to your `Podfile`:
14 |
15 | ```ruby
16 | pod `BZGFormViewController`
17 | ```
18 |
19 | ## Quick start
20 |
21 | First, subclass `BZGFormViewController`.
22 | ```objc
23 | @interface SignupViewController : BZGFormViewController
24 | ```
25 | Next, import "BZGTextFieldCell.h" and create a cell.
26 | ```objc
27 | #import "BZGTextFieldCell.h"
28 |
29 | // ...
30 |
31 | self.usernameCell = [BZGTextFieldCell new];
32 | self.usernameCell.label.text = @"Username";
33 | ```
34 | To validate text and update the cell when the cell's text changes, use the cell's `shouldChangeTextBlock`.
35 | ```objc
36 | self.usernameCell.shouldChangeTextBlock = ^BOOL(BZGTextFieldCell *cell, NSString *newText) {
37 | if (newText.length < 5) {
38 | cell.validationState = BZGValidationStateInvalid;
39 | } else {
40 | cell.validationState = BZGValidationStateValid;
41 | }
42 | return YES;
43 | };
44 | ```
45 | Each `BZGTextFieldCell` contains a `BZGInfoCell`. The info cell will be displayed if the cell's `validationState` is either `BZGValidationStateInvalid` or `BZGValidationStateWarning`. You can set the info cell's text using `setText:`.
46 | ```objc
47 | self.usernameCell.shouldChangeTextBlock = ^BOOL(BZGTextFieldCell *cell, NSString *newText) {
48 | if (newText.length < 5) {
49 | cell.validationState = BZGValidationStateInvalid;
50 | [cell.infoCell setText:@"Username must be at least 5 characters long."];
51 | } else {
52 | cell.validationState = BZGValidationStateValid;
53 | }
54 | return YES;
55 | };
56 | ```
57 | `BZGFormViewController` automatically scrolls the tableview when you begin editing a text field and moves to the next field when you hit return.
58 |
59 | You should use `BZGTextFieldCell`'s `shouldChangeTextBlock`, `didBeginEditingBlock`, `didEndEditingBlock`, and `shouldReturnBlock` for validation and any other logic you would usually put in `UITextFieldDelegate` methods.
60 |
61 | After you've configured your cells, add them into the desired section
62 | ```objc
63 | [self addFormCells:@[self.usernameCell, self.emailCell, self.passwordCell]
64 | atSection:0];
65 | [self addFormCells:@[self.phoneCell] atSection:1];
66 |
67 | ```
68 |
69 | ## Custom sections
70 | `BZGFormViewController` will only manage sections containing form cells. If you'd like to have other sections containing custom cells, you'll have to manage them yourself via the `UITableViewDataSource` methods. Be sure to use the values from `super` for the table view's form section.
71 | ```objc
72 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
73 | {
74 | return 3;
75 | }
76 |
77 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
78 | {
79 | if (section > 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 |
--------------------------------------------------------------------------------
/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
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 |
--------------------------------------------------------------------------------
/BZGFormViewControllerTests/BZGTextFieldCellSpecs.m:
--------------------------------------------------------------------------------
1 | //
2 | // BZGTextFieldCellSpecs.m
3 | //
4 | // https://github.com/benzguo/BZGFormViewController
5 | //
6 |
7 | #import "BZGTextFieldCell.h"
8 | #import "NSError+BZGFormViewController.h"
9 |
10 | BZGTextFieldCell *cell;
11 |
12 | SpecBegin(BZGTextFieldCell)
13 |
14 | before(^{
15 | cell = [[BZGTextFieldCell alloc] init];
16 | });
17 |
18 | after(^{
19 | cell = nil;
20 | });
21 |
22 | describe(@"Initialization", ^{
23 | it(@"should initialize with correct defaults", ^{
24 | expect(cell.selectionStyle).to.equal(UITableViewCellSelectionStyleNone);
25 | expect(cell.validationState).to.equal(BZGValidationStateNone);
26 | expect(cell.textField.textColor).to.equal(BZG_TEXTFIELD_NORMAL_COLOR);
27 | expect(cell.activityIndicatorView.hidden).to.beTruthy;
28 | expect(cell.accessoryType).to.equal(UITableViewCellAccessoryNone);
29 | expect(cell.showsCheckmarkWhenValid).to.equal(YES);
30 | expect(cell.showsValidationWhileEditing).to.equal(NO);
31 | });
32 | });
33 |
34 | describe(@"Validation states", ^{
35 | it(@"should behave correctly when state is BZGValidationStateValid", ^{
36 | cell.validationState = BZGValidationStateValid;
37 | expect(cell.validationState).to.equal(BZGValidationStateValid);
38 | expect(cell.textField.textColor).to.equal(BZG_TEXTFIELD_NORMAL_COLOR);
39 | expect(cell.activityIndicatorView.hidden).to.beTruthy;
40 | expect(cell.accessoryType).to.equal(UITableViewCellAccessoryCheckmark);
41 | });
42 |
43 | it(@"should behave correctly when state is BZGValidationStateInvalid", ^{
44 | cell.validationState = BZGValidationStateInvalid;
45 | expect(cell.validationState).to.equal(BZGValidationStateInvalid);
46 | expect(cell.textField.textColor).to.equal(BZG_TEXTFIELD_INVALID_COLOR);
47 | expect(cell.activityIndicatorView.hidden).to.beTruthy;
48 | expect(cell.accessoryType).to.equal(UITableViewCellAccessoryNone);
49 | });
50 |
51 | it(@"should behave correctly when state is BZGValidationStateWarning", ^{
52 | cell.validationState = BZGValidationStateWarning;
53 | expect(cell.validationState).to.equal(BZGValidationStateWarning);
54 | expect(cell.textField.textColor).to.equal(BZG_TEXTFIELD_NORMAL_COLOR);
55 | expect(cell.activityIndicatorView.hidden).to.beTruthy;
56 | expect(cell.accessoryType).to.equal(UITableViewCellAccessoryNone);
57 | });
58 |
59 | it(@"should behave correctly when state is BZGValidationStateNone", ^{
60 | cell.validationState = BZGValidationStateNone;
61 | expect(cell.validationState).to.equal(BZGValidationStateNone);
62 | expect(cell.textField.textColor).to.equal(BZG_TEXTFIELD_NORMAL_COLOR);
63 | expect(cell.activityIndicatorView.hidden).to.beTruthy;
64 | expect(cell.accessoryType).to.equal(UITableViewCellAccessoryNone);
65 | });
66 |
67 | it(@"should behave correctly when state is BZGValidationStateValidating", ^{
68 | cell.validationState = BZGValidationStateValidating;
69 | expect(cell.validationState).to.equal(BZGValidationStateValidating);
70 | expect(cell.textField.textColor).to.equal(BZG_TEXTFIELD_NORMAL_COLOR);
71 | expect(cell.activityIndicatorView.hidden).to.beFalsy;
72 | expect(cell.activityIndicatorView.isAnimating).to.beTruthy;
73 | expect(cell.accessoryType).to.equal(UITableViewCellAccessoryNone);
74 | });
75 | });
76 |
77 | describe(@"parentCellForTextField:", ^{
78 | it(@"should return nil if the text field has no parent BZGTextFieldFormCell", ^{
79 | UITextField *textField = [[UITextField alloc] init];
80 | BZGTextFieldCell *cell = [BZGTextFieldCell parentCellForTextField:textField];
81 | expect(cell).to.beNil;
82 | });
83 |
84 | it(@"should return the cell when given a BZGTextFieldCell's text field", ^{
85 | cell = [BZGTextFieldCell parentCellForTextField:cell.textField];
86 | expect(cell).toNot.beNil;
87 | expect(cell).to.equal(cell);
88 | });
89 | });
90 |
91 | describe(@"showsCheckmarkWhenValid", ^{
92 | it(@"should not show a checkmarck when showsCheckMarkWhenValid=NO", ^{
93 | cell.showsCheckmarkWhenValid = NO;
94 | cell.validationState = BZGValidationStateValid;
95 | expect(cell.accessoryType).to.equal(UITableViewCellAccessoryNone);
96 | });
97 |
98 | it(@"should not show a checkmarck when showsCheckMarkWhenValid=YES", ^{
99 | cell.showsCheckmarkWhenValid = YES;
100 | cell.validationState = BZGValidationStateValid;
101 | expect(cell.accessoryType).to.equal(UITableViewCellAccessoryCheckmark);
102 | });
103 | });
104 |
105 | describe(@"setValidationError", ^{
106 | it(@"should set the info cell's text to the valdation error", ^{
107 | cell.validationError = [NSError bzg_errorWithDescription:@"Hello, error!"];
108 | expect(cell.infoCell.infoLabel.text).to.equal(@"Hello, error!");
109 | expect(cell.validationError).to.equal([NSError bzg_errorWithDescription:@"Hello, error!"]);
110 | });
111 | });
112 |
113 | SpecEnd
114 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------