forked from chromium/chromium
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
SelectorCoordinator for presenting a picker view from an infobar
Add coordinator for presenting a picker view from an infobar, as needed in update passwords where there are multiple accounts, or in the translate infobar for language selection. Starting the coordinator animates the picker view in from the bottom edge of the baseViewController's view, and stopping animates it out. BUG=622244 Review-Url: https://codereview.chromium.org/2105253004 Cr-Commit-Position: refs/heads/master@{#403780}
- Loading branch information
jyquinn
authored and
Commit bot
committed
Jul 5, 2016
1 parent
0e19eef
commit b7a60b6
Showing
11 changed files
with
507 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
// Copyright 2016 The Chromium Authors. All rights reserved. | ||
// Use of this source code is governed by a BSD-style license that can be | ||
// found in the LICENSE file. | ||
|
||
#ifndef IOS_CHROME_BROWSER_UI_ELEMENTS_SELECTOR_COORDINATOR_H_ | ||
#define IOS_CHROME_BROWSER_UI_ELEMENTS_SELECTOR_COORDINATOR_H_ | ||
|
||
#import "ios/chrome/browser/chrome_coordinator.h" | ||
|
||
@class SelectorCoordinator; | ||
|
||
// Delegate protocol for SelectorCoordinator | ||
@protocol SelectorCoordinatorDelegate<NSObject> | ||
// Called when selection UI has completed. | ||
- (void)selectorCoordinator:(nonnull SelectorCoordinator*)coordinator | ||
didCompleteWithSelection:(nonnull NSString*)selection; | ||
@end | ||
|
||
// Coordinator for displaying UI to allow the user to pick among options. | ||
@interface SelectorCoordinator : ChromeCoordinator | ||
|
||
// Options to present to the user. | ||
@property(nonatomic, nullable, assign) NSOrderedSet<NSString*>* options; | ||
|
||
// The default option. Starts out selected, and is set as the selected option | ||
// if the user performs a cancel action. | ||
@property(nonatomic, nullable, copy) NSString* defaultOption; | ||
|
||
@property(nonatomic, nullable, assign) id<SelectorCoordinatorDelegate> delegate; | ||
|
||
@end | ||
|
||
#endif // IOS_CHROME_BROWSER_UI_ELEMENTS_SELECTOR_COORDINATOR_H_ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
// Copyright 2016 The Chromium Authors. All rights reserved. | ||
// Use of this source code is governed by a BSD-style license that can be | ||
// found in the LICENSE file. | ||
|
||
#import "ios/chrome/browser/ui/elements/selector_coordinator.h" | ||
|
||
#import "base/mac/objc_property_releaser.h" | ||
#import "ios/chrome/browser/ui/elements/selector_picker_view_controller.h" | ||
#import "ios/chrome/browser/ui/elements/selector_view_controller_delegate.h" | ||
|
||
@interface SelectorCoordinator ()<SelectorViewControllerDelegate> { | ||
base::mac::ObjCPropertyReleaser _propertyReleaser_SelectorCoordinator; | ||
__unsafe_unretained id<SelectorCoordinatorDelegate> _delegate; | ||
__unsafe_unretained NSOrderedSet<NSString*>* _options; | ||
} | ||
|
||
// Redeclaration of infoBarPickerController as readwrite. | ||
@property(nonatomic, nullable, retain) | ||
SelectorPickerViewController* selectorPickerViewController; | ||
|
||
@end | ||
|
||
@implementation SelectorCoordinator | ||
|
||
@synthesize options = _options; | ||
@synthesize defaultOption = _defaultOption; | ||
@synthesize delegate = _delegate; | ||
@synthesize selectorPickerViewController = _selectorPickerViewController; | ||
|
||
- (nullable instancetype)initWithBaseViewController: | ||
(nullable UIViewController*)viewController { | ||
self = [super initWithBaseViewController:viewController]; | ||
if (self) { | ||
_propertyReleaser_SelectorCoordinator.Init(self, | ||
[SelectorCoordinator class]); | ||
} | ||
return self; | ||
} | ||
|
||
- (void)start { | ||
self.selectorPickerViewController = | ||
[[SelectorPickerViewController alloc] initWithOptions:self.options | ||
default:self.defaultOption]; | ||
self.selectorPickerViewController.delegate = self; | ||
// TODO(crbug.com/622244): Display via custom UIPresentionController to | ||
// show as a bottom sheet. | ||
self.selectorPickerViewController.modalTransitionStyle = | ||
UIModalTransitionStyleCoverVertical; | ||
self.selectorPickerViewController.modalPresentationStyle = | ||
UIModalPresentationFormSheet; | ||
[self.baseViewController | ||
presentViewController:self.selectorPickerViewController | ||
animated:YES | ||
completion:nil]; | ||
} | ||
|
||
- (void)stop { | ||
[self.selectorPickerViewController dismissViewControllerAnimated:YES | ||
completion:nil]; | ||
} | ||
|
||
#pragma mark SelectorViewControllerDelegate | ||
|
||
- (void)selectorViewController:(UIViewController*)viewController | ||
didSelectOption:(NSString*)option { | ||
[self.delegate selectorCoordinator:self didCompleteWithSelection:option]; | ||
[self stop]; | ||
} | ||
|
||
@end |
70 changes: 70 additions & 0 deletions
70
ios/chrome/browser/ui/elements/selector_coordinator_unittest.mm
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
// Copyright 2016 The Chromium Authors. All rights reserved. | ||
// Use of this source code is governed by a BSD-style license that can be | ||
// found in the LICENSE file. | ||
|
||
#import "ios/chrome/browser/ui/elements/selector_coordinator.h" | ||
|
||
#include "base/test/ios/wait_util.h" | ||
#import "ios/chrome/browser/ui/elements/selector_picker_view_controller.h" | ||
#import "ios/chrome/browser/ui/elements/selector_view_controller_delegate.h" | ||
#include "testing/gtest/include/gtest/gtest.h" | ||
#import "testing/gtest_mac.h" | ||
#include "third_party/ocmock/OCMock/OCMock.h" | ||
#include "third_party/ocmock/gtest_support.h" | ||
|
||
@interface SelectorCoordinator ()<SelectorViewControllerDelegate> | ||
// The view controller for the picker view the coordinator presents. Exposed for | ||
// testing. | ||
@property SelectorPickerViewController* selectorPickerViewController; | ||
@end | ||
|
||
// Tests that invoking start on the coordinator presents the selector view, and | ||
// that invoking stop dismisses the view and invokes the delegate. | ||
TEST(SelectorCoordinatorTest, StartAndStop) { | ||
UIWindow* keyWindow = [[UIApplication sharedApplication] keyWindow]; | ||
UIViewController* rootViewController = keyWindow.rootViewController; | ||
SelectorCoordinator* coordinator = [[[SelectorCoordinator alloc] | ||
initWithBaseViewController:rootViewController] autorelease]; | ||
|
||
void (^testSteps)(void) = ^{ | ||
[coordinator start]; | ||
EXPECT_NSEQ(coordinator.selectorPickerViewController, | ||
rootViewController.presentedViewController); | ||
|
||
[coordinator stop]; | ||
base::test::ios::WaitUntilCondition(^{ | ||
return !rootViewController.presentedViewController; | ||
}); | ||
}; | ||
// Ensure any other presented controllers are dismissed before starting the | ||
// coordinator. | ||
[rootViewController dismissViewControllerAnimated:NO completion:testSteps]; | ||
} | ||
|
||
// Tests that calling the view controller delegate method invokes the | ||
// SelectorCoordinatorDelegate method and stops the coordinator. | ||
TEST(SelectorCoordinatorTest, Delegate) { | ||
UIWindow* keyWindow = [[UIApplication sharedApplication] keyWindow]; | ||
UIViewController* rootViewController = keyWindow.rootViewController; | ||
SelectorCoordinator* coordinator = [[[SelectorCoordinator alloc] | ||
initWithBaseViewController:rootViewController] autorelease]; | ||
id delegate = | ||
[OCMockObject mockForProtocol:@protocol(SelectorCoordinatorDelegate)]; | ||
coordinator.delegate = delegate; | ||
|
||
void (^testSteps)(void) = ^{ | ||
[coordinator start]; | ||
NSString* testOption = @"Test Option"; | ||
[[delegate expect] selectorCoordinator:coordinator | ||
didCompleteWithSelection:testOption]; | ||
[coordinator selectorViewController:coordinator.selectorPickerViewController | ||
didSelectOption:testOption]; | ||
base::test::ios::WaitUntilCondition(^{ | ||
return !rootViewController.presentedViewController; | ||
}); | ||
}; | ||
// Ensure any other presented controllers are dismissed before starting the | ||
// coordinator. | ||
[rootViewController dismissViewControllerAnimated:NO completion:testSteps]; | ||
EXPECT_OCMOCK_VERIFY(delegate); | ||
} |
30 changes: 30 additions & 0 deletions
30
ios/chrome/browser/ui/elements/selector_picker_view_controller.h
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
// Copyright 2016 The Chromium Authors. All rights reserved. | ||
// Use of this source code is governed by a BSD-style license that can be | ||
// found in the LICENSE file. | ||
|
||
#ifndef IOS_CHROME_BROWSER_UI_ELEMENTS_SELECTOR_PICKER_VIEW_CONTROLLER_H_ | ||
#define IOS_CHROME_BROWSER_UI_ELEMENTS_SELECTOR_PICKER_VIEW_CONTROLLER_H_ | ||
|
||
#import <UIKit/UIKit.h> | ||
|
||
@protocol SelectorViewControllerDelegate; | ||
|
||
// View controller for displaying a UIPickerView topped by a UINavigationBar | ||
// displaying "Done" on the right and "Cancel" on the left. | ||
@interface SelectorPickerViewController : UIViewController | ||
|
||
// Initializer for view controller that will display |options|. |defaultOptions| | ||
// will be selected initially, and pressing cancel will set |defaultOption| as | ||
// the selected option. | ||
- (instancetype)initWithOptions:(NSOrderedSet<NSString*>*)options | ||
default:(NSString*)defaultOption | ||
NS_DESIGNATED_INITIALIZER; | ||
- (instancetype)initWithNibName:(NSString*)nibNameOrNil | ||
bundle:(NSBundle*)nibBundleOrNil NS_UNAVAILABLE; | ||
- (instancetype)initWithCoder:(NSCoder*)aDecoder NS_UNAVAILABLE; | ||
|
||
@property(nonatomic, weak) id<SelectorViewControllerDelegate> delegate; | ||
|
||
@end | ||
|
||
#endif // IOS_CHROME_BROWSER_UI_ELEMENTS_SELECTOR_PICKER_VIEW_CONTROLLER_H_ |
157 changes: 157 additions & 0 deletions
157
ios/chrome/browser/ui/elements/selector_picker_view_controller.mm
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
// Copyright 2016 The Chromium Authors. All rights reserved. | ||
// Use of this source code is governed by a BSD-style license that can be | ||
// found in the LICENSE file. | ||
|
||
#import "ios/chrome/browser/ui/elements/selector_picker_view_controller.h" | ||
|
||
#import "ios/chrome/browser/ui/elements/selector_view_controller_delegate.h" | ||
#include "base/logging.h" | ||
#import "base/mac/objc_property_releaser.h" | ||
|
||
namespace { | ||
// Font size of text in the picker view. | ||
CGFloat kUIPickerFontSize = 26; | ||
} // namespace | ||
|
||
@interface SelectorPickerViewController ()<UIPickerViewDelegate, | ||
UIPickerViewDataSource> { | ||
base::mac::ObjCPropertyReleaser | ||
_propertyReleaser_SelectorPickerViewController; | ||
__unsafe_unretained id<SelectorViewControllerDelegate> _delegate; | ||
} | ||
// Options to display. | ||
@property(nonatomic, copy) NSOrderedSet<NSString*>* options; | ||
// The default option. | ||
@property(nonatomic, copy) NSString* defaultOption; | ||
// The displayed UINavigationBar. Exposed for testing. | ||
@property(nonatomic, retain) UINavigationBar* navigationBar; | ||
// The displayed UIPickerView. Exposed for testing. | ||
@property(nonatomic, retain) UIPickerView* pickerView; | ||
// Action for the "Done" button. | ||
- (void)doneButtonPressed; | ||
// Action for the "Cancel" button. | ||
- (void)cancelButtonPressed; | ||
@end | ||
|
||
@implementation SelectorPickerViewController | ||
|
||
@synthesize pickerView = _pickerView; | ||
@synthesize navigationBar = _navigationBar; | ||
|
||
@synthesize options = _options; | ||
@synthesize defaultOption = _defaultOption; | ||
|
||
- (instancetype)initWithOptions:(NSOrderedSet<NSString*>*)options | ||
default:(NSString*)defaultOption { | ||
self = [super initWithNibName:nil bundle:nil]; | ||
if (self) { | ||
_propertyReleaser_SelectorPickerViewController.Init( | ||
self, [SelectorPickerViewController class]); | ||
_options = [options copy]; | ||
_defaultOption = [defaultOption copy]; | ||
} | ||
return self; | ||
} | ||
|
||
- (instancetype)initWithNibName:(NSString*)nibName bundle:(NSBundle*)nibBundle { | ||
NOTREACHED(); | ||
return nil; | ||
} | ||
|
||
- (instancetype)initWithCoder:(NSCoder*)aDecoder { | ||
NOTREACHED(); | ||
return nil; | ||
} | ||
|
||
- (void)loadView { | ||
self.pickerView = [[UIPickerView alloc] initWithFrame:CGRectZero]; | ||
self.navigationBar = [[UINavigationBar alloc] initWithFrame:CGRectZero]; | ||
self.pickerView.translatesAutoresizingMaskIntoConstraints = NO; | ||
self.navigationBar.translatesAutoresizingMaskIntoConstraints = NO; | ||
UIStackView* stackView = [[[UIStackView alloc] | ||
initWithArrangedSubviews:@[ self.navigationBar, self.pickerView ]] | ||
autorelease]; | ||
stackView.axis = UILayoutConstraintAxisVertical; | ||
stackView.translatesAutoresizingMaskIntoConstraints = NO; | ||
self.view = stackView; | ||
} | ||
|
||
- (void)viewDidLoad { | ||
self.view.backgroundColor = [UIColor whiteColor]; | ||
|
||
self.pickerView.showsSelectionIndicator = YES; | ||
self.pickerView.backgroundColor = [UIColor whiteColor]; | ||
self.pickerView.delegate = self; | ||
self.pickerView.dataSource = self; | ||
NSInteger initialIndex = [self.options indexOfObject:self.defaultOption]; | ||
if (initialIndex != NSNotFound) { | ||
[self.pickerView selectRow:initialIndex inComponent:0 animated:NO]; | ||
} | ||
|
||
UINavigationItem* navigationItem = | ||
[[[UINavigationItem alloc] initWithTitle:@""] autorelease]; | ||
UIBarButtonItem* doneButton = [[[UIBarButtonItem alloc] | ||
initWithBarButtonSystemItem:UIBarButtonSystemItemDone | ||
target:nil | ||
action:@selector(doneButtonPressed)] autorelease]; | ||
UIBarButtonItem* cancelButton = [[[UIBarButtonItem alloc] | ||
initWithBarButtonSystemItem:UIBarButtonSystemItemCancel | ||
target:nil | ||
action:@selector(cancelButtonPressed)] autorelease]; | ||
[navigationItem setRightBarButtonItem:doneButton]; | ||
[navigationItem setLeftBarButtonItem:cancelButton]; | ||
[navigationItem setHidesBackButton:YES]; | ||
[self.navigationBar pushNavigationItem:navigationItem animated:NO]; | ||
} | ||
|
||
- (id<SelectorViewControllerDelegate>)delegate { | ||
return _delegate; | ||
} | ||
|
||
- (void)setDelegate:(id<SelectorViewControllerDelegate>)delegate { | ||
_delegate = delegate; | ||
} | ||
|
||
#pragma mark UIPickerViewDataSource | ||
|
||
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView*)pickerView { | ||
return 1; | ||
} | ||
|
||
- (NSInteger)pickerView:(UIPickerView*)pickerView | ||
numberOfRowsInComponent:(NSInteger)component { | ||
return [self.options count]; | ||
} | ||
|
||
#pragma mark UIPickerViewDelegate | ||
|
||
- (UIView*)pickerView:(UIPickerView*)pickerView | ||
viewForRow:(NSInteger)row | ||
forComponent:(NSInteger)component | ||
reusingView:(UIView*)view { | ||
DCHECK_EQ(0, component); | ||
UILabel* label = [view isKindOfClass:[UILabel class]] | ||
? (UILabel*)view | ||
: [[[UILabel alloc] init] autorelease]; | ||
NSString* text = self.options[row]; | ||
label.text = text; | ||
label.textAlignment = NSTextAlignmentCenter; | ||
label.font = [text isEqualToString:self.defaultOption] | ||
? [UIFont boldSystemFontOfSize:kUIPickerFontSize] | ||
: [UIFont systemFontOfSize:kUIPickerFontSize]; | ||
return label; | ||
} | ||
|
||
#pragma mark Private methods | ||
|
||
- (void)doneButtonPressed { | ||
NSInteger selectedIndex = [self.pickerView selectedRowInComponent:0]; | ||
[_delegate selectorViewController:self | ||
didSelectOption:self.options[selectedIndex]]; | ||
} | ||
|
||
- (void)cancelButtonPressed { | ||
[_delegate selectorViewController:self didSelectOption:self.defaultOption]; | ||
} | ||
|
||
@end |
Oops, something went wrong.