Commit 2e47cfb5 authored by Rohit Rao's avatar Rohit Rao Committed by Commit Bot

[ios] Updates the internal structure of MainPresentingViewController.

The TabSwitcher is now displayed as a contained child, while BVCs are presented
inside a container VC.  This structure allows for BVCs to be presented even
before a TabSwitcher is created, and it allows incognito/non-incognito BVCs to
be swapped without having to dismiss and re-present view controllers.

More details can be found in the design doc at
https://docs.google.com/document/d/11kI9oH8NUl0oJU_ZRHt6amblGyzPSwhvB_7JWsKayGY/view

BUG=768563

Change-Id: I971cf80c1c2714d801f9885369ce1d0fb95afb28
Reviewed-on: https://chromium-review.googlesource.com/723940Reviewed-by: default avataredchin <edchin@chromium.org>
Reviewed-by: default avatarMark Cogan <marq@chromium.org>
Commit-Queue: Rohit Rao (ping after 24h) <rohitrao@chromium.org>
Cr-Commit-Position: refs/heads/master@{#509919}
parent 49ac0394
...@@ -10,58 +10,138 @@ ...@@ -10,58 +10,138 @@
#error "This file requires ARC support." #error "This file requires ARC support."
#endif #endif
@interface BVCContainerViewController : UIViewController
@property(nonatomic, weak) UIViewController* currentBVC;
@end
@implementation BVCContainerViewController
- (UIViewController*)currentBVC {
return [self.childViewControllers firstObject];
}
- (void)setCurrentBVC:(UIViewController*)bvc {
DCHECK(bvc);
if (self.currentBVC == bvc) {
return;
}
// Remove the current bvc, if any.
if (self.currentBVC) {
[self.currentBVC willMoveToParentViewController:nil];
[self.currentBVC.view removeFromSuperview];
[self.currentBVC removeFromParentViewController];
}
DCHECK_EQ(nil, self.currentBVC);
DCHECK_EQ(0U, self.view.subviews.count);
// Add the new active view controller.
[self addChildViewController:bvc];
bvc.view.frame = self.view.bounds;
[self.view addSubview:bvc.view];
[bvc didMoveToParentViewController:self];
// Let the system know that the child has changed so appearance updates can
// be made.
[self setNeedsStatusBarAppearanceUpdate];
DCHECK(self.currentBVC == bvc);
}
- (UIViewController*)childViewControllerForStatusBarHidden {
return self.currentBVC;
}
- (UIViewController*)childViewControllerForStatusBarStyle {
return self.currentBVC;
}
- (BOOL)shouldAutorotate {
return self.currentBVC ? [self.currentBVC shouldAutorotate]
: [super shouldAutorotate];
}
@end
@interface MainPresentingViewController ()
@property(nonatomic, weak) UIViewController<TabSwitcher>* tabSwitcher;
@property(nonatomic, strong) BVCContainerViewController* bvcContainer;
@end
@implementation MainPresentingViewController @implementation MainPresentingViewController
@synthesize tabSwitcher = _tabSwitcher;
@synthesize bvcContainer = _bvcContainer;
- (UIViewController*)activeViewController { - (UIViewController*)activeViewController {
return self.presentedViewController; if (self.bvcContainer) {
DCHECK_EQ(self.bvcContainer, self.presentedViewController);
DCHECK(self.bvcContainer.currentBVC);
return self.bvcContainer.currentBVC;
} else if (self.tabSwitcher) {
DCHECK_EQ(self.tabSwitcher, [self.childViewControllers firstObject]);
return self.tabSwitcher;
}
return nil;
} }
- (void)showTabSwitcher:(UIViewController<TabSwitcher>*)tabSwitcher - (void)showTabSwitcher:(UIViewController<TabSwitcher>*)tabSwitcher
completion:(ProceduralBlock)completion { completion:(ProceduralBlock)completion {
[self setActiveViewController:tabSwitcher completion:completion]; DCHECK(tabSwitcher);
// Don't remove and re-add the tabSwitcher if it hasn't changed.
if (self.tabSwitcher != tabSwitcher) {
// Remove any existing tab switchers first.
if (self.tabSwitcher) {
[self.tabSwitcher willMoveToParentViewController:nil];
[self.tabSwitcher.view removeFromSuperview];
[self.tabSwitcher removeFromParentViewController];
}
// Add the new tab switcher as a child VC.
[self addChildViewController:tabSwitcher];
tabSwitcher.view.frame = self.view.bounds;
[self.view addSubview:tabSwitcher.view];
[tabSwitcher didMoveToParentViewController:self];
self.tabSwitcher = tabSwitcher;
}
// If a BVC is currently being presented, dismiss it. This will trigger any
// necessary animations.
if (self.bvcContainer) {
self.bvcContainer = nil;
[super dismissViewControllerAnimated:YES completion:completion];
} else {
if (completion) {
completion();
}
}
} }
- (void)showTabViewController:(UIViewController*)viewController - (void)showTabViewController:(UIViewController*)viewController
completion:(ProceduralBlock)completion { completion:(ProceduralBlock)completion {
[self setActiveViewController:viewController completion:completion]; DCHECK(viewController);
}
// Presents the given view controller, first dismissing any other view // If another BVC is already being presented, swap this one into the
// controllers that are currently presented. Runs the given |completion| block // container.
// after the view controller is visible. if (self.bvcContainer) {
- (void)setActiveViewController:(UIViewController*)activeViewController self.bvcContainer.currentBVC = viewController;
completion:(void (^)())completion {
DCHECK(activeViewController);
if (self.activeViewController == activeViewController) {
if (completion) { if (completion) {
completion(); completion();
} }
return; return;
} }
// TODO(crbug.com/546189): DCHECK here that there isn't a modal view self.bvcContainer = [[BVCContainerViewController alloc] init];
// controller showing once the known violations of that are fixed. self.bvcContainer.currentBVC = viewController;
[super presentViewController:self.bvcContainer
if (self.activeViewController) { animated:(self.tabSwitcher != nil)
// This call must be to super, as the override of completion:completion];
// dismissViewControllerAnimated:completion: below tries to dismiss using
// self.activeViewController, but this call explicitly needs to present
// using the MainPresentingViewController itself.
[super dismissViewControllerAnimated:NO completion:nil];
}
// This call must be to super, as the override of
// presentViewController:animated:completion: below tries to present using
// self.activeViewController, but this call explicitly needs to present
// using the MainPresentingViewController itself.
[super presentViewController:activeViewController
animated:NO
completion:^{
DCHECK(self.activeViewController == activeViewController);
if (completion) {
completion();
}
}];
} }
#pragma mark - UIViewController methods #pragma mark - UIViewController methods
......
...@@ -18,52 +18,144 @@ ...@@ -18,52 +18,144 @@
namespace { namespace {
using MainPresentingViewControllerTest = MainViewControllerTest; class MainPresentingViewControllerTest : public MainViewControllerTest {
public:
MainPresentingViewControllerTest() {
main_view_controller_ = [[MainPresentingViewController alloc] init];
SetRootViewController(main_view_controller_);
CGRect windowRect = CGRectMake(0, 0, 200, 200);
main_view_controller_.view.frame = windowRect;
tab_switcher_ = CreateTestTabSwitcher();
tab_switcher_.view.frame = CGRectMake(0, 0, 10, 10);
normal_tab_view_controller_ = [[UIViewController alloc] init];
normal_tab_view_controller_.view.frame = CGRectMake(20, 20, 10, 10);
incognito_tab_view_controller_ = [[UIViewController alloc] init];
incognito_tab_view_controller_.view.frame = CGRectMake(40, 40, 10, 10);
}
~MainPresentingViewControllerTest() override {}
protected:
// The MainPresentingViewController that is under test. The test fixture sets
// this VC as the root VC for the window.
MainPresentingViewController* main_view_controller_;
// The following view controllers are created by the test fixture and are
// available for use in tests.
UIViewController<TabSwitcher>* tab_switcher_;
UIViewController* normal_tab_view_controller_;
UIViewController* incognito_tab_view_controller_;
};
// Tests that there is no activeViewController for a newly constructed instance.
TEST_F(MainPresentingViewControllerTest, NoActiveViewController) {
EXPECT_EQ(nil, main_view_controller_.activeViewController);
}
// Tests that it is possible to set a TabViewController without first setting a
// TabSwitcher.
TEST_F(MainPresentingViewControllerTest, TabViewControllerBeforeTabSwitcher) {
[main_view_controller_ showTabViewController:normal_tab_view_controller_
completion:nil];
EXPECT_EQ(normal_tab_view_controller_,
main_view_controller_.activeViewController);
// Now setting a TabSwitcher will make the switcher active.
[main_view_controller_ showTabSwitcher:tab_switcher_ completion:nil];
EXPECT_EQ(tab_switcher_, main_view_controller_.activeViewController);
}
// Tests that it is possible to set a TabViewController after setting a
// TabSwitcher.
TEST_F(MainPresentingViewControllerTest, TabViewControllerAfterTabSwitcher) {
[main_view_controller_ showTabSwitcher:tab_switcher_ completion:nil];
EXPECT_EQ(tab_switcher_, main_view_controller_.activeViewController);
[main_view_controller_ showTabViewController:normal_tab_view_controller_
completion:nil];
EXPECT_EQ(normal_tab_view_controller_,
main_view_controller_.activeViewController);
// Showing the TabSwitcher again will make it active.
[main_view_controller_ showTabSwitcher:tab_switcher_ completion:nil];
EXPECT_EQ(tab_switcher_, main_view_controller_.activeViewController);
}
// Tests swapping between two TabViewControllers.
TEST_F(MainPresentingViewControllerTest, SwapTabViewControllers) {
[main_view_controller_ showTabViewController:normal_tab_view_controller_
completion:nil];
EXPECT_EQ(normal_tab_view_controller_,
main_view_controller_.activeViewController);
[main_view_controller_ showTabViewController:incognito_tab_view_controller_
completion:nil];
EXPECT_EQ(incognito_tab_view_controller_,
main_view_controller_.activeViewController);
}
// Tests calling showTabSwitcher twice in a row with the same VC.
TEST_F(MainPresentingViewControllerTest, ShowTabSwitcherTwice) {
[main_view_controller_ showTabSwitcher:tab_switcher_ completion:nil];
EXPECT_EQ(tab_switcher_, main_view_controller_.activeViewController);
[main_view_controller_ showTabSwitcher:tab_switcher_ completion:nil];
EXPECT_EQ(tab_switcher_, main_view_controller_.activeViewController);
}
// Tests calling showTabViewController twice in a row with the same VC.
TEST_F(MainPresentingViewControllerTest, ShowTabViewControllerTwice) {
[main_view_controller_ showTabViewController:normal_tab_view_controller_
completion:nil];
EXPECT_EQ(normal_tab_view_controller_,
main_view_controller_.activeViewController);
[main_view_controller_ showTabViewController:normal_tab_view_controller_
completion:nil];
EXPECT_EQ(normal_tab_view_controller_,
main_view_controller_.activeViewController);
}
// Tests that setting the active view controller work and that completion // Tests that setting the active view controller work and that completion
// handlers are called properly after the new view controller is made active. // handlers are called properly after the new view controller is made active.
TEST_F(MainPresentingViewControllerTest, SetActiveVCAndCompletionHandlers) { TEST_F(MainPresentingViewControllerTest, CompletionHandlers) {
MainPresentingViewController* main_view_controller = // Tests that the completion handler is called when showing the switcher.
[[MainPresentingViewController alloc] init];
SetRootViewController(main_view_controller);
CGRect windowRect = CGRectMake(0, 0, 200, 200);
[main_view_controller view].frame = windowRect;
UIViewController<TabSwitcher>* child_view_controller_1 =
CreateTestTabSwitcher();
[child_view_controller_1 view].frame = CGRectMake(0, 0, 10, 10);
UIViewController<TabSwitcher>* child_view_controller_2 =
CreateTestTabSwitcher();
[child_view_controller_2 view].frame = CGRectMake(20, 20, 10, 10);
// Tests that the completion handler is called when there is no preexisting
// active view controller.
__block BOOL completion_handler_was_called = false; __block BOOL completion_handler_was_called = false;
[main_view_controller showTabSwitcher:child_view_controller_1 [main_view_controller_ showTabSwitcher:tab_switcher_
completion:^{ completion:^{
completion_handler_was_called = YES; completion_handler_was_called = YES;
}]; }];
base::test::ios::WaitUntilCondition(^bool() {
return completion_handler_was_called;
});
ASSERT_TRUE(completion_handler_was_called);
// Tests that the completion handler is called when showing a tab view
// controller.
completion_handler_was_called = NO;
[main_view_controller_ showTabViewController:normal_tab_view_controller_
completion:^{
completion_handler_was_called = YES;
}];
base::test::ios::WaitUntilCondition(^bool() { base::test::ios::WaitUntilCondition(^bool() {
return completion_handler_was_called; return completion_handler_was_called;
}); });
ASSERT_TRUE(completion_handler_was_called); ASSERT_TRUE(completion_handler_was_called);
EXPECT_EQ(main_view_controller.presentedViewController,
child_view_controller_1);
// Tests that the completion handler is called when replacing an existing // Tests that the completion handler is called when replacing an existing tab
// active view controller. // view controller.
completion_handler_was_called = NO; completion_handler_was_called = NO;
[main_view_controller showTabViewController:child_view_controller_2 [main_view_controller_ showTabViewController:incognito_tab_view_controller_
completion:^{ completion:^{
completion_handler_was_called = YES; completion_handler_was_called = YES;
}]; }];
base::test::ios::WaitUntilCondition(^bool() { base::test::ios::WaitUntilCondition(^bool() {
return completion_handler_was_called; return completion_handler_was_called;
}); });
ASSERT_TRUE(completion_handler_was_called); ASSERT_TRUE(completion_handler_was_called);
EXPECT_EQ(main_view_controller.presentedViewController,
child_view_controller_2);
} }
} // namespace } // namespace
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment