Skip to content

Commit

Permalink
[BottomNavigation] Adds the Ripple behavior to BottomNavigation. (#7589)
Browse files Browse the repository at this point in the history
## Related links
* Bug: Closes #7377  
* Ripple: [MDCRippleTouchController](https://github.com/material-components/material-components-ios/tree/develop/components/Ripple)

## Introduction
This PR integrates [`MDCRippleTouchController`](https://github.com/material-components/material-components-ios/blob/develop/components/Ripple/src/MDCRippleTouchController.h) into [`MDCBottomNavigationBar`](https://github.com/material-components/material-components-ios/tree/develop/components/BottomNavigation). This behavior is an opt in flag property added to MDCBottomNavigationBar, `enableRippleBehavior`.  This improves the visual touch feedback for our users as MDCInkView didn't support our current motion guidelines.

## Videos
| Before | After |
| - | - |
|![oldRipple](https://user-images.githubusercontent.com/4066863/59464465-81c2a880-8df6-11e9-82a1-be6b2e4b3840.gif)|![newRipple](https://user-images.githubusercontent.com/4066863/59464470-82f3d580-8df6-11e9-8df3-9fcd9d36d1f6.gif)|
  • Loading branch information
yarneo committed Jun 15, 2019
1 parent 3b4bc76 commit 9246b77
Show file tree
Hide file tree
Showing 7 changed files with 197 additions and 2 deletions.
1 change: 1 addition & 0 deletions MaterialComponents.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,7 @@ Pod::Spec.new do |mdc|

component.dependency "MDFInternationalization"
component.dependency "MaterialComponents/Ink"
component.dependency "MaterialComponents/Ripple"
component.dependency "MaterialComponents/Palettes"
component.dependency "MaterialComponents/ShadowElevations"
component.dependency "MaterialComponents/ShadowLayer"
Expand Down
1 change: 1 addition & 0 deletions components/BottomNavigation/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ mdc_objc_library(
deps = [
"//components/Ink",
"//components/Palettes",
"//components/Ripple",
"//components/ShadowElevations",
"//components/ShadowLayer",
"//components/Typography",
Expand Down
12 changes: 12 additions & 0 deletions components/BottomNavigation/src/MDCBottomNavigationBar.h
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,18 @@ typedef NS_ENUM(NSInteger, MDCBottomNavigationBarAlignment) {
*/
@property(nonatomic, assign) NSInteger titlesNumberOfLines;

/**
By setting this property to @c YES, the Ripple component will be used instead of Ink
to display visual feedback to the user.
@note This property will eventually be enabled by default, deprecated, and then deleted as part
of our migration to Ripple. Learn more at
https://github.com/material-components/material-components-ios/tree/develop/components/Ink#migration-guide-ink-to-ripple
Defaults to NO.
*/
@property(nonatomic, assign) BOOL enableRippleBehavior;

/**
Returns the navigation bar subview associated with the specific item.
Expand Down
22 changes: 21 additions & 1 deletion components/BottomNavigation/src/MDCBottomNavigationBar.m
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@

static NSString *const kOfAnnouncement = @"of";

@interface MDCBottomNavigationBar () <MDCInkTouchControllerDelegate>
@interface MDCBottomNavigationBar () <MDCInkTouchControllerDelegate,
MDCRippleTouchControllerDelegate>

// Declared in MDCBottomNavigationBar (ToBeDeprecated)
@property(nonatomic, assign) BOOL sizeThatFitsIncludesSafeArea;
Expand Down Expand Up @@ -543,6 +544,7 @@ - (void)setItems:(NSArray<UITabBarItem *> *)items {
MDCInkTouchController *controller = [[MDCInkTouchController alloc] initWithView:itemView];
controller.delegate = self;
[self.inkControllers addObject:controller];
itemView.rippleTouchController.delegate = self;

if (self.shouldPretendToBeATabBar) {
NSString *key = kMaterialBottomNavigationStringTable
Expand Down Expand Up @@ -760,4 +762,22 @@ - (MDCInkView *)inkTouchController:(MDCInkTouchController *)inkTouchController
return nil;
}

- (BOOL)inkTouchController:(MDCInkTouchController *)inkTouchController
shouldProcessInkTouchesAtTouchLocation:(CGPoint)location {
if (self.enableRippleBehavior) {
return NO;
}
return YES;
}

#pragma mark - MDCRippleTouchControllerDelegate methods

- (BOOL)rippleTouchController:(MDCRippleTouchController *)rippleTouchController
shouldProcessRippleTouchesAtTouchLocation:(CGPoint)location {
if (self.enableRippleBehavior) {
return YES;
}
return NO;
}

@end
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,15 @@

#import "MDCBottomNavigationBar.h"
#import "MaterialInk.h"
#import "MaterialRipple.h"

@interface MDCBottomNavigationItemView : UIView

@property(nonatomic, assign) BOOL titleBelowIcon;
@property(nonatomic, assign) BOOL selected;
@property(nonatomic, assign) MDCBottomNavigationBarTitleVisibility titleVisibility;
@property(nonatomic, strong) MDCInkView *inkView;
@property(nonatomic, strong) MDCRippleTouchController *rippleTouchController;
@property(nonatomic, assign) UIOffset titlePositionAdjustment;

@property(nonatomic, copy) NSString *badgeValue;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,11 @@ - (void)commonMDCBottomNavigationItemViewInit {
[self addSubview:_inkView];
}

if (!_rippleTouchController) {
_rippleTouchController = [[MDCRippleTouchController alloc] initWithView:self];
_rippleTouchController.rippleView.rippleStyle = MDCRippleStyleUnbounded;
}

if (!_button) {
_button = [[UIButton alloc] initWithFrame:self.bounds];
_button.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
Expand Down Expand Up @@ -480,8 +485,10 @@ - (void)setSelectedItemTintColor:(UIColor *)selectedItemTintColor {
self.iconImageView.tintColor = self.selectedItemTintColor;
self.label.textColor = self.selectedItemTitleColor;
}
self.inkView.inkColor =
UIColor *rippleColor =
[self.selectedItemTintColor colorWithAlphaComponent:MDCBottomNavigationItemViewInkOpacity];
self.inkView.inkColor = rippleColor;
self.rippleTouchController.rippleView.rippleColor = rippleColor;
}

- (void)setUnselectedItemTintColor:(UIColor *)unselectedItemTintColor {
Expand Down
152 changes: 152 additions & 0 deletions components/BottomNavigation/tests/unit/BottomNavigationRippleTests.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
// Copyright 2019-present the Material Components for iOS authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#import <XCTest/XCTest.h>

#import "../../src/private/MDCBottomNavigationItemView.h"
#import "MaterialBottomNavigation.h"
#import "MaterialInk.h"
#import "MaterialRipple.h"

@interface MDCBottomNavigationBar (Testing)
@property(nonatomic, strong) NSMutableArray<MDCBottomNavigationItemView *> *itemViews;
@property(nonatomic, strong) NSMutableArray *inkControllers;
- (BOOL)inkTouchController:(MDCInkTouchController *)inkTouchController
shouldProcessInkTouchesAtTouchLocation:(CGPoint)location;
- (BOOL)rippleTouchController:(MDCRippleTouchController *)rippleTouchController
shouldProcessRippleTouchesAtTouchLocation:(CGPoint)location;
@end

/**
This class confirms behavior of @c MDCBottomNavigationBar when used with Ripple.
*/
@interface BottomNavigationRippleTests : XCTestCase

@property(nonatomic, strong, nullable) MDCBottomNavigationBar *bottomNavigationBar;

@end

@implementation BottomNavigationRippleTests

- (void)setUp {
[super setUp];

self.bottomNavigationBar = [[MDCBottomNavigationBar alloc] init];
UITabBarItem *item1 = [[UITabBarItem alloc] initWithTitle:@"1" image:nil tag:0];
UITabBarItem *item2 = [[UITabBarItem alloc] initWithTitle:@"2" image:nil tag:0];
self.bottomNavigationBar.items = @[ item1, item2 ];
}

- (void)tearDown {
self.bottomNavigationBar = nil;

[super tearDown];
}

/**
Test to confirm behavior of initializing a @c MDCBottomNavigationBar without any customization.
*/
- (void)testEnabledInkAndDisabledRippleColorsAndSuperviewsAndBounds {
// Then
XCTAssertFalse(self.bottomNavigationBar.enableRippleBehavior);
for (MDCBottomNavigationItemView *itemView in self.bottomNavigationBar.itemViews) {
XCTAssertEqualObjects(itemView.rippleTouchController.rippleView.rippleColor,
[UIColor.blackColor colorWithAlphaComponent:(CGFloat)0.15]);
XCTAssertEqualObjects(itemView.inkView.inkColor,
[UIColor.blackColor colorWithAlphaComponent:(CGFloat)0.15]);
XCTAssertEqual(itemView.rippleTouchController.rippleView.rippleStyle, MDCRippleStyleUnbounded);
XCTAssertNotNil(itemView.rippleTouchController.rippleView.superview);
XCTAssertNotNil(itemView.inkView.superview);
CGRect itemViewBounds = CGRectStandardize(itemView.bounds);
CGRect inkBounds = CGRectStandardize(itemView.inkView.bounds);
XCTAssertTrue(CGRectEqualToRect(itemViewBounds, inkBounds), @"%@ is not equal to %@",
NSStringFromCGRect(itemViewBounds), NSStringFromCGRect(inkBounds));
}
}

/**
Test to confirm behavior of initializing a @c MDCBottomNavigationBar with Ripple enabled.
*/
- (void)testEnabledRippleAndDisabledInkColorsAndSuperviewsAndBoundsWithRippleBehaviorEnabled {
// When
self.bottomNavigationBar.enableRippleBehavior = YES;

// Then
XCTAssertTrue(self.bottomNavigationBar.enableRippleBehavior);
for (MDCBottomNavigationItemView *itemView in self.bottomNavigationBar.itemViews) {
XCTAssertEqualObjects(itemView.rippleTouchController.rippleView.rippleColor,
[UIColor.blackColor colorWithAlphaComponent:(CGFloat)0.15]);
XCTAssertEqualObjects(itemView.inkView.inkColor,
[UIColor.blackColor colorWithAlphaComponent:(CGFloat)0.15]);
XCTAssertEqual(itemView.rippleTouchController.rippleView.rippleStyle, MDCRippleStyleUnbounded);
XCTAssertNotNil(itemView.rippleTouchController.rippleView.superview);
XCTAssertNotNil(itemView.inkView.superview);
CGRect itemViewBounds = CGRectStandardize(itemView.bounds);
CGRect rippleBounds = CGRectStandardize(itemView.rippleTouchController.rippleView.bounds);
XCTAssertTrue(CGRectEqualToRect(itemViewBounds, rippleBounds), @"%@ is not equal to %@",
NSStringFromCGRect(itemViewBounds), NSStringFromCGRect(rippleBounds));
}
}

/**
Test to confirm toggling @c enableRippleBehavior triggers ripple on touch and not ink.
*/
- (void)testSetEnableRippleBehaviorToYesThenInvokeItemToCheckRippleIsInvoked {
// When
self.bottomNavigationBar.enableRippleBehavior = YES;

// Then
for (MDCBottomNavigationItemView *itemView in self.bottomNavigationBar.itemViews) {
XCTAssertTrue([self.bottomNavigationBar rippleTouchController:itemView.rippleTouchController
shouldProcessRippleTouchesAtTouchLocation:CGPointZero]);
}
for (MDCInkTouchController *controller in self.bottomNavigationBar.inkControllers) {
XCTAssertFalse([self.bottomNavigationBar inkTouchController:controller
shouldProcessInkTouchesAtTouchLocation:CGPointZero]);
}
}

/**
Test to confirm that default behavior triggers ink on touch and not ripple.
*/
- (void)testTouchingItemToCheckInkIsInvokedAndNotRipple {
// Then
for (MDCBottomNavigationItemView *itemView in self.bottomNavigationBar.itemViews) {
XCTAssertFalse([self.bottomNavigationBar rippleTouchController:itemView.rippleTouchController
shouldProcessRippleTouchesAtTouchLocation:CGPointZero]);
}
for (MDCInkTouchController *controller in self.bottomNavigationBar.inkControllers) {
XCTAssertTrue([self.bottomNavigationBar inkTouchController:controller
shouldProcessInkTouchesAtTouchLocation:CGPointZero]);
}
}

/**
Test setting BottomNavigation's selectedItemTintColor API updates the internal
RippleTouchController's ripple color.
*/
- (void)testSetEnableRippleBehaviorToYesThenSetSelectedItemTintColorToSetRippleColor {
// When
self.bottomNavigationBar.enableRippleBehavior = YES;
[self.bottomNavigationBar setSelectedItemTintColor:UIColor.redColor];

// Then
for (MDCBottomNavigationItemView *itemView in self.bottomNavigationBar.itemViews) {
XCTAssertEqualObjects(itemView.rippleTouchController.rippleView.rippleColor,
[UIColor.redColor colorWithAlphaComponent:(CGFloat)0.15]);
XCTAssertEqualObjects(itemView.inkView.inkColor,
[UIColor.redColor colorWithAlphaComponent:(CGFloat)0.15]);
}
}
@end

0 comments on commit 9246b77

Please sign in to comment.