Skip to content

Commit

Permalink
Merge pull request #16 from benzguo/daz/keyboard_controls
Browse files Browse the repository at this point in the history
Keyboard Controls
  • Loading branch information
0thernet committed Apr 8, 2014
2 parents c9b3c63 + 8cd6bf9 commit 1730452
Show file tree
Hide file tree
Showing 11 changed files with 245 additions and 6 deletions.
11 changes: 11 additions & 0 deletions BZGFormViewController/BZGFormViewController.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
/// The table view section where the form should be displayed.
@property (nonatomic, assign) NSUInteger formSection;

/// A property indicating whether or not the keyboard should display an input accessory view with previous, next, and done buttons.
@property (nonatomic, assign) BOOL showsKeyboardControl;

/**
* A property indicating whether or not the form is valid.
* A form is valid when all of its formCells have validation state
Expand Down Expand Up @@ -50,6 +53,14 @@
*/
- (BZGTextFieldCell *)nextFormCell:(BZGTextFieldCell *)cell;

/**
* Returns the previous form cell.
*
* @param cell The starting form field cell.
* @return The previous form field cell or nil if no cell is found.
*/
- (BZGTextFieldCell *)previousFormCell:(BZGTextFieldCell *)cell;

/**
* Returns the first invalid form field cell.
*
Expand Down
57 changes: 55 additions & 2 deletions BZGFormViewController/BZGFormViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@
#import "BZGFormViewController.h"
#import "BZGTextFieldCell.h"
#import "BZGInfoCell.h"
#import "BZGKeyboardControl.h"
#import "Constants.h"

@interface BZGFormViewController ()

@property (nonatomic, assign) UITableViewStyle style;
@property (nonatomic, assign) BOOL isValid;
@property (nonatomic, strong) BZGKeyboardControl *keyboardControl;

@end

Expand Down Expand Up @@ -176,6 +178,20 @@ - (BZGTextFieldCell *)nextFormCell:(BZGTextFieldCell *)cell
return nil;
}

- (BZGTextFieldCell *)previousFormCell:(BZGTextFieldCell *)cell
{
NSUInteger cellIndex = [self.formCells indexOfObject:cell];
if (cellIndex == NSNotFound || cellIndex == 0) return nil;

for (NSInteger i = cellIndex - 1; i >= 0; --i) {
UITableViewCell *cell = self.formCells[i];
if ([cell isKindOfClass:[BZGTextFieldCell class]]) {
return (BZGTextFieldCell *)cell;
}
}
return nil;
}

#pragma mark - Table view data source

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
Expand Down Expand Up @@ -214,9 +230,12 @@ - (void)textFieldDidBeginEditing:(UITextField *)textField
if (cell.didBeginEditingBlock) {
cell.didBeginEditingBlock(cell, textField.text);
}

NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
[self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionTop animated:YES];
if (self.showsKeyboardControl) {
[self accesorizeTextField:textField];
}
}

- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
Expand Down Expand Up @@ -245,7 +264,7 @@ - (void)textFieldDidEndEditing:(UITextField *)textField
if (cell.didEndEditingBlock) {
cell.didEndEditingBlock(cell, textField.text);
}

[self updateInfoCellBelowFormCell:cell];
}

Expand Down Expand Up @@ -310,5 +329,39 @@ - (void)keyboardWillHide:(NSNotification *)notification {
self.tableView.scrollIndicatorInsets = UIEdgeInsetsZero;
}

#pragma mark - BZGKeyboardControl Methods

- (void)accesorizeTextField:(UITextField *)textField {
BZGTextFieldCell *cell = [BZGTextFieldCell parentCellForTextField:textField];
self.keyboardControl.previousCell = [self previousFormCell:cell];
self.keyboardControl.currentCell = cell;
self.keyboardControl.nextCell = [self nextFormCell:cell];
textField.inputAccessoryView = self.keyboardControl;
}

- (BZGKeyboardControl *)keyboardControl {
if (!_keyboardControl) {
_keyboardControl = [[BZGKeyboardControl alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(self.view.frame), BZG_KEYBOARD_CONTROL_HEIGHT)];
_keyboardControl.previousButton.target = self;
_keyboardControl.previousButton.action = @selector(navigateToPreviousCell:);
_keyboardControl.nextButton.target = self;
_keyboardControl.nextButton.action = @selector(navigateToNextCell);
_keyboardControl.doneButton.target = self;
_keyboardControl.doneButton.action = @selector(doneButtonPressed);
}
return _keyboardControl;
}

- (void)navigateToPreviousCell: (id)sender {
[self.keyboardControl.previousCell.textField becomeFirstResponder];
}

- (void)navigateToNextCell {
[self.keyboardControl.nextCell.textField becomeFirstResponder];
}

- (void)doneButtonPressed {
[self.keyboardControl.currentCell.textField resignFirstResponder];
}

@end
20 changes: 20 additions & 0 deletions BZGFormViewController/BZGKeyboardControl.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//
// BZGKeyboardControl.h
//
// https://github.com/benzguo/BZGFormViewController
//

#import <UIKit/UIKit.h>

@class BZGTextFieldCell;

@interface BZGKeyboardControl : UIView

@property (nonatomic, strong) UIBarButtonItem *previousButton;
@property (nonatomic, strong) UIBarButtonItem *nextButton;
@property (nonatomic, strong) UIBarButtonItem *doneButton;
@property (nonatomic, strong) BZGTextFieldCell *previousCell;
@property (nonatomic, strong) BZGTextFieldCell *currentCell;
@property (nonatomic, strong) BZGTextFieldCell *nextCell;

@end
58 changes: 58 additions & 0 deletions BZGFormViewController/BZGKeyboardControl.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
//
// BZGKeyboardControl.m
//
// https://github.com/benzguo/BZGFormViewController
//

#import "BZGKeyboardControl.h"

const CGFloat BZGKeyboardControlButtonSpacing = 22;

@implementation BZGKeyboardControl

- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
self.previousButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:105
target:nil
action:nil];
self.nextButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:106
target:nil
action:nil];

self.doneButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone
target:nil
action:nil];

self.previousButton.enabled = NO;
self.nextButton.enabled = NO;
self.previousButton.tintColor = [UIColor blackColor];
self.nextButton.tintColor = [UIColor blackColor];
self.doneButton.tintColor = [UIColor blackColor];

UIBarButtonItem *buttonSpacing = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace
target:self
action:nil];
buttonSpacing.width = BZGKeyboardControlButtonSpacing;
UIBarButtonItem *flexibleSpace = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace
target:self
action:nil];
UIToolbar *toolbar = [[UIToolbar alloc] initWithFrame:self.frame];
[toolbar setItems:@[self.previousButton, buttonSpacing, self.nextButton, flexibleSpace, self.doneButton]];
[self addSubview:toolbar];
}
return self;
}


- (void)setPreviousCell:(BZGTextFieldCell *)previousCell {
_previousCell = previousCell;
self.previousButton.enabled = !!previousCell;
}


- (void)setNextCell:(BZGTextFieldCell *)nextCell {
_nextCell = nextCell;
self.nextButton.enabled = !!nextCell;
}
@end
2 changes: 2 additions & 0 deletions BZGFormViewController/Constants.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,6 @@
#define BZG_INFO_LABEL_FONT BZG_SMALL_FONT
#define BZG_INFO_BACKGROUND_COLOR BZG_TABLEVIEW_BACKGROUND_COLOR

#define BZG_KEYBOARD_CONTROL_HEIGHT 44

#endif
10 changes: 10 additions & 0 deletions BZGFormViewControllerTests/BZGFormViewControllerSpecs.m
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,16 @@
});
});

describe(@"previousFormCell:", ^{
it(@"should return the correct previous cell", ^{
formViewController.formCells = [NSMutableArray arrayWithArray:@[cell1, cell2, cell3]];
[formViewController.tableView reloadData];
expect([formViewController previousFormCell:cell3]).to.equal(cell2);
expect([formViewController previousFormCell:cell2]).to.equal(cell1);
expect([formViewController previousFormCell:cell1]).to.equal(nil);
});
});

describe(@"nextFormCell:", ^{
it(@"should return the correct next cell", ^{
formViewController.formCells = [NSMutableArray arrayWithArray:@[cell1, cell2, cell3]];
Expand Down
74 changes: 74 additions & 0 deletions BZGFormViewControllerTests/BZGKeyboardControlSpecs.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
//
// BZGKeyboardControlSpecs.m
//
// https://github.com/benzguo/BZGFormViewController
//

#import "BZGKeyboardControl.h"
#import "BZGTextFieldCell.h"

BZGKeyboardControl *keyboardControl;
BZGTextFieldCell *cell1;
BZGTextFieldCell *cell2;

SpecBegin(BZGKeyboardControl)

before(^{
keyboardControl = [[BZGKeyboardControl alloc] initWithFrame:CGRectMake(0, 0, 320, 44)];
cell1 = [[BZGTextFieldCell alloc] init];
cell2 = [[BZGTextFieldCell alloc] init];
});

after(^{
keyboardControl = nil;
cell1 = nil;
cell2 = nil;
});


describe(@"Initialization", ^{
it(@"should initialize with correct defaults", ^{
expect(keyboardControl.previousButton).toNot.beNil();
expect(keyboardControl.nextButton).toNot.beNil();
expect(keyboardControl.doneButton).toNot.beNil();
expect(keyboardControl.previousButton.enabled).to.beFalsy();
expect(keyboardControl.nextButton.enabled).to.beFalsy();
expect(keyboardControl.previousButton.tintColor).to.equal([UIColor blackColor]);
expect(keyboardControl.nextButton.tintColor).to.equal([UIColor blackColor]);
expect(keyboardControl.doneButton.tintColor).to.equal([UIColor blackColor]);
});
});

describe(@"Button States", ^{

it(@"should have both buttons enabled if it has a previous cell and next cell", ^{
keyboardControl.previousCell = cell1;
keyboardControl.nextCell = cell2;
expect(keyboardControl.previousButton.enabled).to.beTruthy();
expect(keyboardControl.nextButton.enabled).to.beTruthy();
});

it(@"should have only the previous button enabled if it has a previous cell and a nil next cell", ^{
keyboardControl.previousCell = cell1;
keyboardControl.nextCell = nil;
expect(keyboardControl.previousButton.enabled).to.beTruthy();
expect(keyboardControl.nextButton.enabled).to.beFalsy();
});

it(@"should have only the next button enabled if it has a nil previous cell and a next cell", ^{
keyboardControl.previousCell = nil;
keyboardControl.nextCell = cell2;
expect(keyboardControl.previousButton.enabled).to.beFalsy();
expect(keyboardControl.nextButton.enabled).to.beTruthy();
});

it(@"should have both buttons disabled if it has a nil previous cell and a nil next cell", ^{
keyboardControl.previousCell = nil;
keyboardControl.nextCell = nil;
expect(keyboardControl.previousButton.enabled).to.beFalsy();
expect(keyboardControl.nextButton.enabled).to.beFalsy();
});

});

SpecEnd
6 changes: 3 additions & 3 deletions SignupForm/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,13 @@ DEPENDENCIES:
- Specta (~> 0.2.1)

SPEC CHECKSUMS:
BZGMailgunEmailValidation: c9ac686fd3139bcf9239c2146654d2a8264ca851
BZGMailgunEmailValidation: 156c6d389d234fb5a3034c6e1ae4e5cedcc7a203
Expecta: 322f1dc42610106a5ba9871b4924cf1635d80833
libextobjc: 2c9fc8226f0f38e562587f65172f93f0c9b4a47a
OCHamcrest: 782c52ae8c673e5d6060a65005cfd26d20d519ba
OCMock: 74e4ddbcef134170dcc844f91c691f13868075d7
OCMock: ea7fe30ee99e4e3186ad47d032a301fa7e8793ec
OCMockito: 317fa0cf75c9b0d73d2be335789c556537be7fd2
ReactiveCocoa: 953a45dda30674fe704d1d9c77cc874141742f47
Specta: 2d06220591110c6d9757d8be8ecf8e63aa40dc2a

COCOAPODS: 0.29.0
COCOAPODS: 0.31.1
10 changes: 10 additions & 0 deletions SignupForm/SignupForm.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

/* Begin PBXBuildFile section */
30709D4AA1994EF090749611 /* libPods.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 90F90CF32A864DEC854302D2 /* libPods.a */; };
3A1F56A718F388B40090E732 /* BZGKeyboardControl.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A1F56A618F388B40090E732 /* BZGKeyboardControl.m */; };
3A1F56A918F432F90090E732 /* BZGKeyboardControlSpecs.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A1F56A818F432F90090E732 /* BZGKeyboardControlSpecs.m */; };
799AEBE944DD4B758103BA7E /* libPods-SignupFormTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 51A99866C8474A4D9FA92F85 /* libPods-SignupFormTests.a */; };
EB18C91A18DE628500F23064 /* BZGFormCell.m in Sources */ = {isa = PBXBuildFile; fileRef = EB18C91918DE628500F23064 /* BZGFormCell.m */; };
EB18C91C18DE62BC00F23064 /* BZGFormCellSpecs.m in Sources */ = {isa = PBXBuildFile; fileRef = EB18C91B18DE62BC00F23064 /* BZGFormCellSpecs.m */; };
Expand Down Expand Up @@ -44,6 +46,9 @@
/* Begin PBXFileReference section */
2F4402527AED4455AEE5A0EC /* Pods-SignupFormTests.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SignupFormTests.xcconfig"; path = "Pods/Pods-SignupFormTests.xcconfig"; sourceTree = "<group>"; };
36501D3196434BDDACC01A05 /* Pods.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.xcconfig; path = Pods/Pods.xcconfig; sourceTree = "<group>"; };
3A1F56A518F388B40090E732 /* BZGKeyboardControl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BZGKeyboardControl.h; path = ../../BZGFormViewController/BZGKeyboardControl.h; sourceTree = "<group>"; };
3A1F56A618F388B40090E732 /* BZGKeyboardControl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BZGKeyboardControl.m; path = ../../BZGFormViewController/BZGKeyboardControl.m; sourceTree = "<group>"; };
3A1F56A818F432F90090E732 /* BZGKeyboardControlSpecs.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BZGKeyboardControlSpecs.m; path = ../../BZGFormViewControllerTests/BZGKeyboardControlSpecs.m; sourceTree = "<group>"; };
51A99866C8474A4D9FA92F85 /* libPods-SignupFormTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-SignupFormTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
90F90CF32A864DEC854302D2 /* libPods.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libPods.a; sourceTree = BUILT_PRODUCTS_DIR; };
EB18C91818DE628500F23064 /* BZGFormCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BZGFormCell.h; path = ../../BZGFormViewController/BZGFormCell.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -108,6 +113,8 @@
EB59ED66180B2DB900DAA992 /* BZGFormViewController */ = {
isa = PBXGroup;
children = (
3A1F56A518F388B40090E732 /* BZGKeyboardControl.h */,
3A1F56A618F388B40090E732 /* BZGKeyboardControl.m */,
EB18C91818DE628500F23064 /* BZGFormCell.h */,
EB18C91918DE628500F23064 /* BZGFormCell.m */,
EB59ED5C180B2DB100DAA992 /* BZGTextFieldCell.h */,
Expand Down Expand Up @@ -184,6 +191,7 @@
isa = PBXGroup;
children = (
EB18C91B18DE62BC00F23064 /* BZGFormCellSpecs.m */,
3A1F56A818F432F90090E732 /* BZGKeyboardControlSpecs.m */,
EB59ED55180B2D9200DAA992 /* BZGTextFieldCellSpecs.m */,
EB59ED70180B377900DAA992 /* BZGInfoCellSpecs.m */,
EB59ED72180B378A00DAA992 /* BZGFormViewControllerSpecs.m */,
Expand Down Expand Up @@ -368,6 +376,7 @@
EB59ED6C180B2F0800DAA992 /* BZGFormViewController.m in Sources */,
EB59ED63180B2DB100DAA992 /* BZGTextFieldCell.m in Sources */,
EB59ED64180B2DB100DAA992 /* BZGInfoCell.m in Sources */,
3A1F56A718F388B40090E732 /* BZGKeyboardControl.m in Sources */,
EB83D531180B2CA9001037F5 /* AppDelegate.m in Sources */,
EB83D52D180B2CA9001037F5 /* main.m in Sources */,
EB59ED6F180B313000DAA992 /* SignupViewController.m in Sources */,
Expand All @@ -378,6 +387,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
3A1F56A918F432F90090E732 /* BZGKeyboardControlSpecs.m in Sources */,
EB59ED58180B2D9200DAA992 /* BZGTextFieldCellSpecs.m in Sources */,
EB59ED73180B378A00DAA992 /* BZGFormViewControllerSpecs.m in Sources */,
EB18C91C18DE62BC00F23064 /* BZGFormCellSpecs.m in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "16BD5859881D448F9A7C12DE"
BlueprintIdentifier = "3CD0C7462ECA499C999E0ABD"
BuildableName = "libPods.a"
BlueprintName = "Pods"
ReferencedContainer = "container:Pods/Pods.xcodeproj">
Expand Down
1 change: 1 addition & 0 deletions SignupForm/SignupForm/SignupViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ - (void)viewDidLoad
self.passwordCell]];
self.formSection = 0;
self.emailValidator = [BZGMailgunEmailValidator validatorWithPublicKey:MAILGUN_PUBLIC_KEY];
self.showsKeyboardControl = YES;
}

- (void)configureUsernameFieldCell
Expand Down

0 comments on commit 1730452

Please sign in to comment.