Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor responder chain logic into separate class #9

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 14 additions & 8 deletions Form Input Accessory View Demo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
objects = {

/* Begin PBXBuildFile section */
8532576D17E4937A00D8E13D /* XCDResponderChain.m in Sources */ = {isa = PBXBuildFile; fileRef = 8532576C17E4937A00D8E13D /* XCDResponderChain.m */; };
C215427C164FD1C800262EB1 /* [email protected] in Resources */ = {isa = PBXBuildFile; fileRef = C2154275164FD1C800262EB1 /* [email protected] */; };
C215427D164FD1C800262EB1 /* Default.png in Resources */ = {isa = PBXBuildFile; fileRef = C2154276164FD1C800262EB1 /* Default.png */; };
C215427E164FD1C800262EB1 /* [email protected] in Resources */ = {isa = PBXBuildFile; fileRef = C2154277164FD1C800262EB1 /* [email protected] */; };
Expand All @@ -21,6 +22,8 @@
/* End PBXBuildFile section */

/* Begin PBXFileReference section */
8532576B17E4937A00D8E13D /* XCDResponderChain.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XCDResponderChain.h; sourceTree = "<group>"; };
8532576C17E4937A00D8E13D /* XCDResponderChain.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XCDResponderChain.m; sourceTree = "<group>"; };
C2154275164FD1C800262EB1 /* [email protected] */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "[email protected]"; sourceTree = "<group>"; };
C2154276164FD1C800262EB1 /* Default.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Default.png; sourceTree = "<group>"; };
C2154277164FD1C800262EB1 /* [email protected] */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "[email protected]"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -58,7 +61,7 @@
isa = PBXGroup;
children = (
C2F07169164F0E1E008F4BA2 /* XCDFormInputAccessoryView */,
C2F07148164DCA75008F4BA2 /* Form Accessory View Demo */,
C2F07148164DCA75008F4BA2 /* Form Input Accessory View Demo */,
C2F07141164DCA75008F4BA2 /* Frameworks */,
C2F0713F164DCA75008F4BA2 /* Products */,
);
Expand All @@ -82,7 +85,7 @@
name = Frameworks;
sourceTree = "<group>";
};
C2F07148164DCA75008F4BA2 /* Form Accessory View Demo */ = {
C2F07148164DCA75008F4BA2 /* Form Input Accessory View Demo */ = {
isa = PBXGroup;
children = (
C2F07151164DCA75008F4BA2 /* AppDelegate.h */,
Expand All @@ -97,14 +100,16 @@
C215427A164FD1C800262EB1 /* main.m */,
C215427B164FD1C800262EB1 /* MainStoryboard.storyboard */,
);
path = "Form Accessory View Demo";
path = "Form Input Accessory View Demo";
sourceTree = "<group>";
};
C2F07169164F0E1E008F4BA2 /* XCDFormInputAccessoryView */ = {
isa = PBXGroup;
children = (
C2F0716A164F0E32008F4BA2 /* XCDFormInputAccessoryView.h */,
C2F0716B164F0E32008F4BA2 /* XCDFormInputAccessoryView.m */,
8532576B17E4937A00D8E13D /* XCDResponderChain.h */,
8532576C17E4937A00D8E13D /* XCDResponderChain.m */,
);
path = XCDFormInputAccessoryView;
sourceTree = "<group>";
Expand All @@ -125,7 +130,7 @@
dependencies = (
);
name = "Form Input Accessory View Demo";
productName = "Form Accessory View Demo";
productName = "Form Input Accessory View Demo";
productReference = C2F0713E164DCA75008F4BA2 /* Form Input Accessory View Demo.app */;
productType = "com.apple.product-type.application";
};
Expand Down Expand Up @@ -178,6 +183,7 @@
C2F07153164DCA75008F4BA2 /* AppDelegate.m in Sources */,
C2F0715F164DCA75008F4BA2 /* DemoViewController.m in Sources */,
C2F0716C164F0E32008F4BA2 /* XCDFormInputAccessoryView.m in Sources */,
8532576D17E4937A00D8E13D /* XCDResponderChain.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -254,8 +260,8 @@
"\"$(SDKROOT)$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks\"",
);
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = "Form Accessory View Demo/Form Input Accessory View Demo-Prefix.pch";
INFOPLIST_FILE = "Form Accessory View Demo/Form Input Accessory View Demo-Info.plist";
GCC_PREFIX_HEADER = "Form Input Accessory View Demo/Form Input Accessory View Demo-Prefix.pch";
INFOPLIST_FILE = "Form Input Accessory View Demo/Form Input Accessory View Demo-Info.plist";
PRODUCT_NAME = "Form Input Accessory View Demo";
WRAPPER_EXTENSION = app;
};
Expand All @@ -269,8 +275,8 @@
"\"$(SDKROOT)$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks\"",
);
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = "Form Accessory View Demo/Form Input Accessory View Demo-Prefix.pch";
INFOPLIST_FILE = "Form Accessory View Demo/Form Input Accessory View Demo-Info.plist";
GCC_PREFIX_HEADER = "Form Input Accessory View Demo/Form Input Accessory View Demo-Prefix.pch";
INFOPLIST_FILE = "Form Input Accessory View Demo/Form Input Accessory View Demo-Info.plist";
PRODUCT_NAME = "Form Input Accessory View Demo";
WRAPPER_EXTENSION = app;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@

@property (nonatomic, strong) IBOutletCollection(UIView) NSArray *textInputs;

- (IBAction)selectNextResponder:(id)sender;

@end
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,8 @@ - (CGFloat) tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexP
return indexPath.row == 2 ? 200 : tableView.rowHeight;
}

- (IBAction)selectNextResponder:(id)sender {
[_inputAccessoryView.responderChain selectNextResponder];
}

@end
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="2.0" toolsVersion="2843" systemVersion="12C54" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" initialViewController="agQ-MV-Uhd">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="2.0" toolsVersion="3084" systemVersion="12E3067" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" initialViewController="agQ-MV-Uhd">
<dependencies>
<deployment version="1280" identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="1929"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="2083"/>
</dependencies>
<scenes>
<!--Demo View Controller-->
Expand Down Expand Up @@ -38,7 +38,10 @@
<rect key="frame" x="8" y="1" width="148" height="28"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits"/>
<textInputTraits key="textInputTraits" returnKeyType="next"/>
<connections>
<action selector="selectNextResponder:" destination="agQ-MV-Uhd" eventType="editingDidEndOnExit" id="AdW-dU-yTi"/>
</connections>
</textField>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
Expand Down Expand Up @@ -69,7 +72,10 @@
<rect key="frame" x="8" y="1" width="148" height="28"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits" keyboardType="emailAddress"/>
<textInputTraits key="textInputTraits" keyboardType="emailAddress" returnKeyType="next"/>
<connections>
<action selector="selectNextResponder:" destination="agQ-MV-Uhd" eventType="editingDidEndOnExit" id="UM4-cb-IYL"/>
</connections>
</textField>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
Expand Down Expand Up @@ -126,6 +132,7 @@
<class className="DemoViewController" superclassName="UITableViewController">
<source key="sourceIdentifier" type="project" relativePath="./Classes/DemoViewController.h"/>
<relationships>
<relationship kind="action" name="selectNextResponder:"/>
<relationship kind="outletCollection" name="textInputs" candidateClass="UIView"/>
</relationships>
</class>
Expand Down
File renamed without changes.
6 changes: 5 additions & 1 deletion XCDFormInputAccessoryView/XCDFormInputAccessoryView.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,17 @@
//

#import <UIKit/UIKit.h>
#import "XCDResponderChain.h"


@interface XCDFormInputAccessoryView : UIView

- (id) initWithResponders:(NSArray *)responders; // Objects must be UIResponder instances

@property (nonatomic, strong) NSArray *responders;

- (id) initWithResponderChain:(XCDResponderChain *)responderChain;
@property (nonatomic, strong) XCDResponderChain *responderChain;

@property (nonatomic, assign) BOOL hasDoneButton; // Defaults to YES on iPhone, NO on iPad

- (void) setHasDoneButton:(BOOL)hasDoneButton animated:(BOOL)animated;
Expand Down
74 changes: 32 additions & 42 deletions XCDFormInputAccessoryView/XCDFormInputAccessoryView.m
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
// Created by Cédric Luthi on 2012-11-10
// Copyright (c) 2012 Cédric Luthi. All rights reserved.
//

#import "XCDResponderChain.h"
#import "XCDFormInputAccessoryView.h"

static NSString * UIKitLocalizedString(NSString *string)
Expand All @@ -13,21 +13,6 @@
return UIKitBundle ? [UIKitBundle localizedStringForKey:string value:string table:nil] : string;
}

static NSArray * EditableTextInputsInView(UIView *view)
{
NSMutableArray *textInputs = [NSMutableArray new];
for (UIView *subview in view.subviews)
{
BOOL isTextField = [subview isKindOfClass:[UITextField class]];
BOOL isEditableTextView = [subview isKindOfClass:[UITextView class]] && [(UITextView *)subview isEditable];
if (isTextField || isEditableTextView)
[textInputs addObject:subview];
else
[textInputs addObjectsFromArray:EditableTextInputsInView(subview)];
}
return textInputs;
}

@implementation XCDFormInputAccessoryView
{
UIToolbar *_toolbar;
Expand All @@ -39,11 +24,16 @@ - (id) initWithFrame:(CGRect)frame
}

- (id) initWithResponders:(NSArray *)responders
{
return [self initWithResponderChain:responders ? [[XCDResponderChain alloc] initWithResponders:responders] : nil];
}

- (id) initWithResponderChain:(XCDResponderChain *)responderChain
{
if (!(self = [super initWithFrame:CGRectZero]))
return nil;

_responders = responders;
_responderChain = responderChain;

_toolbar = [[UIToolbar alloc] init];
_toolbar.tintColor = nil;
Expand Down Expand Up @@ -102,19 +92,23 @@ - (void) textInputDidBeginEditing:(NSNotification *)notification

- (NSArray *) responders
{
if (_responders)
return _responders;

NSArray *textInputs = EditableTextInputsInView([[UIApplication sharedApplication] keyWindow]);
return [textInputs sortedArrayUsingComparator:^NSComparisonResult(UIView *textInput1, UIView *textInput2) {
UIView *commonAncestorView = textInput1.superview;
while (commonAncestorView && ![textInput2 isDescendantOfView:commonAncestorView])
commonAncestorView = commonAncestorView.superview;

CGRect frame1 = [textInput1 convertRect:textInput1.bounds toView:commonAncestorView];
CGRect frame2 = [textInput2 convertRect:textInput2.bounds toView:commonAncestorView];
return [@(CGRectGetMinY(frame1)) compare:@(CGRectGetMinY(frame2))];
}];
return self.responderChain.responders;
}

- (void) setResponders:(NSArray *)responders
{
self.responderChain.responders = responders;
}

- (XCDResponderChain*) responderChain
{
if (_responderChain == nil)
{
_responderChain = [XCDResponderChain
responderChainWithTextFieldsInView:[[UIApplication sharedApplication] keyWindow]];
[_responderChain sortRespondersByPosition];
}
return _responderChain;
}

- (void) setHasDoneButton:(BOOL)hasDoneButton
Expand Down Expand Up @@ -144,18 +138,14 @@ - (void) setHasDoneButton:(BOOL)hasDoneButton animated:(BOOL)animated

- (void) selectAdjacentResponder:(UISegmentedControl *)sender
{
NSArray *firstResponders = [self.responders filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(UIResponder *responder, NSDictionary *bindings) {
return [responder isFirstResponder];
}]];
UIResponder *firstResponder = [firstResponders lastObject];
NSInteger offset = sender.selectedSegmentIndex == 0 ? -1 : +1;
NSInteger firstResponderIndex = [self.responders indexOfObject:firstResponder];
NSInteger adjacentResponderIndex = firstResponderIndex != NSNotFound ? firstResponderIndex + offset : NSNotFound;
UIResponder *adjacentResponder = nil;
if (adjacentResponderIndex >= 0 && adjacentResponderIndex < (NSInteger)[self.responders count])
adjacentResponder = [self.responders objectAtIndex:adjacentResponderIndex];

[adjacentResponder becomeFirstResponder];
if (sender.selectedSegmentIndex == 0)
{
[self.responderChain selectPreviousResponder];
}
else
{
[self.responderChain selectNextResponder];
}
}

- (void) done
Expand Down
31 changes: 31 additions & 0 deletions XCDFormInputAccessoryView/XCDResponderChain.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//
// XCDResponderChain.h
//
// Created by Jude Venn on 14/09/2013.
// Copyright (c) 2013 Cuttlefish Multimedia Ltd. All rights reserved.
//

#import <Foundation/Foundation.h>

typedef BOOL (^XCDResponderTestCallback)(id obj, BOOL *stop);

@interface XCDResponderChain : NSObject

+ (XCDResponderChain*)responderChainWithTextFieldsInView:(UIView *)view;
+ (XCDResponderChain*)responderChainInView:(UIView*)view passingTest:(XCDResponderTestCallback)predicate;

// Responders must be an array of UIResponders
- (id) initWithResponders:(NSArray *)responders;

@property (nonatomic, strong) NSArray *responders;

// Move the input focus to the adjacent responder
- (void)selectNextResponder;
- (void)selectPreviousResponder;

// By default +responderChainInView will return views order by z-order, which allows
// the developer to order views in the xib. Sometimes it's preferable to have the views
// sorted by vertical position like XCDFormInputAccessoryView, so this method does that.
- (void)sortRespondersByPosition;

@end
Loading