Skip to content

Commit fedac09

Browse files
committed
Revised implemention of next text field selection
1 parent 8089ee0 commit fedac09

1 file changed

Lines changed: 35 additions & 30 deletions

File tree

TPKeyboardAvoiding/UIScrollView+TPKeyboardAvoidingAdditions.m

Lines changed: 35 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,6 @@
1717

1818
#define _UIKeyboardFrameEndUserInfoKey (&UIKeyboardFrameEndUserInfoKey != NULL ? UIKeyboardFrameEndUserInfoKey : @"UIKeyboardBoundsUserInfoKey")
1919

20-
#define fequal(a,b) (fabs((a) - (b)) < DBL_EPSILON)
21-
22-
2320
@interface TPKeyboardAvoidingState : NSObject
2421
@property (nonatomic, assign) UIEdgeInsets priorInset;
2522
@property (nonatomic, assign) UIEdgeInsets priorScrollIndicatorInsets;
@@ -153,9 +150,7 @@ - (BOOL)TPKeyboardAvoiding_focusNextTextField {
153150
return NO;
154151
}
155152

156-
CGFloat minY = CGFLOAT_MAX;
157-
UIView *view = nil;
158-
[self TPKeyboardAvoiding_findTextFieldAfterTextField:firstResponder beneathView:self minY:&minY foundView:&view];
153+
UIView *view = [self TPKeyboardAvoiding_findNextInputViewAfterView:firstResponder beneathView:self];
159154

160155
if ( view ) {
161156
[view performSelector:@selector(becomeFirstResponder) withObject:nil afterDelay:0.0];
@@ -194,38 +189,50 @@ - (UIView*)TPKeyboardAvoiding_findFirstResponderBeneathView:(UIView*)view {
194189
return nil;
195190
}
196191

197-
- (void)TPKeyboardAvoiding_findTextFieldAfterTextField:(UIView*)priorTextField beneathView:(UIView*)view minY:(CGFloat*)minY foundView:(UIView* __autoreleasing *)foundView {
198-
// Search recursively for text field or text view below priorTextField
199-
CGFloat priorFieldOffset = CGRectGetMinY([self convertRect:priorTextField.frame fromView:priorTextField.superview]);
192+
- (UIView*)TPKeyboardAvoiding_findNextInputViewAfterView:(UIView*)priorView beneathView:(UIView*)view {
193+
UIView * candidate = nil;
194+
[self TPKeyboardAvoiding_findNextInputViewAfterView:priorView beneathView:view bestCandidate:&candidate];
195+
return candidate;
196+
}
197+
198+
- (void)TPKeyboardAvoiding_findNextInputViewAfterView:(UIView*)priorView beneathView:(UIView*)view bestCandidate:(UIView**)bestCandidate {
199+
// Search recursively for input view below/to right of priorTextField
200+
CGRect priorFrame = [self convertRect:priorView.frame fromView:priorView.superview];
201+
CGRect candidateFrame = *bestCandidate ? [self convertRect:(*bestCandidate).frame fromView:(*bestCandidate).superview] : CGRectZero;
202+
CGFloat bestCandidateHeuristic = -sqrt(candidateFrame.origin.x*candidateFrame.origin.x + candidateFrame.origin.y*candidateFrame.origin.y)
203+
+ (fabs(CGRectGetMinY(candidateFrame) - CGRectGetMinY(priorFrame)) < FLT_EPSILON ? 1e6 : 0);
204+
200205
for ( UIView *childView in view.subviews ) {
201-
if ([self TPKeyboardAvoiding_viewIsNextTextField:childView]) {
206+
if ( [self TPKeyboardAvoiding_viewIsValidKeyViewCandidate:childView] ) {
202207
CGRect frame = [self convertRect:childView.frame fromView:view];
203-
if ( childView != priorTextField
204-
&& CGRectGetMinY(frame) >= priorFieldOffset
205-
&& CGRectGetMinY(frame) < *minY &&
206-
!(fequal(frame.origin.y, priorTextField.frame.origin.y)
207-
&& frame.origin.x < priorTextField.frame.origin.x) ) {
208-
*minY = CGRectGetMinY(frame);
209-
*foundView = childView;
208+
209+
// Use a heuristic to evaluate candidates: prefer elements closest to the top left, and on the same line
210+
CGFloat heuristic = -sqrt(frame.origin.x*frame.origin.x + frame.origin.y*frame.origin.y)
211+
+ (fabs(CGRectGetMinY(frame) - CGRectGetMinY(priorFrame)) < FLT_EPSILON ? 1e6 : 0);
212+
213+
// Find views beneath, or to the right. For those views that match, choose the view closest to the top left
214+
if ( childView != priorView
215+
&& ((fabs(CGRectGetMinY(frame) - CGRectGetMinY(priorFrame)) < FLT_EPSILON && CGRectGetMinX(frame) > CGRectGetMinX(priorFrame))
216+
|| CGRectGetMinY(frame) > CGRectGetMinY(priorFrame))
217+
&& (!*bestCandidate || heuristic > bestCandidateHeuristic) ) {
218+
219+
*bestCandidate = childView;
220+
bestCandidateHeuristic = heuristic;
210221
}
211222
} else {
212-
[self TPKeyboardAvoiding_findTextFieldAfterTextField:priorTextField beneathView:childView minY:minY foundView:foundView];
223+
[self TPKeyboardAvoiding_findNextInputViewAfterView:priorView beneathView:childView bestCandidate:bestCandidate];
213224
}
214225
}
215226
}
216227

217-
- (BOOL)TPKeyboardAvoiding_viewIsNextTextField:(UIView *)view {
218-
if (view.hidden) return NO;
228+
- (BOOL)TPKeyboardAvoiding_viewIsValidKeyViewCandidate:(UIView *)view {
229+
if ( view.hidden || !view.userInteractionEnabled ) return NO;
219230

220-
if ([view isKindOfClass:[UITextField class]]) {
221-
UITextField *textField = (UITextField *)view;
222-
if (!textField.enabled) return NO;
223-
231+
if ( [view isKindOfClass:[UITextField class]] && ((UITextField*)view).enabled ) {
232+
return YES;
224233
}
225234

226-
if ( ([view isKindOfClass:[UITextField class]] ||
227-
[view isKindOfClass:[UITextView class]])
228-
&& view.isUserInteractionEnabled) {
235+
if ( [view isKindOfClass:[UITextView class]] && ((UITextView*)view).isEditable ) {
229236
return YES;
230237
}
231238

@@ -308,9 +315,7 @@ - (void)TPKeyboardAvoiding_initializeView:(UIView*)view {
308315
&& ((UITextField*)view).returnKeyType == UIReturnKeyDefault
309316
&& (![(UITextField*)view delegate] || [(UITextField*)view delegate] == (id<UITextFieldDelegate>)self) ) {
310317
[(UITextField*)view setDelegate:(id<UITextFieldDelegate>)self];
311-
UIView *otherView = nil;
312-
CGFloat minY = CGFLOAT_MAX;
313-
[self TPKeyboardAvoiding_findTextFieldAfterTextField:view beneathView:self minY:&minY foundView:&otherView];
318+
UIView *otherView = [self TPKeyboardAvoiding_findNextInputViewAfterView:view beneathView:self];
314319

315320
if ( otherView ) {
316321
((UITextField*)view).returnKeyType = UIReturnKeyNext;

0 commit comments

Comments
 (0)