From a336b35e9cda9d9d94e0c03af8e28ccdd224b179 Mon Sep 17 00:00:00 2001 From: Victor Weng Date: Tue, 21 Jan 2014 17:31:16 -0500 Subject: [PATCH] =?UTF-8?q?Fixed=20a=20couple=20of=20bugs:=201.=20top=20vi?= =?UTF-8?q?ew=20position=20is=20not=20restored=20if=20select=20a=20lower?= =?UTF-8?q?=20text=20field=20and=20then=20navigate=20back=20to=20upper=20o?= =?UTF-8?q?nes=20via=20the=20toolbar=20previous=20button.=202.=20apply=20t?= =?UTF-8?q?he=20resign/become=20active=20workaround=20for=20both=20UITextV?= =?UTF-8?q?iew=20and=20UITextField=20as=20in=20iOS=207=20the=20keyboardWil?= =?UTF-8?q?lAppear=20event=20could=20be=20fired=20either=20before=20or=20a?= =?UTF-8?q?fter=20textDidBeginEditing=20event.=20It=E2=80=99s=20not=20a=20?= =?UTF-8?q?predicatble=20sequence.=203.=20added=20a=20fix=20for=20UITextVi?= =?UTF-8?q?ew=20bug.=204.=20some=20other=20minor=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../IQKeyBoardManager/IQKeyboardManager.m | 131 ++++++++++++------ 1 file changed, 92 insertions(+), 39 deletions(-) diff --git a/KeyboardTextFieldDemo/IQKeyBoardManager/IQKeyboardManager.m b/KeyboardTextFieldDemo/IQKeyBoardManager/IQKeyboardManager.m index 1783f972..4150281e 100755 --- a/KeyboardTextFieldDemo/IQKeyBoardManager/IQKeyboardManager.m +++ b/KeyboardTextFieldDemo/IQKeyBoardManager/IQKeyboardManager.m @@ -116,6 +116,7 @@ - (void)keyboardWillShow:(NSNotification*)aNotification; - (void)keyboardWillHide:(NSNotification*)aNotification; - (void)textFieldViewDidBeginEditing:(NSNotification*)notification; - (void)textFieldViewDidEndEditing:(NSNotification*)notification; +- (void)textFieldViewDidChange:(NSNotification*)notification; - (void)tapRecognized:(UITapGestureRecognizer*)gesture; @@ -187,10 +188,11 @@ -(id)initUniqueInstance // Registering for textView notification. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textFieldViewDidBeginEditing:) name:UITextViewTextDidBeginEditingNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textFieldViewDidEndEditing:) name:UITextViewTextDidEndEditingNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textFieldViewDidChange:) name:UITextViewTextDidChangeNotification object:nil]; tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapRecognized:)]; [tapGesture setDelegate:self]; - + // Default settings [self setKeyboardDistanceFromTextField:10.0]; animationDuration = 0.25; @@ -256,6 +258,8 @@ -(void)setEnable:(BOOL)enable //Sending a fake notification for keyboardWillHide to retain view's original frame. [self keyboardWillHide:nil]; + [self removeToolbarIfRequired]; + //Setting NO to _enable. _enable = enable; @@ -313,8 +317,8 @@ +(UIScrollView*)superScrollView:(UIView*)view while (superview) { - if ([superview isKindOfClass:[UIScrollView class]]) - { + if ([superview isKindOfClass:[UIScrollView class]]) + { return (UIScrollView*)superview; } else superview = superview.superview; @@ -327,7 +331,7 @@ +(UIScrollView*)superScrollView:(UIView*)view + (UIViewController*) topMostController { UIViewController *topController = [[IQKeyboardManager sharedManager] rootViewController]; - + // Getting topMost ViewController while ([topController presentedViewController]) topController = [topController presentedViewController]; @@ -340,7 +344,7 @@ +(UIViewController*)currentViewController; UIViewController *currentViewController = [IQKeyboardManager topMostController]; while ([currentViewController isKindOfClass:[UINavigationController class]] && [(UINavigationController*)currentViewController topViewController]) - currentViewController = [(UINavigationController*)currentViewController topViewController]; + currentViewController = [(UINavigationController*)currentViewController topViewController]; return currentViewController; } @@ -368,9 +372,6 @@ -(void)adjustFrame // We are unable to get textField object while keyboard showing on UIWebView's textField. if (textFieldView == nil) return; - // Boolean to know keyboard is showing/hiding - isKeyboardShowing = YES; - // Getting KeyWindow object. UIWindow *window = [self keyWindow]; // Getting RootViewController. @@ -380,7 +381,7 @@ -(void)adjustFrame CGRect textFieldViewRect = [[textFieldView superview] convertRect:textFieldView.frame toView:window]; // Getting RootViewRect. CGRect rootViewRect = [[rootController view] frame]; - + CGFloat move = 0; // Move positive = textField is hidden. // Move negative = textField is showing. @@ -406,9 +407,9 @@ -(void)adjustFrame // Getting it's superScrollView. UIScrollView *superScrollView = [IQKeyboardManager superScrollView:textFieldView]; - + //If there was a lastScrollView. - if (lastScrollView) + if (lastScrollView) { //If we can't find current superScrollView, then setting lastScrollView to it's original form. if (superScrollView == nil) @@ -431,14 +432,14 @@ -(void)adjustFrame lastScrollView = superScrollView; startingContentOffset = superScrollView.contentOffset; } - + // Special case for ScrollView. // If we found lastScrollView then setting it's contentOffset to show textField. if (lastScrollView) { UIView *lastView = textFieldView; UIScrollView *superScrollView = lastScrollView; - + while (superScrollView) { CGRect lastViewRect = [[lastView superview] convertRect:lastView.frame toView:superScrollView]; @@ -454,9 +455,10 @@ -(void)adjustFrame superScrollView = [IQKeyboardManager superScrollView:lastView]; } } + //Going ahead. No else if. - - + + //Special case for UITextView(When it's hight is too big to fit on screen. { CGFloat initialMove = move; @@ -492,8 +494,8 @@ -(void)adjustFrame }]; } } - - + + // Special case for iPad modalPresentationStyle. if ([[IQKeyboardManager topMostController] modalPresentationStyle] == UIModalPresentationFormSheet || [[IQKeyboardManager topMostController] modalPresentationStyle] == UIModalPresentationPageSheet) @@ -588,7 +590,7 @@ -(void)adjustFrame // Keyboard Will hide. So setting rootViewController to it's default frame. - (void)keyboardWillHide:(NSNotification*)aNotification { - //If it's not a fake notification generated by [self setEnable:NO]. + //If it's not a fake notification generated by [self setEnable:NO]. if (aNotification != nil) kbShowNotification = nil; if (_enable == NO) return; @@ -596,6 +598,7 @@ - (void)keyboardWillHide:(NSNotification*)aNotification // We are unable to get textField object while keyboard showing on UIWebView's textField. if (textFieldView == nil) return; + //Due to orientation callback we need to set it's original position. [UIView animateWithDuration:animationDuration animations:^{ textFieldView.frame = textFieldViewIntialFrame; @@ -604,6 +607,7 @@ - (void)keyboardWillHide:(NSNotification*)aNotification // Boolean to know keyboard is showing/hiding isKeyboardShowing = NO; + // Getting keyboard animation duration CGFloat aDuration = [[[aNotification userInfo] objectForKey:UIKeyboardAnimationDurationUserInfoKey] floatValue]; if (aDuration!= 0.0f) @@ -616,19 +620,28 @@ - (void)keyboardWillHide:(NSNotification*)aNotification lastScrollView = nil; startingContentOffset = CGPointZero; // Setting rootViewController frame to it's original position. - [self setRootViewFrame:topViewBeginRect]; + if (!CGRectIsEmpty(topViewBeginRect)) + { + [self setRootViewFrame:topViewBeginRect]; + + topViewBeginRect = CGRectNull; + } } // UIKeyboard Will show -(void)keyboardWillShow:(NSNotification*)aNotification { - kbShowNotification = aNotification; + // Boolean to know keyboard is showing/hiding + isKeyboardShowing = YES; + + // Store for reference + kbShowNotification = aNotification; if (_enable == NO) return; //Due to orientation callback we need to resave it's original frame. textFieldViewIntialFrame = textFieldView.frame; - + // Getting keyboard animation duration CGFloat duration = [[[aNotification userInfo] objectForKey:UIKeyboardAnimationDurationUserInfoKey] floatValue]; @@ -657,10 +670,31 @@ -(void)keyboardWillShow:(NSNotification*)aNotification break; } - [self adjustFrame]; } #pragma mark - UITextFieldView Delegate methods +// Bug fix for iOS 7.0.x - http://stackoverflow.com/questions/18966675/uitextview-in-ios7-clips-the-last-line-of-text-string +-(void)textFieldViewDidChange:(NSNotification*)notification +{ + UITextView *textView = (UITextView *)notification.object; + + CGRect line = [textView caretRectForPosition: + textView.selectedTextRange.start]; + CGFloat overflow = line.origin.y + line.size.height + - ( textView.contentOffset.y + textView.bounds.size.height + - textView.contentInset.bottom - textView.contentInset.top ); + if ( overflow > 0 ) { + // We are at the bottom of the visible text and introduced a line feed, scroll down (iOS 7 does not do it) + // Scroll caret to visible area + CGPoint offset = textView.contentOffset; + offset.y += overflow + 7; // leave 7 pixels margin + // Cannot animate with setContentOffset:animated: or caret will not appear + [UIView animateWithDuration:.2 animations:^{ + [textView setContentOffset:offset]; + }]; + } +} + // Removing fetched object. -(void)textFieldViewDidEndEditing:(NSNotification*)notification { @@ -679,20 +713,22 @@ -(void)textFieldViewDidBeginEditing:(NSNotification*)notification { // Getting object textFieldView = notification.object; + textFieldViewIntialFrame = textFieldView.frame; - + //If autoToolbar enable, then add toolbar on all the UITextField/UITextView's if required. if (_enableAutoToolbar) { - //UITextView special case. Keyboard Notification is firing before textView notification so we need to resign it first and then again set it as first responder to add toolbar on it. - if ([textFieldView isKindOfClass:[UITextView class]] && textFieldView.inputAccessoryView == nil) + // in iOS 7+ Keyboard Notification sometimes fires before textView notification so we need to resign it first and then again set it as first responder to add toolbar on it. + // By victor - this happens on both UITextField and UITextView + if (textFieldView.inputAccessoryView == nil) { UIView *view = textFieldView; - + //Resigning becoming first responder with some delay. [UIView animateWithDuration:0.00001 animations:^{ [self addToolbarIfRequired]; - + }completion:^(BOOL finished) { [view resignFirstResponder]; [view becomeFirstResponder]; @@ -703,19 +739,18 @@ -(void)textFieldViewDidBeginEditing:(NSNotification*)notification [self addToolbarIfRequired]; } } - - if (_enable == NO) return; + + if (_enable == NO) return; [textFieldView.window addGestureRecognizer:tapGesture]; - - if (isKeyboardShowing == NO) + + // We should save rootViewRect initially no matter keyboard is showing or not, as of iOS 7 keyboard could show early or later + if (CGRectIsEmpty(topViewBeginRect)) { - // keyboard is not showing(At the beginning only). We should save rootViewRect. UIViewController *rootController = [IQKeyboardManager topMostController]; topViewBeginRect = rootController.view.frame; } - // keyboard is already showing. adjust frame. [self adjustFrame]; } @@ -760,7 +795,7 @@ -(NSArray*)responderViews { // Getting all siblings NSArray *siblings = textFieldView.superview.subviews; - + //Array of (UITextField/UITextView's). NSMutableArray *textFields = [[NSMutableArray alloc] init]; @@ -777,7 +812,7 @@ -(NSArray*)responderViews else if (_toolbarManageBehaviour == IQAutoToolbarByTag) { return [textFields sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) { - + if ([(UIView*)obj1 tag] < [(UIView*)obj2 tag]) return NSOrderedAscending; else if ([(UIView*)obj1 tag] > [(UIView*)obj2 tag]) return NSOrderedDescending; @@ -810,7 +845,7 @@ -(void)addToolbarIfRequired if (![textField inputAccessoryView]) { [textField addPreviousNextDoneOnKeyboardWithTarget:self previousAction:@selector(previousAction:) nextAction:@selector(nextAction:) doneAction:@selector(doneAction:)]; - + // If firstTextField, then previous should not be enabled. if ([siblings objectAtIndex:0] == textField) { @@ -826,6 +861,21 @@ -(void)addToolbarIfRequired } } +-(void)removeToolbarIfRequired +{ + // Getting all the sibling textFields. + NSArray *siblings = [self responderViews]; + + for (UITextField *textField in siblings) + { + if ([textField inputAccessoryView].tag == NSIntegerMin) + { + [textField.inputAccessoryView removeFromSuperview]; + [textField setInputAccessoryView:nil]; + } + } +} + // Previous button action. -(void)previousAction:(UISegmentedControl*)segmentedControl { @@ -874,6 +924,7 @@ -(void)addDoneOnKeyboardWithTarget:(id)target action:(SEL)action UIToolbar *toolbar = [[UIToolbar alloc] init]; [toolbar setTag:NSIntegerMin]; [toolbar sizeToFit]; + toolbar.tag = NSIntegerMin; // mark it so as to differentiate with user's accessoryViews, if any. // Create a fake button to maintain flexibleSpace between doneButton and nilButton. (Actually it moves done button to right side. UIBarButtonItem *nilButton =[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil]; @@ -890,9 +941,9 @@ -(void)addDoneOnKeyboardWithTarget:(id)target action:(SEL)action { [toolbar setBarStyle:UIBarStyleBlackTranslucent]; } - + // Adding button to toolBar. - [toolbar setItems:[NSArray arrayWithObjects: nilButton,doneButton, nil]]; + [toolbar setItems:[NSArray arrayWithObjects: nilButton, doneButton, nil]]; // Setting toolbar to textFieldPhoneNumber keyboard. [(UITextField*)self setInputAccessoryView:toolbar]; @@ -907,6 +958,7 @@ -(void)addCancelDoneOnKeyboardWithTarget:(id)target cancelAction:(SEL)cancelActi UIToolbar *toolbar = [[UIToolbar alloc] init]; [toolbar setTag:NSIntegerMin]; [toolbar sizeToFit]; + toolbar.tag = NSIntegerMin; // mark it so as to differentiate with user's accessoryViews, if any. // Create a cancel button to show on keyboard to resign it. Adding a selector to resign it. UIBarButtonItem *cancelButton =[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:target action:cancelAction]; @@ -943,7 +995,8 @@ -(void)addPreviousNextDoneOnKeyboardWithTarget:(id)target previousAction:(SEL)pr UIToolbar *toolbar = [[UIToolbar alloc] init]; [toolbar setTag:NSIntegerMin]; [toolbar sizeToFit]; - + toolbar.tag = NSIntegerMin; // mark it so as to differentiate with user's accessoryViews, if any. + NSMutableArray *items = [[NSMutableArray alloc] init]; // Create a done button to show on keyboard to resign it. Adding a selector to resign it.