Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(104)

Unified Diff: ios/chrome/browser/ui/settings/settings_navigation_controller.mm

Issue 2587023002: Upstream Chrome on iOS source code [8/11]. (Closed)
Patch Set: Created 4 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: ios/chrome/browser/ui/settings/settings_navigation_controller.mm
diff --git a/ios/chrome/browser/ui/settings/settings_navigation_controller.mm b/ios/chrome/browser/ui/settings/settings_navigation_controller.mm
new file mode 100644
index 0000000000000000000000000000000000000000..8c4e774e626c0a8b367c59b12c4c7f09c0b8a394
--- /dev/null
+++ b/ios/chrome/browser/ui/settings/settings_navigation_controller.mm
@@ -0,0 +1,636 @@
+// Copyright 2013 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/settings/settings_navigation_controller.h"
+
+#include "base/ios/ios_util.h"
+#import "base/ios/weak_nsobject.h"
+#include "base/mac/foundation_util.h"
+#import "base/mac/scoped_nsobject.h"
+#include "components/strings/grit/components_strings.h"
+#include "ios/chrome/browser/browser_state/chrome_browser_state.h"
+#include "ios/chrome/browser/sync/sync_setup_service.h"
+#include "ios/chrome/browser/sync/sync_setup_service_factory.h"
+#import "ios/chrome/browser/ui/commands/UIKit+ChromeExecuteCommand.h"
+#import "ios/chrome/browser/ui/commands/clear_browsing_data_command.h"
+#include "ios/chrome/browser/ui/commands/ios_command_ids.h"
+#import "ios/chrome/browser/ui/commands/show_signin_command.h"
+#import "ios/chrome/browser/ui/icons/chrome_icon.h"
+#import "ios/chrome/browser/ui/keyboard/UIKeyCommand+Chrome.h"
+#import "ios/chrome/browser/ui/material_components/app_bar_presenting.h"
+#import "ios/chrome/browser/ui/material_components/utils.h"
+#import "ios/chrome/browser/ui/settings/accounts_collection_view_controller.h"
+#import "ios/chrome/browser/ui/settings/clear_browsing_data_collection_view_controller.h"
+#import "ios/chrome/browser/ui/settings/contextual_search_collection_view_controller.h"
+#import "ios/chrome/browser/ui/settings/import_data_collection_view_controller.h"
+#import "ios/chrome/browser/ui/settings/native_apps_collection_view_controller.h"
+#import "ios/chrome/browser/ui/settings/save_passwords_collection_view_controller.h"
+#import "ios/chrome/browser/ui/settings/settings_collection_view_controller.h"
+#import "ios/chrome/browser/ui/settings/settings_utils.h"
+#import "ios/chrome/browser/ui/settings/sync_encryption_passphrase_collection_view_controller.h"
+#import "ios/chrome/browser/ui/settings/sync_settings_collection_view_controller.h"
+#include "ios/chrome/browser/ui/ui_util.h"
+#import "ios/chrome/browser/ui/uikit_ui_util.h"
+#include "ios/chrome/grit/ios_strings.h"
+#include "ios/public/provider/chrome/browser/chrome_browser_provider.h"
+#import "ios/public/provider/chrome/browser/user_feedback/user_feedback_provider.h"
+#import "ios/third_party/material_components_ios/src/components/AppBar/src/MaterialAppBar.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/l10n/l10n_util_mac.h"
+
+// TODO(crbug.com/620361): Remove the entire class when iOS 9 is dropped.
+@interface SettingsAppBarContainerViewController
+ : MDCAppBarContainerViewController
+@end
+
+@implementation SettingsAppBarContainerViewController
+
+#pragma mark - Status bar
+
+- (UIViewController*)childViewControllerForStatusBarHidden {
+ if (!base::ios::IsRunningOnIOS10OrLater()) {
+ // TODO(crbug.com/620361): Remove the entire method override when iOS 9 is
+ // dropped.
+ return self.contentViewController;
+ } else {
+ return [super childViewControllerForStatusBarHidden];
+ }
+}
+
+- (UIViewController*)childViewControllerForStatusBarStyle {
+ if (!base::ios::IsRunningOnIOS10OrLater()) {
+ // TODO(crbug.com/620361): Remove the entire method override when iOS 9 is
+ // dropped.
+ return self.contentViewController;
+ } else {
+ return [super childViewControllerForStatusBarStyle];
+ }
+}
+
+@end
+
+@interface SettingsNavigationController ()<UIGestureRecognizerDelegate>
+
+// Sets up the UI. Used by both initializers.
+- (void)configureUI;
+
+// Closes the settings by calling |closeSettings| on |delegate|.
+- (void)closeSettings;
+
+// Creates an autoreleased "X" button that closes the settings when tapped.
+- (UIBarButtonItem*)closeButton;
+
+// Creates an autoreleased "CANCEL" button that closes the settings when tapped.
+- (UIBarButtonItem*)cancelButton;
+
+// Intercepts the chrome command |sender|. If |sender| is an
+// |IDC_CLOSE_SETTINGS_AND_OPEN_URL| and |delegate_| is not nil, then it
+// calls [delegate closeSettingsAndOpenUrl:sender], otherwise it forwards the
+// command up the responder chain.
+- (void)chromeExecuteCommand:(id)sender;
+
+@end
+
+@implementation SettingsNavigationController {
+ ios::ChromeBrowserState* mainBrowserState_; // weak
+ base::WeakNSProtocol<id<SettingsNavigationControllerDelegate>> delegate_;
+ // Keeps a mapping between the view controllers that are wrapped to display an
+ // app bar and the containers that wrap them.
+ base::scoped_nsobject<NSMutableDictionary> appBarContainedViewControllers_;
+}
+
+@synthesize shouldCommitSyncChangesOnDismissal =
+ shouldCommitSyncChangesOnDismissal_;
+
+#pragma mark - SettingsNavigationController methods.
+
+// clang-format off
++ (SettingsNavigationController*)newSettingsMainControllerWithMainBrowserState:
+ (ios::ChromeBrowserState*)browserState
+ currentBrowserState:
+ (ios::ChromeBrowserState*)currentBrowserState
+ delegate:
+ (id<SettingsNavigationControllerDelegate>)delegate {
+ // clang-format on
+ base::scoped_nsobject<UIViewController> controller(
+ [[SettingsCollectionViewController alloc]
+ initWithBrowserState:browserState
+ currentBrowserState:currentBrowserState]);
+ SettingsNavigationController* nc = [[SettingsNavigationController alloc]
+ initWithRootViewController:controller
+ browserState:browserState
+ delegate:delegate];
+ [controller navigationItem].rightBarButtonItem = [nc doneButton];
+ return nc;
+}
+
++ (SettingsNavigationController*)
+newAccountsController:(ios::ChromeBrowserState*)browserState
+ delegate:(id<SettingsNavigationControllerDelegate>)delegate {
+ base::scoped_nsobject<UIViewController> controller([
+ [AccountsCollectionViewController alloc] initWithBrowserState:browserState
+ closeSettingsOnAddAccount:YES]);
+ SettingsNavigationController* nc = [[SettingsNavigationController alloc]
+ initWithRootViewController:controller
+ browserState:browserState
+ delegate:delegate];
+ [controller navigationItem].leftBarButtonItem = [nc closeButton];
+ return nc;
+}
+
++ (SettingsNavigationController*)
+ newSyncController:(ios::ChromeBrowserState*)browserState
+allowSwitchSyncAccount:(BOOL)allowSwitchSyncAccount
+ delegate:(id<SettingsNavigationControllerDelegate>)delegate {
+ base::scoped_nsobject<UIViewController> controller(
+ [[SyncSettingsCollectionViewController alloc]
+ initWithBrowserState:browserState
+ allowSwitchSyncAccount:allowSwitchSyncAccount]);
+ SettingsNavigationController* nc = [[SettingsNavigationController alloc]
+ initWithRootViewController:controller
+ browserState:browserState
+ delegate:delegate];
+ [controller navigationItem].rightBarButtonItem = [nc doneButton];
+ return nc;
+}
+
++ (SettingsNavigationController*)
+newUserFeedbackController:(ios::ChromeBrowserState*)browserState
+ delegate:(id<SettingsNavigationControllerDelegate>)delegate
+ feedbackDataSource:(id<UserFeedbackDataSource>)dataSource {
+ DCHECK(ios::GetChromeBrowserProvider()
+ ->GetUserFeedbackProvider()
+ ->IsUserFeedbackEnabled());
+ base::scoped_nsobject<UIViewController> controller(
+ ios::GetChromeBrowserProvider()
+ ->GetUserFeedbackProvider()
+ ->CreateViewController(dataSource));
+ DCHECK(controller);
+ SettingsNavigationController* nc = [[SettingsNavigationController alloc]
+ initWithRootViewController:controller
+ browserState:browserState
+ delegate:delegate];
+ [controller navigationItem].rightBarButtonItem = [nc cancelButton];
+ return nc;
+}
+
++ (SettingsNavigationController*)
+newClearBrowsingDataController:(ios::ChromeBrowserState*)browserState
+ delegate:
+ (id<SettingsNavigationControllerDelegate>)delegate {
+ base::scoped_nsobject<UIViewController> controller(
+ [[ClearBrowsingDataCollectionViewController alloc]
+ initWithBrowserState:browserState]);
+ SettingsNavigationController* nc = [[SettingsNavigationController alloc]
+ initWithRootViewController:controller
+ browserState:browserState
+ delegate:delegate];
+ [controller navigationItem].rightBarButtonItem = [nc doneButton];
+ return nc;
+}
+
++ (SettingsNavigationController*)
+newContextualSearchController:(ios::ChromeBrowserState*)browserState
+ delegate:
+ (id<SettingsNavigationControllerDelegate>)delegate {
+ base::scoped_nsobject<UIViewController> controller(
+ [[ContextualSearchCollectionViewController alloc]
+ initWithBrowserState:browserState]);
+ SettingsNavigationController* nc = [[SettingsNavigationController alloc]
+ initWithRootViewController:controller
+ browserState:browserState
+ delegate:delegate];
+ [controller navigationItem].rightBarButtonItem = [nc doneButton];
+ return nc;
+}
+
++ (SettingsNavigationController*)
+newSyncEncryptionPassphraseController:(ios::ChromeBrowserState*)browserState
+ delegate:(id<SettingsNavigationControllerDelegate>)
+ delegate {
+ base::scoped_nsobject<UIViewController> controller(
+ [[SyncEncryptionPassphraseCollectionViewController alloc]
+ initWithBrowserState:browserState]);
+ SettingsNavigationController* nc = [[SettingsNavigationController alloc]
+ initWithRootViewController:controller
+ browserState:browserState
+ delegate:delegate];
+ [controller navigationItem].leftBarButtonItem = [nc closeButton];
+ return nc;
+}
+
++ (SettingsNavigationController*)
+newNativeAppsController:(ios::ChromeBrowserState*)browserState
+ delegate:(id<SettingsNavigationControllerDelegate>)delegate {
+ base::scoped_nsobject<UIViewController> controller(
+ [[NativeAppsCollectionViewController alloc]
+ initWithURLRequestContextGetter:browserState->GetRequestContext()]);
+ SettingsNavigationController* nc = [[SettingsNavigationController alloc]
+ initWithRootViewController:controller
+ browserState:browserState
+ delegate:delegate];
+ return nc;
+}
+
++ (SettingsNavigationController*)
+newSavePasswordsController:(ios::ChromeBrowserState*)browserState
+ delegate:(id<SettingsNavigationControllerDelegate>)delegate {
+ base::scoped_nsobject<UIViewController> controller(
+ [[SavePasswordsCollectionViewController alloc]
+ initWithBrowserState:browserState]);
+
+ SettingsNavigationController* nc = [[SettingsNavigationController alloc]
+ initWithRootViewController:controller
+ browserState:browserState
+ delegate:delegate];
+ [controller navigationItem].rightBarButtonItem = [nc doneButton];
+
+ // Make sure the close button is always present, as the Save Passwords screen
+ // isn't just shown from Settings.
+ [controller navigationItem].leftBarButtonItem = [nc closeButton];
+ return nc;
+}
+
++ (SettingsNavigationController*)
+newImportDataController:(ios::ChromeBrowserState*)browserState
+ delegate:(id<SettingsNavigationControllerDelegate>)delegate
+ importDataDelegate:(id<ImportDataControllerDelegate>)importDataDelegate
+ fromEmail:(NSString*)fromEmail
+ toEmail:(NSString*)toEmail
+ isSignedIn:(BOOL)isSignedIn {
+ base::scoped_nsobject<UIViewController> controller(
+ [[ImportDataCollectionViewController alloc]
+ initWithDelegate:importDataDelegate
+ fromEmail:fromEmail
+ toEmail:toEmail
+ isSignedIn:isSignedIn]);
+
+ SettingsNavigationController* nc = [[SettingsNavigationController alloc]
+ initWithRootViewController:controller
+ browserState:browserState
+ delegate:delegate];
+
+ // Make sure the close button is always present, as the Save Passwords screen
+ // isn't just shown from Settings.
+ [controller navigationItem].leftBarButtonItem = [nc closeButton];
+ return nc;
+}
+
+#pragma mark - Lifecycle
+
+- (instancetype)
+initWithRootViewController:(UIViewController*)rootViewController
+ browserState:(ios::ChromeBrowserState*)browserState
+ delegate:(id<SettingsNavigationControllerDelegate>)delegate {
+ DCHECK(browserState);
+ DCHECK(!browserState->IsOffTheRecord());
+ self = [super initWithRootViewController:rootViewController];
+ if (self) {
+ mainBrowserState_ = browserState;
+ delegate_.reset(delegate);
+ shouldCommitSyncChangesOnDismissal_ = YES;
+ [self configureUI];
+ }
+ return self;
+}
+
+- (void)settingsWillBeDismissed {
+ // Notify all controllers that settings are about to be dismissed.
+ for (UIViewController* controller in [self viewControllers]) {
+ if ([controller respondsToSelector:@selector(settingsWillBeDismissed)]) {
+ [controller performSelector:@selector(settingsWillBeDismissed)];
+ }
+ }
+
+ // Sync changes cannot be cancelled and they must always be commited when
+ // existing settings.
+ if (shouldCommitSyncChangesOnDismissal_) {
+ SyncSetupServiceFactory::GetForBrowserState([self mainBrowserState])
+ ->CommitChanges();
+ }
+
+ // Reset the delegate to prevent any queued transitions from attempting to
+ // close the settings.
+ delegate_.reset();
+}
+
+- (void)closeSettings {
+ [delegate_ closeSettings];
+}
+
+- (void)popViewControllerOrCloseSettingsAnimated:(BOOL)animated {
+ if (self.viewControllers.count > 1) {
+ // Pop the top view controller to reveal the view controller underneath.
+ [self popViewControllerAnimated:animated];
+ } else {
+ // If there is only one view controller in the navigation stack,
+ // simply close settings.
+ [self closeSettings];
+ }
+}
+
+- (void)configureUI {
+ [self setModalPresentationStyle:UIModalPresentationFormSheet];
+ [self setModalTransitionStyle:UIModalTransitionStyleCoverVertical];
+ // Since the navigation bar is hidden, the gesture to swipe to go back can
+ // become inactive. Setting the delegate to self is an MDC workaround to have
+ // it consistently work with AppBar.
+ // https://github.com/material-components/material-components-ios/issues/720
+ [self setNavigationBarHidden:YES];
+ [self.interactivePopGestureRecognizer setDelegate:self];
+}
+
+- (BOOL)hasRightDoneButton {
+ UIBarButtonItem* rightButton =
+ self.topViewController.navigationItem.rightBarButtonItem;
+ if (!rightButton)
+ return NO;
+ base::scoped_nsobject<UIBarButtonItem> doneButton([self doneButton]);
+ return [rightButton style] == [doneButton style] &&
+ [[rightButton title] compare:[doneButton title]] == NSOrderedSame;
+}
+
+- (UIBarButtonItem*)doneButton {
+ // Create a custom Done bar button item, as Material Navigation Bar does not
+ // handle a system UIBarButtonSystemItemDone item.
+ return [[[UIBarButtonItem alloc]
+ initWithTitle:l10n_util::GetNSString(IDS_IOS_NAVIGATION_BAR_DONE_BUTTON)
+ style:UIBarButtonItemStyleDone
+ target:self
+ action:@selector(closeSettings)] autorelease];
+}
+
+- (UIBarButtonItem*)closeButton {
+ UIBarButtonItem* closeButton =
+ [ChromeIcon templateBarButtonItemWithImage:[ChromeIcon closeIcon]
+ target:self
+ action:@selector(closeSettings)];
+ closeButton.accessibilityLabel = l10n_util::GetNSString(IDS_ACCNAME_CLOSE);
+ return closeButton;
+}
+
+- (UIBarButtonItem*)cancelButton {
+ // Create a custom Cancel bar button item, as Material Navigation Bar does not
+ // handle a system UIBarButtonSystemItemCancel item.
+ return [[[UIBarButtonItem alloc]
+ initWithTitle:l10n_util::GetNSString(IDS_IOS_NAVIGATION_BAR_CANCEL_BUTTON)
+ style:UIBarButtonItemStyleDone
+ target:self
+ action:@selector(closeSettings)] autorelease];
+}
+
+- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
+ return [self.topViewController supportedInterfaceOrientations];
+}
+
+- (BOOL)shouldAutorotate {
+ return [self.topViewController shouldAutorotate];
+}
+
+#pragma mark - UIGestureRecognizerDelegate
+
+- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer*)gestureRecognizer {
+ DCHECK_EQ(gestureRecognizer, self.interactivePopGestureRecognizer);
+ return self.viewControllers.count > 1;
+}
+
+#pragma mark - Accessibility
+
+- (BOOL)accessibilityPerformEscape {
+ UIViewController* poppedController = [self popViewControllerAnimated:YES];
+ if (!poppedController)
+ [self closeSettings];
+ return YES;
+}
+
+#pragma mark - UINavigationController
+
+- (void)pushViewController:(UIViewController*)viewController
+ animated:(BOOL)animated {
+ // Wrap the view controller in an MDCAppBarContainerViewController if needed.
+ [super pushViewController:[self wrappedControllerIfNeeded:viewController]
+ animated:animated];
+}
+
+- (UIViewController*)popViewControllerAnimated:(BOOL)animated {
+ UIViewController* viewController = [super popViewControllerAnimated:animated];
+ // Unwrap the view controller from its MDCAppBarContainerViewController if
+ // needed.
+ return [self unwrappedControllerIfNeeded:viewController];
+}
+
+- (NSArray*)popToViewController:(UIViewController*)viewController
+ animated:(BOOL)animated {
+ // First, check if the view controller was wrapped in an app bar container.
+ MDCAppBarContainerViewController* appBarContainer =
+ [self appBarContainerForController:viewController];
+ // Pop the view controllers.
+ NSArray* poppedViewControllers =
+ [super popToViewController:appBarContainer ?: viewController
+ animated:animated];
+ // Unwrap the popped view controllers from their
+ // MDCAppBarContainerViewController if needed.
+ NSMutableArray* viewControllers = [NSMutableArray array];
+ [poppedViewControllers
+ enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL* stop) {
+ [viewControllers
+ addObject:[self unwrappedControllerIfNeeded:viewController]];
+ }];
+ return viewControllers;
+}
+
+// Ensures that the keyboard is always dismissed during a navigation transition.
+- (BOOL)disablesAutomaticKeyboardDismissal {
+ return NO;
+}
+
+#pragma mark - UIResponder (ChromeExecuteCommand)
+
+- (void)chromeExecuteCommand:(id)sender {
+ switch ([sender tag]) {
+ case IDC_CLOSE_SETTINGS: {
+ [delegate_ closeSettings];
+ return;
+ }
+ case IDC_OPEN_URL:
+ NOTREACHED() << "You should probably use the command "
+ << "IDC_CLOSE_SETTINGS_AND_OPEN_URL instead of IDC_OPEN_URL";
+ case IDC_CLOSE_SETTINGS_AND_OPEN_URL: {
+ [delegate_ closeSettingsAndOpenUrl:sender];
+ return;
+ }
+ case IDC_CLOSE_SETTINGS_AND_OPEN_NEW_INCOGNITO_TAB: {
+ [delegate_ closeSettingsAndOpenNewIncognitoTab];
+ return;
+ }
+ case IDC_SHOW_SIGNIN_IOS:
+ // Sign-in actions can only happen on the main browser state (not on
+ // incognito browser state), which is unique. The command can just be
+ // forwarded up the responder chain.
+ break;
+ case IDC_CLEAR_BROWSING_DATA_IOS: {
+ // Check that the data for the right browser state is being cleared before
+ // forwarding it up the responder chain.
+ ios::ChromeBrowserState* commandBrowserState =
+ [base::mac::ObjCCast<ClearBrowsingDataCommand>(sender) browserState];
+
+ // Clearing browsing data for the wrong profile is a destructive action.
+ // Executing it on the wrong profile is a privacy issue. Kill the
+ // app if this ever happens.
+ CHECK_EQ(commandBrowserState, [self mainBrowserState]);
+ break;
+ }
+ case IDC_RESET_ALL_WEBVIEWS:
+ // The command to reset all webview is not related to the browser state so
+ // it can just be forwarded it up the responder chain.
+ break;
+ case IDC_SHOW_ACCOUNTS_SETTINGS: {
+ base::scoped_nsobject<UIViewController> controller(
+ [[AccountsCollectionViewController alloc]
+ initWithBrowserState:mainBrowserState_
+ closeSettingsOnAddAccount:NO]);
+ [self pushViewController:controller animated:YES];
+ return;
+ }
+ case IDC_SHOW_SYNC_SETTINGS: {
+ base::scoped_nsobject<UIViewController> controller(
+ [[SyncSettingsCollectionViewController alloc]
+ initWithBrowserState:mainBrowserState_
+ allowSwitchSyncAccount:YES]);
+ [self pushViewController:controller animated:YES];
+ return;
+ }
+ case IDC_SHOW_SYNC_PASSPHRASE_SETTINGS: {
+ base::scoped_nsobject<UIViewController> controller(
+ [[SyncEncryptionPassphraseCollectionViewController alloc]
+ initWithBrowserState:mainBrowserState_]);
+ [self pushViewController:controller animated:YES];
+ return;
+ }
+ default:
+ NOTREACHED()
+ << "Unexpected command " << [sender tag]
+ << " Settings commands must execute on the main browser state.";
+ }
+ [[self nextResponder] chromeExecuteCommand:sender];
+}
+
+#pragma mark - UIResponder
+
+- (NSArray*)keyCommands {
+ base::WeakNSObject<SettingsNavigationController> weakSelf(self);
+ return @[
+ [UIKeyCommand cr_keyCommandWithInput:UIKeyInputEscape
+ modifierFlags:Cr_UIKeyModifierNone
+ title:nil
+ action:^{
+ [weakSelf closeSettings];
+ }],
+ ];
+}
+
+#pragma mark - Profile
+
+- (ios::ChromeBrowserState*)mainBrowserState {
+ return mainBrowserState_;
+}
+
+#pragma mark - Status bar
+
+- (BOOL)modalPresentationCapturesStatusBarAppearance {
+ if (!base::ios::IsRunningOnIOS10OrLater()) {
+ // TODO(crbug.com/620361): Remove the entire method override when iOS 9 is
+ // dropped.
+ return YES;
+ } else {
+ return [super modalPresentationCapturesStatusBarAppearance];
+ }
+}
+
+- (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection {
+ [super traitCollectionDidChange:previousTraitCollection];
+ if (!base::ios::IsRunningOnIOS10OrLater()) {
+ // TODO(crbug.com/620361): Remove the entire method override when iOS 9 is
+ // dropped.
+ [self setNeedsStatusBarAppearanceUpdate];
+ }
+}
+
+#pragma mark - AppBar Containment
+
+// If viewController doesn't implement the AppBarPresenting protocol, it is
+// wrapped in an MDCAppBarContainerViewController, which is returned. Otherwise,
+// viewController is returned.
+- (UIViewController*)wrappedControllerIfNeeded:(UIViewController*)controller {
+ // If the controller can't be presented with an app bar, it needs to be
+ // wrapped in an MDCAppBarContainerViewController.
+ if (![controller conformsToProtocol:@protocol(AppBarPresenting)]) {
+ MDCAppBarContainerViewController* appBarContainer =
+ [[[SettingsAppBarContainerViewController alloc]
+ initWithContentViewController:controller] autorelease];
+
+ // Configure the style.
+ ConfigureAppBarWithCardStyle(appBarContainer.appBar);
+
+ // Adjust the frame of the contained view controller's view to be below the
+ // app bar.
+ CGRect contentFrame = controller.view.frame;
+ CGSize headerSize = [appBarContainer.appBar.headerViewController.headerView
+ sizeThatFits:contentFrame.size];
+ contentFrame = UIEdgeInsetsInsetRect(
+ contentFrame, UIEdgeInsetsMake(headerSize.height, 0, 0, 0));
+ controller.view.frame = contentFrame;
+
+ // Register the app bar container and return it.
+ [self registerAppBarContainer:appBarContainer];
+ return appBarContainer;
+ } else {
+ return controller;
+ }
+}
+
+// If controller is an MDCAppBarContainerViewController, it returns its content
+// view controller. Otherwise, it returns viewController.
+- (UIViewController*)unwrappedControllerIfNeeded:(UIViewController*)controller {
+ MDCAppBarContainerViewController* potentialAppBarController =
+ base::mac::ObjCCast<MDCAppBarContainerViewController>(controller);
+ if (potentialAppBarController) {
+ // Unregister the app bar container and return it.
+ [self unregisterAppBarContainer:potentialAppBarController];
+ return [potentialAppBarController contentViewController];
+ } else {
+ return controller;
+ }
+}
+
+// Adds an app bar container in a dictionary mapping its content view
+// controller's pointer to itself.
+- (void)registerAppBarContainer:(MDCAppBarContainerViewController*)container {
+ if (!appBarContainedViewControllers_) {
+ appBarContainedViewControllers_.reset([[NSMutableDictionary alloc] init]);
+ }
+ NSValue* key = [self keyForController:[container contentViewController]];
+ [appBarContainedViewControllers_ setObject:container forKey:key];
+}
+
+// Removes the app bar container entry from the aforementioned dictionary.
+- (void)unregisterAppBarContainer:(MDCAppBarContainerViewController*)container {
+ NSValue* key = [self keyForController:[container contentViewController]];
+ [appBarContainedViewControllers_ removeObjectForKey:key];
+}
+
+// Returns the app bar container containing |controller| if it is contained.
+// Otherwise, returns nil.
+- (MDCAppBarContainerViewController*)appBarContainerForController:
+ (UIViewController*)controller {
+ NSValue* key = [self keyForController:controller];
+ return [appBarContainedViewControllers_ objectForKey:key];
+}
+
+// Returns the dictionary key to use when dealing with |controller|.
+- (NSValue*)keyForController:(UIViewController*)controller {
+ return [NSValue valueWithPointer:controller];
+}
+
+@end

Powered by Google App Engine
This is Rietveld 408576698