| OLD | NEW |
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #import "ios/chrome/browser/autofill/form_input_accessory_view_controller.h" | 5 #import "ios/chrome/browser/autofill/form_input_accessory_view_controller.h" |
| 6 | 6 |
| 7 #include "base/ios/block_types.h" | 7 #include "base/ios/block_types.h" |
| 8 #include "base/ios/ios_util.h" |
| 8 #include "base/mac/foundation_util.h" | 9 #include "base/mac/foundation_util.h" |
| 9 #include "base/mac/scoped_block.h" | 10 #include "base/mac/scoped_block.h" |
| 10 #include "base/mac/scoped_nsobject.h" | 11 #include "base/mac/scoped_nsobject.h" |
| 11 #include "base/memory/scoped_ptr.h" | 12 #include "base/memory/scoped_ptr.h" |
| 12 #include "base/strings/sys_string_conversions.h" | |
| 13 #include "base/strings/utf_string_conversions.h" | |
| 14 #import "components/autofill/ios/browser/js_suggestion_manager.h" | 13 #import "components/autofill/ios/browser/js_suggestion_manager.h" |
| 15 #import "ios/chrome/browser/autofill/form_input_accessory_view.h" | 14 #import "ios/chrome/browser/autofill/form_input_accessory_view.h" |
| 15 #import "ios/chrome/browser/autofill/form_suggestion_view.h" |
| 16 #import "ios/chrome/browser/passwords/password_generation_utils.h" | 16 #import "ios/chrome/browser/passwords/password_generation_utils.h" |
| 17 #include "ios/chrome/browser/ui/ui_util.h" |
| 17 #include "ios/web/public/test/crw_test_js_injection_receiver.h" | 18 #include "ios/web/public/test/crw_test_js_injection_receiver.h" |
| 18 #include "ios/web/public/url_scheme_util.h" | 19 #include "ios/web/public/url_scheme_util.h" |
| 19 #import "ios/web/public/web_state/crw_web_view_proxy.h" | 20 #import "ios/web/public/web_state/crw_web_view_proxy.h" |
| 20 #include "ios/web/public/web_state/url_verification_constants.h" | 21 #include "ios/web/public/web_state/url_verification_constants.h" |
| 21 #include "ios/web/public/web_state/web_state.h" | 22 #include "ios/web/public/web_state/web_state.h" |
| 22 #include "url/gurl.h" | 23 #include "url/gurl.h" |
| 23 | 24 |
| 24 namespace autofill { | 25 namespace autofill { |
| 25 NSString* const kFormSuggestionAssistButtonPreviousElement = @"previousTap"; | 26 NSString* const kFormSuggestionAssistButtonPreviousElement = @"previousTap"; |
| 26 NSString* const kFormSuggestionAssistButtonNextElement = @"nextTap"; | 27 NSString* const kFormSuggestionAssistButtonNextElement = @"nextTap"; |
| 27 NSString* const kFormSuggestionAssistButtonDone = @"done"; | 28 NSString* const kFormSuggestionAssistButtonDone = @"done"; |
| 29 CGFloat const kInputAccessoryHeight = 44.0f; |
| 28 } // namespace autofill | 30 } // namespace autofill |
| 29 | 31 |
| 30 namespace { | 32 namespace { |
| 31 | 33 |
| 32 // Finds all views of a particular kind if class |klass| in the subview | 34 // Finds all views of a particular kind if class |klass| in the subview |
| 33 // hierarchy of the given |root| view. | 35 // hierarchy of the given |root| view. |
| 34 NSArray* FindDescendantsOfClass(UIView* root, Class klass) { | 36 NSArray* FindDescendantsOfClass(UIView* root, Class klass) { |
| 35 DCHECK(root); | 37 DCHECK(root); |
| 36 NSMutableArray* viewsToExamine = [NSMutableArray arrayWithObject:root]; | 38 NSMutableArray* viewsToExamine = [NSMutableArray arrayWithObject:root]; |
| 37 NSMutableArray* descendants = [NSMutableArray array]; | 39 NSMutableArray* descendants = [NSMutableArray array]; |
| (...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 86 | 88 |
| 87 // Computes the frame of each part of the accessory view of the keyboard. It is | 89 // Computes the frame of each part of the accessory view of the keyboard. It is |
| 88 // assumed that the keyboard has either two parts (when it is split) or one part | 90 // assumed that the keyboard has either two parts (when it is split) or one part |
| 89 // (when it is merged). | 91 // (when it is merged). |
| 90 // | 92 // |
| 91 // If there are two parts, the frame of the left part is returned in | 93 // If there are two parts, the frame of the left part is returned in |
| 92 // |leftFrame| and the frame of the right part is returned in |rightFrame|. | 94 // |leftFrame| and the frame of the right part is returned in |rightFrame|. |
| 93 // If there is only one part, the frame is returned in |leftFrame| and | 95 // If there is only one part, the frame is returned in |leftFrame| and |
| 94 // |rightFrame| has size zero. | 96 // |rightFrame| has size zero. |
| 95 // | 97 // |
| 96 // Heuristics are used to compute this information. It returns true if the | 98 // Heuristics are used to compute this information. It returns false if the |
| 97 // number of |inputAccessoryView.subviews| is not 2. | 99 // number of |inputAccessoryView.subviews| is not 2. |
| 98 bool ComputeFramesOfKeyboardParts(UIView* inputAccessoryView, | 100 bool ComputeFramesOfKeyboardParts(UIView* inputAccessoryView, |
| 99 CGRect* leftFrame, | 101 CGRect* leftFrame, |
| 100 CGRect* rightFrame) { | 102 CGRect* rightFrame) { |
| 101 // It is observed (on iOS 6) there are always two subviews in the original | 103 // It is observed (on iOS 6) there are always two subviews in the original |
| 102 // input accessory view. When the keyboard is split, each subview represents | 104 // input accessory view. When the keyboard is split, each subview represents |
| 103 // one part of the accesssary view of the keyboard. When the keyboard is | 105 // one part of the accesssary view of the keyboard. When the keyboard is |
| 104 // merged, one subview has the same frame as that of the whole accessory view | 106 // merged, one subview has the same frame as that of the whole accessory view |
| 105 // and the other has zero size with the screen width as origin.x. | 107 // and the other has zero size with the screen width as origin.x. |
| 106 // The computation here is based on this observation. | 108 // The computation here is based on this observation. |
| (...skipping 16 matching lines...) Expand all Loading... |
| 123 | 125 |
| 124 } // namespace | 126 } // namespace |
| 125 | 127 |
| 126 @interface FormInputAccessoryViewController () | 128 @interface FormInputAccessoryViewController () |
| 127 | 129 |
| 128 // Allows injection of the JsSuggestionManager. | 130 // Allows injection of the JsSuggestionManager. |
| 129 - (instancetype)initWithWebState:(web::WebState*)webState | 131 - (instancetype)initWithWebState:(web::WebState*)webState |
| 130 JSSuggestionManager:(JsSuggestionManager*)JSSuggestionManager | 132 JSSuggestionManager:(JsSuggestionManager*)JSSuggestionManager |
| 131 providers:(NSArray*)providers; | 133 providers:(NSArray*)providers; |
| 132 | 134 |
| 133 // Called when the keyboard did change frame. | 135 // Called when the keyboard will or did change frame. |
| 134 - (void)keyboardDidChangeFrame:(NSNotification*)notification; | 136 - (void)keyboardWillOrDidChangeFrame:(NSNotification*)notification; |
| 135 | 137 |
| 136 // Called when the keyboard is dismissed. | 138 // Called when the keyboard is dismissed. |
| 137 - (void)keyboardDidHide:(NSNotification*)notification; | 139 - (void)keyboardDidHide:(NSNotification*)notification; |
| 138 | 140 |
| 139 // Hides the subviews in |accessoryView|. | 141 // Hides the subviews in |accessoryView|. |
| 140 - (void)hideSubviewsInOriginalAccessoryView:(UIView*)accessoryView; | 142 - (void)hideSubviewsInOriginalAccessoryView:(UIView*)accessoryView; |
| 141 | 143 |
| 142 // Attempts to execute/tap/send-an-event-to the iOS built-in "next" and | 144 // Attempts to execute/tap/send-an-event-to the iOS built-in "next" and |
| 143 // "previous" form assist controls. Returns NO if this attempt failed, YES | 145 // "previous" form assist controls. Returns NO if this attempt failed, YES |
| 144 // otherwise. [HACK] | 146 // otherwise. [HACK] |
| (...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 180 base::scoped_nsobject<JsSuggestionManager> _JSSuggestionManager; | 182 base::scoped_nsobject<JsSuggestionManager> _JSSuggestionManager; |
| 181 | 183 |
| 182 // The original subviews in keyboard accessory view that were originally not | 184 // The original subviews in keyboard accessory view that were originally not |
| 183 // hidden but were hidden when showing Autofill suggestions. | 185 // hidden but were hidden when showing Autofill suggestions. |
| 184 base::scoped_nsobject<NSMutableArray> _hiddenOriginalSubviews; | 186 base::scoped_nsobject<NSMutableArray> _hiddenOriginalSubviews; |
| 185 | 187 |
| 186 // The objects that can provide a custom input accessory view while filling | 188 // The objects that can provide a custom input accessory view while filling |
| 187 // forms. | 189 // forms. |
| 188 base::scoped_nsobject<NSArray> _providers; | 190 base::scoped_nsobject<NSArray> _providers; |
| 189 | 191 |
| 192 // Whether suggestions have previously been shown. |
| 193 BOOL _suggestionsHaveBeenShown; |
| 194 |
| 190 // The object that manages the currently-shown custom accessory view. | 195 // The object that manages the currently-shown custom accessory view. |
| 191 base::WeakNSProtocol<id<FormInputAccessoryViewProvider>> _currentProvider; | 196 base::WeakNSProtocol<id<FormInputAccessoryViewProvider>> _currentProvider; |
| 192 } | 197 } |
| 193 | 198 |
| 194 - (instancetype)initWithWebState:(web::WebState*)webState | 199 - (instancetype)initWithWebState:(web::WebState*)webState |
| 195 providers:(NSArray*)providers { | 200 providers:(NSArray*)providers { |
| 196 JsSuggestionManager* suggestionManager = | 201 JsSuggestionManager* suggestionManager = |
| 197 base::mac::ObjCCastStrict<JsSuggestionManager>( | 202 base::mac::ObjCCastStrict<JsSuggestionManager>( |
| 198 [webState->GetJSInjectionReceiver() | 203 [webState->GetJSInjectionReceiver() |
| 199 instanceOfClass:[JsSuggestionManager class]]); | 204 instanceOfClass:[JsSuggestionManager class]]); |
| 200 return [self initWithWebState:webState | 205 return [self initWithWebState:webState |
| 201 JSSuggestionManager:suggestionManager | 206 JSSuggestionManager:suggestionManager |
| 202 providers:providers]; | 207 providers:providers]; |
| 203 } | 208 } |
| 204 | 209 |
| 205 - (instancetype)initWithWebState:(web::WebState*)webState | 210 - (instancetype)initWithWebState:(web::WebState*)webState |
| 206 JSSuggestionManager:(JsSuggestionManager*)JSSuggestionManager | 211 JSSuggestionManager:(JsSuggestionManager*)JSSuggestionManager |
| 207 providers:(NSArray*)providers { | 212 providers:(NSArray*)providers { |
| 208 self = [super init]; | 213 self = [super init]; |
| 209 if (self) { | 214 if (self) { |
| 210 _JSSuggestionManager.reset([JSSuggestionManager retain]); | 215 _JSSuggestionManager.reset([JSSuggestionManager retain]); |
| 211 _hiddenOriginalSubviews.reset([[NSMutableArray alloc] init]); | 216 _hiddenOriginalSubviews.reset([[NSMutableArray alloc] init]); |
| 212 _webStateObserverBridge.reset( | 217 _webStateObserverBridge.reset( |
| 213 new web::WebStateObserverBridge(webState, self)); | 218 new web::WebStateObserverBridge(webState, self)); |
| 214 _providers.reset([providers copy]); | 219 _providers.reset([providers copy]); |
| 215 // There is no defined relation on the timing of JavaScript events and | 220 _suggestionsHaveBeenShown = NO; |
| 216 // keyboard showing up. So it is necessary to listen to the keyboard | |
| 217 // notification to make sure the keyboard is updated. | |
| 218 [[NSNotificationCenter defaultCenter] | |
| 219 addObserver:self | |
| 220 selector:@selector(keyboardDidChangeFrame:) | |
| 221 name:UIKeyboardDidChangeFrameNotification | |
| 222 object:nil]; | |
| 223 [[NSNotificationCenter defaultCenter] | |
| 224 addObserver:self | |
| 225 selector:@selector(keyboardDidHide:) | |
| 226 name:UIKeyboardDidHideNotification | |
| 227 object:nil]; | |
| 228 } | 221 } |
| 229 return self; | 222 return self; |
| 230 } | 223 } |
| 231 | 224 |
| 225 - (void)wasShown { |
| 226 // There is no defined relation on the timing of JavaScript events and |
| 227 // keyboard showing up. So it is necessary to listen to the keyboard |
| 228 // notification to make sure the keyboard is updated. |
| 229 if (base::ios::IsRunningOnIOS9OrLater() && IsIPadIdiom()) { |
| 230 [[NSNotificationCenter defaultCenter] |
| 231 addObserver:self |
| 232 selector:@selector(keyboardWillOrDidChangeFrame:) |
| 233 name:UIKeyboardWillChangeFrameNotification |
| 234 object:nil]; |
| 235 [[NSNotificationCenter defaultCenter] |
| 236 addObserver:self |
| 237 selector:@selector(textInputDidBeginEditing:) |
| 238 name:UITextFieldTextDidBeginEditingNotification |
| 239 object:nil]; |
| 240 [[NSNotificationCenter defaultCenter] |
| 241 addObserver:self |
| 242 selector:@selector(textInputDidBeginEditing:) |
| 243 name:UITextViewTextDidBeginEditingNotification |
| 244 object:nil]; |
| 245 } |
| 246 [[NSNotificationCenter defaultCenter] |
| 247 addObserver:self |
| 248 selector:@selector(keyboardWillOrDidChangeFrame:) |
| 249 name:UIKeyboardDidChangeFrameNotification |
| 250 object:nil]; |
| 251 [[NSNotificationCenter defaultCenter] |
| 252 addObserver:self |
| 253 selector:@selector(keyboardDidHide:) |
| 254 name:UIKeyboardDidHideNotification |
| 255 object:nil]; |
| 256 } |
| 257 |
| 258 - (void)wasHidden { |
| 259 [_customAccessoryView removeFromSuperview]; |
| 260 [[NSNotificationCenter defaultCenter] removeObserver:self]; |
| 261 } |
| 262 |
| 232 - (void)dealloc { | 263 - (void)dealloc { |
| 233 [[NSNotificationCenter defaultCenter] removeObserver:self]; | 264 [[NSNotificationCenter defaultCenter] removeObserver:self]; |
| 234 [super dealloc]; | 265 [super dealloc]; |
| 235 } | 266 } |
| 236 | 267 |
| 237 - (web::WebState*)webState { | 268 - (web::WebState*)webState { |
| 238 return _webStateObserverBridge ? _webStateObserverBridge->web_state() | 269 return _webStateObserverBridge ? _webStateObserverBridge->web_state() |
| 239 : nullptr; | 270 : nullptr; |
| 240 } | 271 } |
| 241 | 272 |
| 242 - (id<CRWWebViewProxy>)webViewProxy { | 273 - (id<CRWWebViewProxy>)webViewProxy { |
| 243 return self.webState ? self.webState->GetWebViewProxy() : nil; | 274 return self.webState ? self.webState->GetWebViewProxy() : nil; |
| 244 } | 275 } |
| 245 | 276 |
| 246 - (void)hideSubviewsInOriginalAccessoryView:(UIView*)accessoryView { | 277 - (void)hideSubviewsInOriginalAccessoryView:(UIView*)accessoryView { |
| 247 for (UIView* subview in [accessoryView subviews]) { | 278 for (UIView* subview in [accessoryView subviews]) { |
| 248 if (!subview.hidden) { | 279 if (!subview.hidden) { |
| 249 [_hiddenOriginalSubviews addObject:subview]; | 280 [_hiddenOriginalSubviews addObject:subview]; |
| 250 subview.hidden = YES; | 281 subview.hidden = YES; |
| 251 } | 282 } |
| 252 } | 283 } |
| 253 } | 284 } |
| 254 | 285 |
| 255 - (void)showCustomInputAccessoryView:(UIView*)view { | 286 - (void)showCustomInputAccessoryView:(UIView*)view { |
| 256 [self restoreDefaultInputAccessoryView]; | 287 DCHECK(view); |
| 257 CGRect leftFrame; | 288 if (base::ios::IsRunningOnIOS9OrLater() && IsIPadIdiom()) { |
| 258 CGRect rightFrame; | 289 // On iPads running iOS 9 or later, there's no inputAccessoryView available |
| 259 UIView* inputAccessoryView = [self.webViewProxy getKeyboardAccessory]; | 290 // so we attach the custom view directly to the keyboard view instead. |
| 260 if (ComputeFramesOfKeyboardParts(inputAccessoryView, &leftFrame, | 291 [_customAccessoryView removeFromSuperview]; |
| 261 &rightFrame)) { | 292 |
| 262 [self hideSubviewsInOriginalAccessoryView:inputAccessoryView]; | 293 // If the keyboard isn't visible or no suggestions have been triggered yet, |
| 294 // don't show the custom view. |
| 295 int numSuggestions = |
| 296 [[static_cast<FormSuggestionView*>(view) suggestions] count]; |
| 297 if (CGRectIntersection([UIScreen mainScreen].bounds, _keyboardFrame) |
| 298 .size.height == 0 || |
| 299 CGRectEqualToRect(_keyboardFrame, CGRectZero) || |
| 300 (!_suggestionsHaveBeenShown && numSuggestions == 0)) { |
| 301 _customAccessoryView.reset(); |
| 302 return; |
| 303 } |
| 304 _suggestionsHaveBeenShown = YES; |
| 305 |
| 306 CGFloat height = autofill::kInputAccessoryHeight; |
| 307 CGRect frame = CGRectMake(_keyboardFrame.origin.x, -height, |
| 308 _keyboardFrame.size.width, height); |
| 263 _customAccessoryView.reset( | 309 _customAccessoryView.reset( |
| 264 [[FormInputAccessoryView alloc] initWithFrame:inputAccessoryView.frame | 310 [[FormInputAccessoryView alloc] initWithFrame:frame customView:view]); |
| 265 delegate:self | 311 UIView* keyboardView = [self getKeyboardView]; |
| 266 customView:view | 312 DCHECK(keyboardView); |
| 267 leftFrame:leftFrame | 313 [keyboardView addSubview:_customAccessoryView]; |
| 268 rightFrame:rightFrame]); | 314 } else { |
| 269 [inputAccessoryView addSubview:_customAccessoryView]; | 315 // On all other versions, the custom view replaces the default UI of the |
| 316 // inputAccessoryView. |
| 317 [self restoreDefaultInputAccessoryView]; |
| 318 CGRect leftFrame; |
| 319 CGRect rightFrame; |
| 320 UIView* inputAccessoryView = [self.webViewProxy getKeyboardAccessory]; |
| 321 if (ComputeFramesOfKeyboardParts(inputAccessoryView, &leftFrame, |
| 322 &rightFrame)) { |
| 323 [self hideSubviewsInOriginalAccessoryView:inputAccessoryView]; |
| 324 _customAccessoryView.reset([[FormInputAccessoryView alloc] |
| 325 initWithFrame:inputAccessoryView.frame |
| 326 delegate:self |
| 327 customView:view |
| 328 leftFrame:leftFrame |
| 329 rightFrame:rightFrame]); |
| 330 [inputAccessoryView addSubview:_customAccessoryView]; |
| 331 } |
| 270 } | 332 } |
| 271 } | 333 } |
| 272 | 334 |
| 273 - (void)restoreDefaultInputAccessoryView { | 335 - (void)restoreDefaultInputAccessoryView { |
| 274 [_customAccessoryView removeFromSuperview]; | 336 [_customAccessoryView removeFromSuperview]; |
| 275 _customAccessoryView.reset(); | 337 _customAccessoryView.reset(); |
| 276 for (UIView* subview in _hiddenOriginalSubviews.get()) { | 338 for (UIView* subview in _hiddenOriginalSubviews.get()) { |
| 277 subview.hidden = NO; | 339 subview.hidden = NO; |
| 278 } | 340 } |
| 279 [_hiddenOriginalSubviews removeAllObjects]; | 341 [_hiddenOriginalSubviews removeAllObjects]; |
| (...skipping 194 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 474 webState:webState | 536 webState:webState |
| 475 accessoryViewUpdateBlock:readyCompletion]; | 537 accessoryViewUpdateBlock:readyCompletion]; |
| 476 }; | 538 }; |
| 477 | 539 |
| 478 // Run all the blocks in |findProviderBlocks| until one invokes its | 540 // Run all the blocks in |findProviderBlocks| until one invokes its |
| 479 // completion with YES. The first one to do so will be passed to | 541 // completion with YES. The first one to do so will be passed to |
| 480 // |onProviderFound|. | 542 // |onProviderFound|. |
| 481 passwords::RunSearchPipeline(findProviderBlocks, onProviderFound); | 543 passwords::RunSearchPipeline(findProviderBlocks, onProviderFound); |
| 482 } | 544 } |
| 483 | 545 |
| 484 - (void)keyboardDidChangeFrame:(NSNotification*)notification { | 546 - (UIView*)getKeyboardView { |
| 547 NSArray* windows = [UIApplication sharedApplication].windows; |
| 548 if (windows.count < 2) |
| 549 return nil; |
| 550 |
| 551 UIWindow* window = windows[1]; |
| 552 for (UIView* subview in window.subviews) { |
| 553 if ([NSStringFromClass([subview class]) rangeOfString:@"PeripheralHost"] |
| 554 .location != NSNotFound) { |
| 555 return subview; |
| 556 } |
| 557 if ([NSStringFromClass([subview class]) rangeOfString:@"SetContainer"] |
| 558 .location != NSNotFound) { |
| 559 for (UIView* subsubview in subview.subviews) { |
| 560 if ([NSStringFromClass([subsubview class]) rangeOfString:@"SetHost"] |
| 561 .location != NSNotFound) { |
| 562 return subsubview; |
| 563 } |
| 564 } |
| 565 } |
| 566 } |
| 567 |
| 568 return nil; |
| 569 } |
| 570 |
| 571 - (void)keyboardWillOrDidChangeFrame:(NSNotification*)notification { |
| 485 if (!self.webState || !_currentProvider) | 572 if (!self.webState || !_currentProvider) |
| 486 return; | 573 return; |
| 487 CGRect keyboardFrame = | 574 CGRect keyboardFrame = |
| 488 [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue]; | 575 [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue]; |
| 489 // With iOS8 (beta) this method can be called even when the rect has not | 576 // With iOS8 (beta) this method can be called even when the rect has not |
| 490 // changed. When this is detected we exit early. | 577 // changed. When this is detected we exit early. |
| 491 if (CGRectEqualToRect(CGRectIntegral(_keyboardFrame), | 578 if (CGRectEqualToRect(CGRectIntegral(_keyboardFrame), |
| 492 CGRectIntegral(keyboardFrame))) { | 579 CGRectIntegral(keyboardFrame))) { |
| 493 return; | 580 return; |
| 494 } | 581 } |
| 495 _keyboardFrame = keyboardFrame; | 582 _keyboardFrame = keyboardFrame; |
| 496 [_currentProvider resizeAccessoryView]; | 583 [_currentProvider resizeAccessoryView]; |
| 497 } | 584 } |
| 498 | 585 |
| 586 // On iPads running iOS 9 or later, when any text field or text view (e.g. |
| 587 // omnibox, settings, card unmask dialog) begins editing, reset ourselves so |
| 588 // that we don't present our custom view over the keyboard. |
| 589 - (void)textInputDidBeginEditing:(NSNotification*)notification { |
| 590 [self reset]; |
| 591 } |
| 592 |
| 499 - (void)keyboardDidHide:(NSNotification*)notification { | 593 - (void)keyboardDidHide:(NSNotification*)notification { |
| 500 _keyboardFrame = CGRectZero; | 594 _keyboardFrame = CGRectZero; |
| 501 } | 595 } |
| 502 | 596 |
| 503 @end | 597 @end |
| OLD | NEW |