Chromium Code Reviews| 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 } | |
| 236 [[NSNotificationCenter defaultCenter] | |
| 237 addObserver:self | |
| 238 selector:@selector(keyboardWillOrDidChangeFrame:) | |
| 239 name:UIKeyboardDidChangeFrameNotification | |
| 240 object:nil]; | |
| 241 [[NSNotificationCenter defaultCenter] | |
| 242 addObserver:self | |
| 243 selector:@selector(keyboardDidHide:) | |
| 244 name:UIKeyboardDidHideNotification | |
| 245 object:nil]; | |
| 246 } | |
| 247 | |
| 248 - (void)wasHidden { | |
| 249 [_customAccessoryView removeFromSuperview]; | |
| 250 [[NSNotificationCenter defaultCenter] removeObserver:self]; | |
| 251 } | |
| 252 | |
| 232 - (void)dealloc { | 253 - (void)dealloc { |
| 233 [[NSNotificationCenter defaultCenter] removeObserver:self]; | 254 [[NSNotificationCenter defaultCenter] removeObserver:self]; |
| 234 [super dealloc]; | 255 [super dealloc]; |
| 235 } | 256 } |
| 236 | 257 |
| 237 - (web::WebState*)webState { | 258 - (web::WebState*)webState { |
| 238 return _webStateObserverBridge ? _webStateObserverBridge->web_state() | 259 return _webStateObserverBridge ? _webStateObserverBridge->web_state() |
| 239 : nullptr; | 260 : nullptr; |
| 240 } | 261 } |
| 241 | 262 |
| 242 - (id<CRWWebViewProxy>)webViewProxy { | 263 - (id<CRWWebViewProxy>)webViewProxy { |
| 243 return self.webState ? self.webState->GetWebViewProxy() : nil; | 264 return self.webState ? self.webState->GetWebViewProxy() : nil; |
| 244 } | 265 } |
| 245 | 266 |
| 246 - (void)hideSubviewsInOriginalAccessoryView:(UIView*)accessoryView { | 267 - (void)hideSubviewsInOriginalAccessoryView:(UIView*)accessoryView { |
| 247 for (UIView* subview in [accessoryView subviews]) { | 268 for (UIView* subview in [accessoryView subviews]) { |
| 248 if (!subview.hidden) { | 269 if (!subview.hidden) { |
| 249 [_hiddenOriginalSubviews addObject:subview]; | 270 [_hiddenOriginalSubviews addObject:subview]; |
| 250 subview.hidden = YES; | 271 subview.hidden = YES; |
| 251 } | 272 } |
| 252 } | 273 } |
| 253 } | 274 } |
| 254 | 275 |
| 255 - (void)showCustomInputAccessoryView:(UIView*)view { | 276 - (void)showCustomInputAccessoryView:(UIView*)view { |
|
justincohen
2015/08/20 17:29:16
DCHECK(view)
Justin Donnelly
2015/08/20 20:24:28
Done.
| |
| 256 [self restoreDefaultInputAccessoryView]; | 277 if (base::ios::IsRunningOnIOS9OrLater() && IsIPadIdiom()) { |
| 257 CGRect leftFrame; | 278 // On iPads running iOS 9 or later, there's no inputAccessoryView available |
| 258 CGRect rightFrame; | 279 // so we attach the custom view directly to the keyboard view instead. |
| 259 UIView* inputAccessoryView = [self.webViewProxy getKeyboardAccessory]; | 280 [_customAccessoryView removeFromSuperview]; |
| 260 if (ComputeFramesOfKeyboardParts(inputAccessoryView, &leftFrame, | 281 UIViewController* rootViewController = |
| 261 &rightFrame)) { | 282 [UIApplication sharedApplication].keyWindow.rootViewController; |
| 262 [self hideSubviewsInOriginalAccessoryView:inputAccessoryView]; | 283 |
| 284 // If the keyboard isn't visible, another controller has been presented over | |
| 285 // the webview (as in the case of the credit card unmask dialog), or no | |
| 286 // suggestions have been trigger yet, don't show the custom view. | |
|
justincohen
2015/08/20 17:29:16
triggered
Justin Donnelly
2015/08/20 20:24:28
Done.
| |
| 287 int numSuggestions = | |
| 288 [[static_cast<FormSuggestionView*>(view) suggestions] count]; | |
| 289 if (CGRectIntersection([UIScreen mainScreen].bounds, _keyboardFrame) | |
| 290 .size.height == 0 || | |
| 291 CGRectEqualToRect(_keyboardFrame, CGRectZero) || | |
| 292 [rootViewController presentedViewController] || | |
| 293 (!_suggestionsHaveBeenShown && numSuggestions == 0)) { | |
| 294 _customAccessoryView.reset(); | |
| 295 return; | |
| 296 } | |
| 297 _suggestionsHaveBeenShown = YES; | |
| 298 | |
| 299 CGFloat height = autofill::kInputAccessoryHeight; | |
| 300 CGRect frame = CGRectMake(_keyboardFrame.origin.x, -height, | |
| 301 _keyboardFrame.size.width, height); | |
| 263 _customAccessoryView.reset( | 302 _customAccessoryView.reset( |
| 264 [[FormInputAccessoryView alloc] initWithFrame:inputAccessoryView.frame | 303 [[FormInputAccessoryView alloc] initWithFrame:frame customView:view]); |
| 265 delegate:self | 304 UIView* keyboardView = [self getKeyboardView]; |
| 266 customView:view | 305 DCHECK(keyboardView); |
|
justincohen
2015/08/20 17:29:16
I'm curious what keyboardView is if you have a phy
Justin Donnelly
2015/08/20 20:24:28
I actually do most of my testing in the simulator
| |
| 267 leftFrame:leftFrame | 306 [keyboardView addSubview:_customAccessoryView]; |
| 268 rightFrame:rightFrame]); | 307 } else { |
| 269 [inputAccessoryView addSubview:_customAccessoryView]; | 308 // On all other versions, the custom view replaces the default UI of the |
| 309 // inputAccessoryView. | |
| 310 [self restoreDefaultInputAccessoryView]; | |
| 311 CGRect leftFrame; | |
| 312 CGRect rightFrame; | |
| 313 UIView* inputAccessoryView = [self.webViewProxy getKeyboardAccessory]; | |
| 314 if (ComputeFramesOfKeyboardParts(inputAccessoryView, &leftFrame, | |
| 315 &rightFrame)) { | |
| 316 [self hideSubviewsInOriginalAccessoryView:inputAccessoryView]; | |
| 317 _customAccessoryView.reset([[FormInputAccessoryView alloc] | |
| 318 initWithFrame:inputAccessoryView.frame | |
| 319 delegate:self | |
| 320 customView:view | |
| 321 leftFrame:leftFrame | |
| 322 rightFrame:rightFrame]); | |
| 323 [inputAccessoryView addSubview:_customAccessoryView]; | |
| 324 } | |
| 270 } | 325 } |
| 271 } | 326 } |
| 272 | 327 |
| 273 - (void)restoreDefaultInputAccessoryView { | 328 - (void)restoreDefaultInputAccessoryView { |
| 274 [_customAccessoryView removeFromSuperview]; | 329 [_customAccessoryView removeFromSuperview]; |
| 275 _customAccessoryView.reset(); | 330 _customAccessoryView.reset(); |
| 276 for (UIView* subview in _hiddenOriginalSubviews.get()) { | 331 for (UIView* subview in _hiddenOriginalSubviews.get()) { |
| 277 subview.hidden = NO; | 332 subview.hidden = NO; |
| 278 } | 333 } |
| 279 [_hiddenOriginalSubviews removeAllObjects]; | 334 [_hiddenOriginalSubviews removeAllObjects]; |
| (...skipping 194 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 474 webState:webState | 529 webState:webState |
| 475 accessoryViewUpdateBlock:readyCompletion]; | 530 accessoryViewUpdateBlock:readyCompletion]; |
| 476 }; | 531 }; |
| 477 | 532 |
| 478 // Run all the blocks in |findProviderBlocks| until one invokes its | 533 // Run all the blocks in |findProviderBlocks| until one invokes its |
| 479 // completion with YES. The first one to do so will be passed to | 534 // completion with YES. The first one to do so will be passed to |
| 480 // |onProviderFound|. | 535 // |onProviderFound|. |
| 481 passwords::RunSearchPipeline(findProviderBlocks, onProviderFound); | 536 passwords::RunSearchPipeline(findProviderBlocks, onProviderFound); |
| 482 } | 537 } |
| 483 | 538 |
| 484 - (void)keyboardDidChangeFrame:(NSNotification*)notification { | 539 - (UIView*)getKeyboardView { |
| 540 NSArray* windows = [UIApplication sharedApplication].windows; | |
| 541 if (windows.count < 2) | |
| 542 return nil; | |
| 543 | |
| 544 UIWindow* window = windows[1]; | |
| 545 for (UIView* subview in window.subviews) { | |
| 546 if ([NSStringFromClass([subview class]) | |
| 547 isEqualToString:@"UIPeripheralHost"]) { | |
| 548 return subview; | |
| 549 } | |
| 550 if ([NSStringFromClass([subview class]) | |
| 551 isEqualToString:@"UIInputSetContainerView"]) { | |
| 552 for (UIView* subsubview in subview.subviews) { | |
| 553 if ([NSStringFromClass([subsubview class]) | |
| 554 isEqualToString:@"UIInputSetHostView"]) { | |
| 555 return subsubview; | |
| 556 } | |
| 557 } | |
| 558 } | |
| 559 } | |
| 560 | |
| 561 return nil; | |
| 562 } | |
| 563 | |
| 564 - (void)keyboardWillOrDidChangeFrame:(NSNotification*)notification { | |
| 485 if (!self.webState || !_currentProvider) | 565 if (!self.webState || !_currentProvider) |
| 486 return; | 566 return; |
| 487 CGRect keyboardFrame = | 567 CGRect keyboardFrame = |
| 488 [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue]; | 568 [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue]; |
| 489 // With iOS8 (beta) this method can be called even when the rect has not | 569 // With iOS8 (beta) this method can be called even when the rect has not |
| 490 // changed. When this is detected we exit early. | 570 // changed. When this is detected we exit early. |
| 491 if (CGRectEqualToRect(CGRectIntegral(_keyboardFrame), | 571 if (CGRectEqualToRect(CGRectIntegral(_keyboardFrame), |
| 492 CGRectIntegral(keyboardFrame))) { | 572 CGRectIntegral(keyboardFrame))) { |
| 493 return; | 573 return; |
| 494 } | 574 } |
| 495 _keyboardFrame = keyboardFrame; | 575 _keyboardFrame = keyboardFrame; |
| 496 [_currentProvider resizeAccessoryView]; | 576 [_currentProvider resizeAccessoryView]; |
| 497 } | 577 } |
| 498 | 578 |
| 499 - (void)keyboardDidHide:(NSNotification*)notification { | 579 - (void)keyboardDidHide:(NSNotification*)notification { |
| 500 _keyboardFrame = CGRectZero; | 580 _keyboardFrame = CGRectZero; |
| 501 } | 581 } |
| 502 | 582 |
| 503 @end | 583 @end |
| OLD | NEW |