OLD | NEW |
(Empty) | |
| 1 // Copyright 2012 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #import "ios/chrome/browser/ui/find_bar/find_bar_controller_ios.h" |
| 6 |
| 7 #include "base/format_macros.h" |
| 8 #include "base/i18n/rtl.h" |
| 9 #include "base/ios/ios_util.h" |
| 10 #include "base/mac/bundle_locations.h" |
| 11 #include "base/mac/foundation_util.h" |
| 12 #include "base/mac/objc_property_releaser.h" |
| 13 #include "base/mac/scoped_nsobject.h" |
| 14 #include "base/strings/sys_string_conversions.h" |
| 15 #include "components/strings/grit/components_strings.h" |
| 16 #import "ios/chrome/browser/find_in_page/find_in_page_controller.h" |
| 17 #import "ios/chrome/browser/find_in_page/find_in_page_model.h" |
| 18 #import "ios/chrome/browser/ui/UIView+SizeClassSupport.h" |
| 19 #import "ios/chrome/browser/ui/commands/UIKit+ChromeExecuteCommand.h" |
| 20 #import "ios/chrome/browser/ui/find_bar/find_bar_view.h" |
| 21 #include "ios/chrome/browser/ui/ui_util.h" |
| 22 #include "ui/base/l10n/l10n_util_mac.h" |
| 23 #include "ui/base/resource/resource_bundle.h" |
| 24 |
| 25 NSString* const kFindInPageContainerViewId = @"kFindInPageContainerViewId"; |
| 26 |
| 27 namespace { |
| 28 |
| 29 // Find Bar height. |
| 30 // Right padding on iPad find bar. |
| 31 const CGFloat kFindBarIPhoneHeight = 56; |
| 32 const CGFloat kFindBarIPadHeight = 62; |
| 33 |
| 34 // Find Bar animation drop down duration. |
| 35 const CGFloat kAnimationDuration = 0.15; |
| 36 |
| 37 // For the first |kSearchDelayChars| characters, delay by |kSearchLongDelay| |
| 38 // For the remaining characters, delay by |kSearchShortDelay|. |
| 39 const NSUInteger kSearchDelayChars = 3; |
| 40 const NSTimeInterval kSearchLongDelay = 1.0; |
| 41 const NSTimeInterval kSearchShortDelay = 0.100; |
| 42 |
| 43 } // anonymous namespace |
| 44 |
| 45 #pragma mark - FindBarControllerIOS |
| 46 |
| 47 @interface FindBarControllerIOS ()<UITextFieldDelegate> { |
| 48 base::mac::ObjCPropertyReleaser _propertyReleaser_FindInPageController; |
| 49 } |
| 50 |
| 51 // Set up iPad UI |
| 52 - (void)setUpIPad; |
| 53 // Set up iPhone UI |
| 54 - (void)setUpIPhone; |
| 55 // Animate find bar to iPad top right, or, when possible, to align find bar |
| 56 // horizontally with |alignmentFrame|. |
| 57 - (void)showIPadFindBarView:(BOOL)animate |
| 58 intoView:(UIView*)parentView |
| 59 withFrame:(CGRect)targetFrame |
| 60 alignWithFrame:(CGRect)alignmentFrame |
| 61 selectText:(BOOL)selectText; |
| 62 // Animate find bar over iPhone toolbar. |
| 63 - (void)showIPhoneFindBarView:(BOOL)animate |
| 64 intoView:(UIView*)parentView |
| 65 withFrame:(CGRect)targetFrame |
| 66 selectText:(BOOL)selectText; |
| 67 // Returns the appropriate variant of the image for |image_name| based on |
| 68 // |_isIncognito| and device idiom. |
| 69 - (UIImage*)imageWithName:(NSString*)image_name; |
| 70 // Delay searching for the first |kSearchDelayChars| characters of a search term |
| 71 // to give time for a user to type out a longer word. Short words are currently |
| 72 // very inefficient and lock up the UIWebView. |
| 73 - (void)editingChanged:(id)sender; |
| 74 // Return the expected find bar height. This will include the status bar height |
| 75 // when running iOS7 on an iPhone. |
| 76 - (CGFloat)findBarHeight; |
| 77 // Selects text in such way that selection menu does not appear and |
| 78 // a11y label is read. When -[UITextField selectAll:] is used, iOS |
| 79 // will read "Select All" instead of a11y label. |
| 80 - (void)selectAllText; |
| 81 |
| 82 // Redefined to be readwrite. This view acts as background for |findBarView| and |
| 83 // contains it as a subview. |
| 84 @property(nonatomic, readwrite, strong) UIView* view; |
| 85 // The view containing all the buttons and textfields that is common between |
| 86 // iPhone and iPad. |
| 87 @property(nonatomic, retain) FindBarView* findBarView; |
| 88 // Typing delay timer. |
| 89 @property(nonatomic, retain) NSTimer* delayTimer; |
| 90 // Yes if incognito. |
| 91 @property(nonatomic, assign) BOOL isIncognito; |
| 92 @end |
| 93 |
| 94 @implementation FindBarControllerIOS |
| 95 |
| 96 @synthesize view = _view; |
| 97 @synthesize findBarView = _findBarView; |
| 98 @synthesize delayTimer = _delayTimer; |
| 99 @synthesize isIncognito = _isIncognito; |
| 100 |
| 101 #pragma mark - Lifecycle |
| 102 |
| 103 - (instancetype)initWithIncognito:(BOOL)isIncognito { |
| 104 self = [super init]; |
| 105 if (self) { |
| 106 _propertyReleaser_FindInPageController.Init(self, |
| 107 [FindBarControllerIOS class]); |
| 108 _isIncognito = isIncognito; |
| 109 } |
| 110 return self; |
| 111 } |
| 112 |
| 113 #pragma mark View Setup & Teardown |
| 114 |
| 115 - (UIView*)newFindBarView { |
| 116 BOOL isIPad = IsIPadIdiom(); |
| 117 UIView* findBarBackground = nil; |
| 118 if (isIPad) { |
| 119 // Future self.view. Contains only |contentView|. Is an image view that is |
| 120 // typecast elsewhere but always is exposed as a UIView. |
| 121 findBarBackground = |
| 122 [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 345, 62)]; |
| 123 findBarBackground.backgroundColor = [UIColor clearColor]; |
| 124 findBarBackground.userInteractionEnabled = YES; |
| 125 } else { |
| 126 findBarBackground = |
| 127 [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 56)]; |
| 128 findBarBackground.backgroundColor = [UIColor whiteColor]; |
| 129 } |
| 130 |
| 131 self.findBarView = [[[FindBarView alloc] |
| 132 initWithDarkAppearance:self.isIncognito && !IsIPadIdiom()] autorelease]; |
| 133 [findBarBackground addSubview:self.findBarView]; |
| 134 self.findBarView.translatesAutoresizingMaskIntoConstraints = NO; |
| 135 base::scoped_nsobject<NSMutableArray> constraints( |
| 136 [[NSMutableArray alloc] init]); |
| 137 [constraints addObjectsFromArray:@[ |
| 138 [self.findBarView.trailingAnchor |
| 139 constraintEqualToAnchor:findBarBackground.trailingAnchor], |
| 140 [self.findBarView.leadingAnchor |
| 141 constraintEqualToAnchor:findBarBackground.leadingAnchor], |
| 142 [self.findBarView.heightAnchor constraintEqualToConstant:56.0f] |
| 143 ]]; |
| 144 |
| 145 if (isIPad) { |
| 146 [constraints |
| 147 addObject:[self.findBarView.centerYAnchor |
| 148 constraintEqualToAnchor:findBarBackground.centerYAnchor |
| 149 constant:-2]]; |
| 150 } else { |
| 151 [constraints |
| 152 addObject:[self.findBarView.bottomAnchor |
| 153 constraintEqualToAnchor:findBarBackground.bottomAnchor]]; |
| 154 } |
| 155 |
| 156 [NSLayoutConstraint activateConstraints:constraints]; |
| 157 |
| 158 self.findBarView.inputField.delegate = self; |
| 159 [self.findBarView.inputField addTarget:self |
| 160 action:@selector(editingChanged:) |
| 161 forControlEvents:UIControlEventEditingChanged]; |
| 162 [self.findBarView.nextButton addTarget:self |
| 163 action:@selector(hideKeyboard:) |
| 164 forControlEvents:UIControlEventTouchUpInside]; |
| 165 [self.findBarView.previousButton addTarget:self |
| 166 action:@selector(hideKeyboard:) |
| 167 forControlEvents:UIControlEventTouchUpInside]; |
| 168 |
| 169 return findBarBackground; |
| 170 } |
| 171 |
| 172 - (void)setupViewInView:(UIView*)view { |
| 173 self.view = [[self newFindBarView] autorelease]; |
| 174 |
| 175 // Idiom specific setup. |
| 176 if ([self shouldShowCompactSearchBarInView:view]) |
| 177 [self setUpIPhone]; |
| 178 else |
| 179 [self setUpIPad]; |
| 180 |
| 181 self.view.accessibilityIdentifier = kFindInPageContainerViewId; |
| 182 } |
| 183 |
| 184 - (void)teardownView { |
| 185 [self.view removeFromSuperview]; |
| 186 self.view = nil; |
| 187 } |
| 188 |
| 189 - (void)setUpIPhone { |
| 190 CGRect frame = self.view.frame; |
| 191 frame.size.height = [self findBarHeight]; |
| 192 self.view.frame = frame; |
| 193 |
| 194 if (self.isIncognito) { |
| 195 [self.view setBackgroundColor:[UIColor colorWithWhite:115 / 255.0 alpha:1]]; |
| 196 } |
| 197 } |
| 198 |
| 199 - (void)setUpIPad { |
| 200 self.view.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin; |
| 201 UIEdgeInsets backgroundInsets = UIEdgeInsetsMake(6, 9, 10, 8); |
| 202 UIImage* bgImage = [UIImage imageNamed:@"find_bg"]; |
| 203 bgImage = [bgImage resizableImageWithCapInsets:backgroundInsets]; |
| 204 UIImageView* bgView = (UIImageView*)self.view; |
| 205 [bgView setImage:bgImage]; |
| 206 } |
| 207 |
| 208 #pragma mark - Public |
| 209 |
| 210 - (NSString*)searchTerm { |
| 211 return [self.findBarView.inputField text]; |
| 212 } |
| 213 |
| 214 - (BOOL)isFindInPageShown { |
| 215 return self.view != nil; |
| 216 } |
| 217 |
| 218 - (BOOL)isFocused { |
| 219 return [self.findBarView.inputField isFirstResponder]; |
| 220 } |
| 221 |
| 222 - (void)updateResultsCount:(FindInPageModel*)model { |
| 223 [self updateWithMatchNumber:model.currentIndex |
| 224 matchCount:model.matches |
| 225 searchText:model.text]; |
| 226 } |
| 227 |
| 228 - (void)updateView:(FindInPageModel*)model |
| 229 initialUpdate:(BOOL)initialUpdate |
| 230 focusTextfield:(BOOL)focusTextfield { |
| 231 [self.delayTimer invalidate]; |
| 232 self.delayTimer = nil; |
| 233 |
| 234 if (initialUpdate) { |
| 235 // Set initial text and first search. |
| 236 [self.findBarView.inputField setText:model.text]; |
| 237 [self editingChanged:self.findBarView.inputField]; |
| 238 } |
| 239 |
| 240 // Focus input field if necessary. |
| 241 if (focusTextfield) { |
| 242 [self.findBarView.inputField becomeFirstResponder]; |
| 243 } else { |
| 244 [self.findBarView.inputField resignFirstResponder]; |
| 245 } |
| 246 |
| 247 [self updateWithMatchNumber:model.currentIndex |
| 248 matchCount:model.matches |
| 249 searchText:model.text]; |
| 250 } |
| 251 |
| 252 - (void)updateWithMatchNumber:(NSUInteger)matchNumber |
| 253 matchCount:(NSUInteger)matchCount |
| 254 searchText:(NSString*)searchText { |
| 255 NSString* text = nil; |
| 256 if (searchText.length != 0) { |
| 257 NSString* indexStr = [NSString stringWithFormat:@"%" PRIdNS, matchNumber]; |
| 258 NSString* matchesStr = [NSString stringWithFormat:@"%" PRIdNS, matchCount]; |
| 259 text = l10n_util::GetNSStringF(IDS_FIND_IN_PAGE_COUNT, |
| 260 base::SysNSStringToUTF16(indexStr), |
| 261 base::SysNSStringToUTF16(matchesStr)); |
| 262 } |
| 263 [self.findBarView updateResultsLabelWithText:text]; |
| 264 |
| 265 BOOL enabled = matchCount != 0; |
| 266 self.findBarView.nextButton.enabled = enabled; |
| 267 self.findBarView.previousButton.enabled = enabled; |
| 268 } |
| 269 |
| 270 - (void)addFindBarView:(BOOL)animate |
| 271 intoView:(UIView*)view |
| 272 withFrame:(CGRect)frame |
| 273 alignWithFrame:(CGRect)omniboxFrame |
| 274 selectText:(BOOL)selectText { |
| 275 // If already showing find bar, nothing to do. |
| 276 if (self.view) { |
| 277 return; |
| 278 } |
| 279 if ([self shouldShowCompactSearchBarInView:view]) { |
| 280 [self showIPhoneFindBarView:animate |
| 281 intoView:view |
| 282 withFrame:frame |
| 283 selectText:selectText]; |
| 284 } else { |
| 285 [self showIPadFindBarView:animate |
| 286 intoView:view |
| 287 withFrame:frame |
| 288 alignWithFrame:omniboxFrame |
| 289 selectText:selectText]; |
| 290 } |
| 291 } |
| 292 |
| 293 - (void)hideFindBarView:(BOOL)animate { |
| 294 // If view is nil, nothing to hide. |
| 295 if (!self.view) { |
| 296 return; |
| 297 } |
| 298 |
| 299 self.findBarView.inputField.selectedTextRange = nil; |
| 300 [self.delayTimer invalidate]; |
| 301 self.delayTimer = nil; |
| 302 |
| 303 if (animate) { |
| 304 [UIView animateWithDuration:kAnimationDuration |
| 305 animations:^{ |
| 306 CGRect frame = self.view.frame; |
| 307 frame.size.height = 0; |
| 308 self.view.frame = frame; |
| 309 } |
| 310 completion:^(BOOL finished) { |
| 311 [self teardownView]; |
| 312 }]; |
| 313 } else { |
| 314 [self teardownView]; |
| 315 } |
| 316 } |
| 317 |
| 318 - (void)hideKeyboard:(id)sender { |
| 319 [self.view endEditing:YES]; |
| 320 } |
| 321 |
| 322 - (UIImage*)imageWithName:(NSString*)imageName { |
| 323 NSString* name = !IsIPadIdiom() && self.isIncognito |
| 324 ? [imageName stringByAppendingString:@"_incognito"] |
| 325 : imageName; |
| 326 return [UIImage imageNamed:name]; |
| 327 } |
| 328 |
| 329 #pragma mark - Internal |
| 330 |
| 331 - (void)selectAllText { |
| 332 UITextRange* wholeTextRange = [self.findBarView.inputField |
| 333 textRangeFromPosition:self.findBarView.inputField.beginningOfDocument |
| 334 toPosition:self.findBarView.inputField.endOfDocument]; |
| 335 self.findBarView.inputField.selectedTextRange = wholeTextRange; |
| 336 } |
| 337 |
| 338 - (BOOL)shouldShowCompactSearchBarInView:(UIView*)view { |
| 339 return !IsIPadIdiom(); |
| 340 } |
| 341 |
| 342 // Animate find bar to iPad top right. |
| 343 - (void)showIPadFindBarView:(BOOL)animate |
| 344 intoView:(UIView*)parentView |
| 345 withFrame:(CGRect)targetFrame |
| 346 alignWithFrame:(CGRect)omniboxFrame |
| 347 selectText:(BOOL)selectText { |
| 348 DCHECK(IsIPadIdiom()); |
| 349 [self setupViewInView:parentView]; |
| 350 UIView* view = self.view; |
| 351 CGRect frame = view.frame; |
| 352 frame.size.width = |
| 353 MIN(CGRectGetWidth(parentView.bounds), CGRectGetWidth(frame)); |
| 354 frame.origin.y = targetFrame.origin.y; |
| 355 frame.size.height = 0; |
| 356 |
| 357 CGFloat containerWidth = parentView.bounds.size.width; |
| 358 CGFloat nibWidth = frame.size.width; |
| 359 |
| 360 // On iPad, there are three possible frames for the Search bar: |
| 361 // 1. In Regular width size class, it is short, right-aligned to the omnibox's |
| 362 // right edge. |
| 363 // 2. In Compact size class, if the short bar width is less than the omnibox, |
| 364 // stretch and align the search bar to the omnibox. |
| 365 // 3. Finally, if the short bar width is more than the omnibox, fill the |
| 366 // container view from edge to edge, ignoring the omnibox. |
| 367 if (view.cr_widthSizeClass == REGULAR) { |
| 368 if (base::i18n::IsRTL()) { |
| 369 frame.origin.x = CGRectGetMinX(omniboxFrame); |
| 370 } else { |
| 371 frame.origin.x = |
| 372 CGRectGetMinX(omniboxFrame) + CGRectGetWidth(omniboxFrame) - nibWidth; |
| 373 } |
| 374 frame.size.width = nibWidth; |
| 375 } else { |
| 376 // Compact size class. |
| 377 if (omniboxFrame.size.width > nibWidth) { |
| 378 frame.origin.x = omniboxFrame.origin.x; |
| 379 frame.size.width = omniboxFrame.size.width; |
| 380 } else { |
| 381 frame.origin.x = 0; |
| 382 frame.size.width = containerWidth; |
| 383 } |
| 384 } |
| 385 |
| 386 view.frame = frame; |
| 387 [parentView addSubview:view]; |
| 388 |
| 389 CGFloat duration = (animate) ? kAnimationDuration : 0; |
| 390 [UIView animateWithDuration:duration |
| 391 animations:^{ |
| 392 CGRect frame = view.frame; |
| 393 frame.size.height = [self findBarHeight]; |
| 394 view.frame = frame; |
| 395 } |
| 396 completion:^(BOOL finished) { |
| 397 if (selectText) |
| 398 [self selectAllText]; |
| 399 }]; |
| 400 } |
| 401 |
| 402 // Animate find bar over iPhone toolbar. |
| 403 - (void)showIPhoneFindBarView:(BOOL)animate |
| 404 intoView:(UIView*)parentView |
| 405 withFrame:(CGRect)targetFrame |
| 406 selectText:(BOOL)selectText { |
| 407 [self setupViewInView:parentView]; |
| 408 UIView* view = self.view; |
| 409 CGRect frame = view.frame; |
| 410 frame.size.width = targetFrame.size.width; |
| 411 frame.origin.y = 0 - frame.size.height; |
| 412 frame.origin.x = 0; |
| 413 view.frame = frame; |
| 414 [parentView addSubview:view]; |
| 415 |
| 416 CGFloat duration = (animate) ? kAnimationDuration : 0; |
| 417 [UIView animateWithDuration:duration |
| 418 animations:^{ |
| 419 CGRect frame = view.frame; |
| 420 frame.origin.y = 0; |
| 421 view.frame = frame; |
| 422 } |
| 423 completion:^(BOOL finished) { |
| 424 if (selectText) |
| 425 [self selectAllText]; |
| 426 }]; |
| 427 } |
| 428 |
| 429 - (void)textChanged { |
| 430 [self.view chromeExecuteCommand:self.findBarView.inputField]; |
| 431 } |
| 432 |
| 433 - (void)editingChanged:(id)sender { |
| 434 [self.delayTimer invalidate]; |
| 435 NSUInteger length = [[self searchTerm] length]; |
| 436 if (length == 0) |
| 437 return [self textChanged]; |
| 438 |
| 439 // Delay delivery of text change event. Use a longer delay when the input |
| 440 // length is short. |
| 441 NSTimeInterval delay = |
| 442 (length > kSearchDelayChars) ? kSearchShortDelay : kSearchLongDelay; |
| 443 self.delayTimer = |
| 444 [NSTimer scheduledTimerWithTimeInterval:delay |
| 445 target:self |
| 446 selector:@selector(textChanged) |
| 447 userInfo:nil |
| 448 repeats:NO]; |
| 449 } |
| 450 |
| 451 - (CGFloat)findBarHeight { |
| 452 if (IsIPadIdiom()) |
| 453 return kFindBarIPadHeight; |
| 454 return StatusBarHeight() + kFindBarIPhoneHeight; |
| 455 } |
| 456 |
| 457 #pragma mark - UITextFieldDelegate |
| 458 |
| 459 - (BOOL)textFieldShouldBeginEditing:(UITextField*)textField { |
| 460 DCHECK(textField == self.findBarView.inputField); |
| 461 [[NSNotificationCenter defaultCenter] |
| 462 postNotificationName:kFindBarTextFieldWillBecomeFirstResponderNotification |
| 463 object:self]; |
| 464 return YES; |
| 465 } |
| 466 |
| 467 - (void)textFieldDidEndEditing:(UITextField*)textField { |
| 468 DCHECK(textField == self.findBarView.inputField); |
| 469 [[NSNotificationCenter defaultCenter] |
| 470 postNotificationName:kFindBarTextFieldDidResignFirstResponderNotification |
| 471 object:self]; |
| 472 } |
| 473 |
| 474 - (BOOL)textFieldShouldReturn:(UITextField*)textField { |
| 475 DCHECK(textField == self.findBarView.inputField); |
| 476 [self.findBarView.inputField resignFirstResponder]; |
| 477 return YES; |
| 478 } |
| 479 |
| 480 @end |
OLD | NEW |