Index: ios/chrome/browser/ui/side_swipe/side_swipe_controller.mm |
diff --git a/ios/chrome/browser/ui/side_swipe/side_swipe_controller.mm b/ios/chrome/browser/ui/side_swipe/side_swipe_controller.mm |
new file mode 100644 |
index 0000000000000000000000000000000000000000..3d4a3fba5cfd17e6bedd4796003f96a138bd2b37 |
--- /dev/null |
+++ b/ios/chrome/browser/ui/side_swipe/side_swipe_controller.mm |
@@ -0,0 +1,538 @@ |
+// Copyright 2015 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/side_swipe/side_swipe_controller.h" |
+ |
+#include <memory> |
+ |
+#include "components/reading_list/core/reading_list_switches.h" |
+#import "components/reading_list/ios/reading_list_model.h" |
+#import "ios/chrome/browser/browser_state/chrome_browser_state.h" |
+#import "ios/chrome/browser/infobars/infobar_container_view.h" |
+#import "ios/chrome/browser/reading_list/reading_list_model_factory.h" |
+#import "ios/chrome/browser/snapshots/snapshot_cache.h" |
+#import "ios/chrome/browser/tabs/tab.h" |
+#import "ios/chrome/browser/tabs/tab_model_observer.h" |
+#import "ios/chrome/browser/ui/reading_list/reading_list_side_swipe_provider.h" |
+#import "ios/chrome/browser/ui/side_swipe/card_side_swipe_view.h" |
+#import "ios/chrome/browser/ui/side_swipe/history_side_swipe_provider.h" |
+#import "ios/chrome/browser/ui/side_swipe/side_swipe_navigation_view.h" |
+#import "ios/chrome/browser/ui/side_swipe/side_swipe_util.h" |
+#import "ios/chrome/browser/ui/side_swipe_gesture_recognizer.h" |
+#include "ios/chrome/browser/ui/ui_util.h" |
+#import "ios/web/public/web_state/web_state_observer_bridge.h" |
+#import "ios/web/web_state/ui/crw_web_controller.h" |
+ |
+namespace ios_internal { |
+NSString* const kSideSwipeWillStartNotification = |
+ @"kSideSwipeWillStartNotification"; |
+NSString* const kSideSwipeDidStopNotification = |
+ @"kSideSwipeDidStopNotification"; |
+} // namespace ios_internal |
+ |
+namespace { |
+ |
+enum class SwipeType { NONE, CHANGE_TAB, CHANGE_PAGE }; |
+ |
+// Swipe starting distance from edge. |
+const CGFloat kSwipeEdge = 20; |
+ |
+// Distance between sections of iPad side swipe. |
+const CGFloat kIpadTabSwipeDistance = 100; |
+ |
+// Number of tabs to keep in the grey image cache. |
+const NSUInteger kIpadGreySwipeTabCount = 8; |
+} |
+ |
+@interface SideSwipeController ()<CRWWebStateObserver, |
+ TabModelObserver, |
+ UIGestureRecognizerDelegate> { |
+ @private |
+ |
+ TabModel* model_; |
+ |
+ // Side swipe view for tab navigation. |
+ base::scoped_nsobject<CardSideSwipeView> tabSideSwipeView_; |
+ |
+ // Side swipe view for page navigation. |
+ base::scoped_nsobject<SideSwipeNavigationView> pageSideSwipeView_; |
+ |
+ // YES if the user is currently swiping. |
+ BOOL inSwipe_; |
+ |
+ // Swipe gesture recognizer. |
+ base::scoped_nsobject<SideSwipeGestureRecognizer> swipeGestureRecognizer_; |
+ |
+ base::scoped_nsobject<SideSwipeGestureRecognizer> panGestureRecognizer_; |
+ |
+ // Used in iPad side swipe gesture, tracks the starting tab index. |
+ NSUInteger startingTabIndex_; |
+ |
+ // If the swipe is for a page change or a tab change. |
+ SwipeType swipeType_; |
+ |
+ // Bridge to observe the web state from Objective-C. |
+ std::unique_ptr<web::WebStateObserverBridge> webStateObserverBridge_; |
+ |
+ // Curtain over web view while waiting for it to load. |
+ base::scoped_nsobject<UIView> curtain_; |
+ |
+ // Provides forward/back action for history entries. |
+ base::scoped_nsobject<HistorySideSwipeProvider> historySideSwipeProvider_; |
+ |
+ // Provides forward action for reading list. |
+ base::scoped_nsobject<ReadingListSideSwipeProvider> |
+ readingListSideSwipeProvider_; |
+ |
+ base::WeakNSProtocol<id<SideSwipeContentProvider>> currentContentProvider_; |
+} |
+ |
+// Load grey snapshots for the next |kIpadGreySwipeTabCount| tabs in |
+// |direction|. |
+- (void)createGreyCache:(UISwipeGestureRecognizerDirection)direction; |
+// Tell snapshot cache to clear grey cache. |
+- (void)deleteGreyCache; |
+// Handle tab side swipe for iPad. Change tabs according to swipe distance. |
+- (void)handleiPadTabSwipe:(SideSwipeGestureRecognizer*)gesture; |
+// Handle tab side swipe for iPhone. Introduces a CardSideSwipeView to convey |
+// the tab change. |
+- (void)handleiPhoneTabSwipe:(SideSwipeGestureRecognizer*)gesture; |
+// Overlays |curtain_| as a white view to hide the web view while it updates. |
+// Calls |completionHandler| when the curtain is removed. |
+- (void)addCurtainWithCompletionHandler:(ProceduralBlock)completionHandler; |
+// Removes the |curtain_| and calls |completionHandler| when the curtain is |
+// removed. |
+- (void)dismissCurtainWithCompletionHandler:(ProceduralBlock)completionHandler; |
+@end |
+ |
+@implementation SideSwipeController |
+ |
+@synthesize inSwipe = inSwipe_; |
+@synthesize swipeDelegate = swipeDelegate_; |
+@synthesize snapshotDelegate = snapshotDelegate_; |
+ |
+- (id)initWithTabModel:(TabModel*)model |
+ browserState:(ios::ChromeBrowserState*)browserState { |
+ DCHECK(model); |
+ self = [super init]; |
+ if (self) { |
+ model_ = model; |
+ [model_ addObserver:self]; |
+ historySideSwipeProvider_.reset( |
+ [[HistorySideSwipeProvider alloc] initWithTabModel:model_]); |
+ |
+ if (!browserState->IsOffTheRecord() && |
+ reading_list::switches::IsReadingListEnabled()) { |
+ readingListSideSwipeProvider_.reset([[ReadingListSideSwipeProvider alloc] |
+ initWithReadingList:ReadingListModelFactory::GetForBrowserState( |
+ browserState)]); |
+ } |
+ } |
+ return self; |
+} |
+ |
+- (void)dealloc { |
+ [model_ removeObserver:self]; |
+ [super dealloc]; |
+} |
+ |
+- (void)addHorizontalGesturesToView:(UIView*)view { |
+ swipeGestureRecognizer_.reset([[SideSwipeGestureRecognizer alloc] |
+ initWithTarget:self |
+ action:@selector(handleSwipe:)]); |
+ [swipeGestureRecognizer_ setMaximumNumberOfTouches:1]; |
+ [swipeGestureRecognizer_ setDelegate:self]; |
+ [swipeGestureRecognizer_ setSwipeEdge:kSwipeEdge]; |
+ [view addGestureRecognizer:swipeGestureRecognizer_]; |
+ |
+ // Add a second gesture recognizer to handle swiping on the toolbar to change |
+ // tabs. |
+ panGestureRecognizer_.reset([[SideSwipeGestureRecognizer alloc] |
+ initWithTarget:self |
+ action:@selector(handlePan:)]); |
+ [panGestureRecognizer_ setMaximumNumberOfTouches:1]; |
+ [panGestureRecognizer_ setSwipeThreshold:48]; |
+ [panGestureRecognizer_ setDelegate:self]; |
+ [view addGestureRecognizer:panGestureRecognizer_]; |
+} |
+ |
+- (NSSet*)swipeRecognizers { |
+ return [NSSet setWithObjects:swipeGestureRecognizer_.get(), nil]; |
+} |
+ |
+- (void)setEnabled:(BOOL)enabled { |
+ [swipeGestureRecognizer_ setEnabled:enabled]; |
+} |
+ |
+- (BOOL)shouldAutorotate { |
+ return !([tabSideSwipeView_ window] || inSwipe_); |
+} |
+ |
+// Always return yes, as this swipe should work with various recognizers, |
+// including UITextTapRecognizer, UILongPressGestureRecognizer, |
+// UIScrollViewPanGestureRecognizer and others. |
+- (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer |
+ shouldRecognizeSimultaneouslyWithGestureRecognizer: |
+ (UIGestureRecognizer*)otherGestureRecognizer { |
+ return YES; |
+} |
+ |
+- (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer |
+ shouldBeRequiredToFailByGestureRecognizer: |
+ (UIGestureRecognizer*)otherGestureRecognizer { |
+ // Only take precedence over a pan gesture recognizer so that moving up and |
+ // down while swiping doesn't trigger overscroll actions. |
+ if ([otherGestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) { |
+ return YES; |
+ } |
+ return NO; |
+} |
+ |
+// Gestures should only be recognized within |contentArea_| or the toolbar. |
+- (BOOL)gestureRecognizerShouldBegin:(SideSwipeGestureRecognizer*)gesture { |
+ if (inSwipe_) { |
+ return NO; |
+ } |
+ |
+ if ([swipeDelegate_ preventSideSwipe]) |
+ return NO; |
+ |
+ CGPoint location = [gesture locationInView:gesture.view]; |
+ |
+ // Both the toolbar frame and the contentView frame below are inset by |
+ // -1 because CGRectContainsPoint does include points on the max X and Y |
+ // edges, which will happen frequently with edge swipes from the right side. |
+ // Since the toolbar and the contentView can overlap, check the toolbar frame |
+ // first, and confirm the right gesture recognizer is firing. |
+ CGRect toolbarFrame = |
+ CGRectInset([[[swipeDelegate_ toolbarController] view] frame], -1, -1); |
+ if (CGRectContainsPoint(toolbarFrame, location)) { |
+ if (![gesture isEqual:panGestureRecognizer_]) { |
+ return NO; |
+ } |
+ |
+ if ([[swipeDelegate_ toolbarController] isOmniboxFirstResponder] || |
+ [[swipeDelegate_ toolbarController] showingOmniboxPopup]) { |
+ return NO; |
+ } |
+ return YES; |
+ } |
+ |
+ // Otherwise, only allow contentView touches with |swipeGestureRecognizer_|. |
+ CGRect contentViewFrame = |
+ CGRectInset([[swipeDelegate_ contentView] frame], -1, -1); |
+ if (CGRectContainsPoint(contentViewFrame, location)) { |
+ if (![gesture isEqual:swipeGestureRecognizer_]) { |
+ return NO; |
+ } |
+ swipeType_ = SwipeType::CHANGE_PAGE; |
+ return YES; |
+ } |
+ return NO; |
+} |
+ |
+- (void)createGreyCache:(UISwipeGestureRecognizerDirection)direction { |
+ NSInteger dx = (direction == UISwipeGestureRecognizerDirectionLeft) ? -1 : 1; |
+ NSInteger index = startingTabIndex_ + dx; |
+ NSMutableArray* sessionIDs = |
+ [NSMutableArray arrayWithCapacity:kIpadGreySwipeTabCount]; |
+ for (NSUInteger count = 0; count < kIpadGreySwipeTabCount; count++) { |
+ // Wrap around edges. |
+ if (index >= (NSInteger)[model_ count]) |
+ index = 0; |
+ else if (index < 0) |
+ index = [model_ count] - 1; |
+ |
+ // Don't wrap past the starting index. |
+ if (index == (NSInteger)startingTabIndex_) |
+ break; |
+ |
+ Tab* tab = [model_ tabAtIndex:index]; |
+ if (tab && tab.webController.usePlaceholderOverlay) { |
+ [sessionIDs addObject:[tab currentSessionID]]; |
+ } |
+ index = index + dx; |
+ } |
+ [[SnapshotCache sharedInstance] createGreyCache:sessionIDs]; |
+ for (Tab* tab in model_) { |
+ tab.useGreyImageCache = YES; |
+ } |
+} |
+ |
+- (void)deleteGreyCache { |
+ [[SnapshotCache sharedInstance] removeGreyCache]; |
+ for (Tab* tab in model_) { |
+ tab.useGreyImageCache = NO; |
+ } |
+} |
+ |
+- (void)handlePan:(SideSwipeGestureRecognizer*)gesture { |
+ if (!IsIPadIdiom()) { |
+ return [self handleiPhoneTabSwipe:gesture]; |
+ } else { |
+ return [self handleiPadTabSwipe:gesture]; |
+ } |
+} |
+ |
+- (void)handleSwipe:(SideSwipeGestureRecognizer*)gesture { |
+ DCHECK(swipeType_ != SwipeType::NONE); |
+ if (swipeType_ == SwipeType::CHANGE_TAB) { |
+ if (!IsIPadIdiom()) { |
+ return [self handleiPhoneTabSwipe:gesture]; |
+ } else { |
+ return [self handleiPadTabSwipe:gesture]; |
+ } |
+ } |
+ if (swipeType_ == SwipeType::CHANGE_PAGE) { |
+ return [self handleSwipeToNavigate:gesture]; |
+ } |
+ NOTREACHED(); |
+} |
+ |
+- (void)handleiPadTabSwipe:(SideSwipeGestureRecognizer*)gesture { |
+ // Don't handle swipe when there are no tabs. |
+ NSInteger count = [model_ count]; |
+ if (count == 0) |
+ return; |
+ |
+ if (gesture.state == UIGestureRecognizerStateBegan) { |
+ // If the toolbar is hidden, move it to visible. |
+ [[model_ currentTab] updateFullscreenWithToolbarVisible:YES]; |
+ [[model_ currentTab] updateSnapshotWithOverlay:YES visibleFrameOnly:YES]; |
+ [[NSNotificationCenter defaultCenter] |
+ postNotificationName:ios_internal::kSideSwipeWillStartNotification |
+ object:nil]; |
+ [[swipeDelegate_ tabStripController] setHighlightsSelectedTab:YES]; |
+ startingTabIndex_ = [model_ indexOfTab:[model_ currentTab]]; |
+ [self createGreyCache:gesture.direction]; |
+ } else if (gesture.state == UIGestureRecognizerStateChanged) { |
+ // Side swipe for iPad involves changing the selected tab as the swipe moves |
+ // across the width of the view. The screen is broken up into |
+ // |kIpadTabSwipeDistance| / |width| segments, with the current tab in the |
+ // first section. The swipe does not wrap edges. |
+ CGFloat distance = [gesture locationInView:gesture.view].x; |
+ if (gesture.direction == UISwipeGestureRecognizerDirectionLeft) { |
+ distance = gesture.startPoint.x - distance; |
+ } else { |
+ distance -= gesture.startPoint.x; |
+ } |
+ |
+ NSInteger indexDelta = std::floor(distance / kIpadTabSwipeDistance); |
+ // Don't wrap past the first tab. |
+ if (indexDelta < count) { |
+ // Flip delta when swiping forward. |
+ if (IsSwipingForward(gesture.direction)) |
+ indexDelta = 0 - indexDelta; |
+ |
+ Tab* currentTab = [model_ currentTab]; |
+ NSInteger currentIndex = [model_ indexOfTab:currentTab]; |
+ |
+ // Wrap around edges. |
+ NSInteger newIndex = (NSInteger)(startingTabIndex_ + indexDelta) % count; |
+ |
+ // C99 defines the modulo result as negative if our offset is negative. |
+ if (newIndex < 0) |
+ newIndex += count; |
+ |
+ if (newIndex != currentIndex) { |
+ Tab* tab = [model_ tabAtIndex:newIndex]; |
+ // Toggle overlay preview mode for selected tab. |
+ [tab.webController setOverlayPreviewMode:YES]; |
+ [model_ setCurrentTab:tab]; |
+ // And disable overlay preview mode for last selected tab. |
+ [currentTab.webController setOverlayPreviewMode:NO]; |
+ } |
+ } |
+ } else { |
+ if (gesture.state == UIGestureRecognizerStateCancelled) { |
+ Tab* tab = [model_ tabAtIndex:startingTabIndex_]; |
+ [[model_ currentTab].webController setOverlayPreviewMode:NO]; |
+ [model_ setCurrentTab:tab]; |
+ } |
+ [[model_ currentTab].webController setOverlayPreviewMode:NO]; |
+ |
+ // Redisplay the view if it was in overlay preview mode. |
+ [swipeDelegate_ displayTab:[model_ currentTab] isNewSelection:YES]; |
+ [[swipeDelegate_ tabStripController] setHighlightsSelectedTab:NO]; |
+ [self deleteGreyCache]; |
+ [[NSNotificationCenter defaultCenter] |
+ postNotificationName:ios_internal::kSideSwipeDidStopNotification |
+ object:nil]; |
+ } |
+} |
+ |
+- (id<SideSwipeContentProvider>)contentProviderForGesture:(BOOL)goBack { |
+ if (goBack && [historySideSwipeProvider_ canGoBack]) { |
+ return historySideSwipeProvider_; |
+ } |
+ if (!goBack && [historySideSwipeProvider_ canGoForward]) { |
+ return historySideSwipeProvider_; |
+ } |
+ if (goBack && [readingListSideSwipeProvider_ canGoBack]) { |
+ return readingListSideSwipeProvider_; |
+ } |
+ if (!goBack && [readingListSideSwipeProvider_ canGoForward]) { |
+ return readingListSideSwipeProvider_; |
+ } |
+ return nil; |
+} |
+ |
+// Show swipe to navigate. |
+- (void)handleSwipeToNavigate:(SideSwipeGestureRecognizer*)gesture { |
+ if (gesture.state == UIGestureRecognizerStateBegan) { |
+ // If the toolbar is hidden, move it to visible. |
+ [[model_ currentTab] updateFullscreenWithToolbarVisible:YES]; |
+ |
+ inSwipe_ = YES; |
+ [swipeDelegate_ updateAccessoryViewsForSideSwipeWithVisibility:NO]; |
+ BOOL goBack = IsSwipingBack(gesture.direction); |
+ |
+ currentContentProvider_.reset([self contentProviderForGesture:goBack]); |
+ BOOL canNavigate = currentContentProvider_ != nil; |
+ |
+ CGRect gestureBounds = gesture.view.bounds; |
+ CGFloat headerHeight = [swipeDelegate_ headerHeight]; |
+ CGRect navigationFrame = |
+ CGRectMake(CGRectGetMinX(gestureBounds), |
+ CGRectGetMinY(gestureBounds) + headerHeight, |
+ CGRectGetWidth(gestureBounds), |
+ CGRectGetHeight(gestureBounds) - headerHeight); |
+ |
+ pageSideSwipeView_.reset([[SideSwipeNavigationView alloc] |
+ initWithFrame:navigationFrame |
+ withDirection:gesture.direction |
+ canNavigate:canNavigate |
+ image:[currentContentProvider_ paneIcon] |
+ rotateForward:[currentContentProvider_ rotateForwardIcon]]); |
+ [pageSideSwipeView_ setTargetView:[swipeDelegate_ contentView]]; |
+ |
+ [gesture.view insertSubview:pageSideSwipeView_ |
+ belowSubview:[[swipeDelegate_ toolbarController] view]]; |
+ } |
+ |
+ base::WeakNSObject<Tab> weakCurrentTab([model_ currentTab]); |
+ [pageSideSwipeView_ handleHorizontalPan:gesture |
+ onOverThresholdCompletion:^{ |
+ BOOL wantsBack = IsSwipingBack(gesture.direction); |
+ web::WebState* webState = [weakCurrentTab webState]; |
+ if (wantsBack) { |
+ [currentContentProvider_ goBack:webState]; |
+ } else { |
+ [currentContentProvider_ goForward:webState]; |
+ } |
+ |
+ if (webState && webState->IsLoading()) { |
+ webStateObserverBridge_.reset( |
+ new web::WebStateObserverBridge(webState, self)); |
+ [self addCurtainWithCompletionHandler:^{ |
+ inSwipe_ = NO; |
+ }]; |
+ } else { |
+ inSwipe_ = NO; |
+ } |
+ [swipeDelegate_ updateAccessoryViewsForSideSwipeWithVisibility:YES]; |
+ } |
+ onUnderThresholdCompletion:^{ |
+ [swipeDelegate_ updateAccessoryViewsForSideSwipeWithVisibility:YES]; |
+ inSwipe_ = NO; |
+ }]; |
+} |
+ |
+// Show horizontal swipe stack view for iPhone. |
+- (void)handleiPhoneTabSwipe:(SideSwipeGestureRecognizer*)gesture { |
+ if (gesture.state == UIGestureRecognizerStateBegan) { |
+ // If the toolbar is hidden, move it to visible. |
+ [[model_ currentTab] updateFullscreenWithToolbarVisible:YES]; |
+ |
+ inSwipe_ = YES; |
+ |
+ CGRect frame = [[swipeDelegate_ contentView] frame]; |
+ |
+ // Add horizontal stack view controller. |
+ CGFloat headerHeight = |
+ [self.snapshotDelegate snapshotContentAreaForTab:[model_ currentTab]] |
+ .origin.y; |
+ if (tabSideSwipeView_) { |
+ [tabSideSwipeView_ setFrame:frame]; |
+ [tabSideSwipeView_ setTopMargin:headerHeight]; |
+ } else { |
+ tabSideSwipeView_.reset([[CardSideSwipeView alloc] |
+ initWithFrame:frame |
+ topMargin:headerHeight |
+ model:model_]); |
+ [tabSideSwipeView_ setAutoresizingMask:UIViewAutoresizingFlexibleWidth | |
+ UIViewAutoresizingFlexibleHeight]; |
+ [tabSideSwipeView_ setDelegate:swipeDelegate_]; |
+ [tabSideSwipeView_ setBackgroundColor:[UIColor blackColor]]; |
+ } |
+ |
+ // Ensure that there's an up-to-date snapshot of the current tab. |
+ [[model_ currentTab] updateSnapshotWithOverlay:YES visibleFrameOnly:YES]; |
+ // Hide the infobar after snapshot has been updated (see the previous line) |
+ // to avoid it obscuring the cards in the side swipe view. |
+ [swipeDelegate_ updateAccessoryViewsForSideSwipeWithVisibility:NO]; |
+ |
+ // Layout tabs with new snapshots in the current orientation. |
+ [tabSideSwipeView_ |
+ updateViewsForDirection:gesture.direction |
+ withToolbar:[swipeDelegate_ toolbarController]]; |
+ |
+ // Insert behind infobar container (which is below toolbar) |
+ // so card border doesn't look janky during animation. |
+ DCHECK([swipeDelegate_ verifyToolbarViewPlacementInView:gesture.view]); |
+ // Insert above the toolbar. |
+ [gesture.view addSubview:tabSideSwipeView_]; |
+ |
+ // Remove content area so it doesn't receive any pan events. |
+ [[swipeDelegate_ contentView] removeFromSuperview]; |
+ } |
+ |
+ [tabSideSwipeView_ handleHorizontalPan:gesture]; |
+} |
+ |
+- (void)addCurtainWithCompletionHandler:(ProceduralBlock)completionHandler { |
+ if (!curtain_) { |
+ curtain_.reset( |
+ [[UIView alloc] initWithFrame:[swipeDelegate_ contentView].bounds]); |
+ [curtain_ setBackgroundColor:[UIColor whiteColor]]; |
+ } |
+ [[swipeDelegate_ contentView] addSubview:curtain_]; |
+ |
+ // Fallback in case load takes a while. 3 seconds is a balance between how |
+ // long it can take a web view to clear the previous page image, and what |
+ // feels like to 'too long' to see the curtain. |
+ [self performSelector:@selector(dismissCurtainWithCompletionHandler:) |
+ withObject:[[completionHandler copy] autorelease] |
+ afterDelay:3]; |
+} |
+ |
+- (void)dismissCurtainWithCompletionHandler:(ProceduralBlock)completionHandler { |
+ [NSObject cancelPreviousPerformRequestsWithTarget:self]; |
+ webStateObserverBridge_.reset(); |
+ [curtain_ removeFromSuperview]; |
+ curtain_.reset(); |
+ completionHandler(); |
+} |
+ |
+#pragma mark - CRWWebStateObserver Methods |
+ |
+- (void)webStateDidStopLoading:(web::WebState*)webState { |
+ [self dismissCurtainWithCompletionHandler:^{ |
+ inSwipe_ = NO; |
+ }]; |
+} |
+ |
+#pragma mark - TabModelObserver Methods |
+ |
+- (void)tabModel:(TabModel*)model |
+ didChangeActiveTab:(Tab*)newTab |
+ previousTab:(Tab*)previousTab |
+ atIndex:(NSUInteger)index { |
+ // Toggling the gesture's enabled state off and on will effectively cancel |
+ // the gesture recognizer. |
+ [swipeGestureRecognizer_ setEnabled:NO]; |
+ [swipeGestureRecognizer_ setEnabled:YES]; |
+} |
+ |
+@end |