Skip to content

Commit

Permalink
[Tabs] Give MDCTabBarDelegate pass through methods for "willDisplayCe…
Browse files Browse the repository at this point in the history
…ll"/"didEndDisplayingCell" (#7518)

One of the solutions proposed in #6275 is to provide pass through methods for the UICollectionViewDelegate methods `-collectionView:willDisplayCell:forItemAtIndexPath:` and `-collectionView:didEndDisplayingCell:forItemAtIndexPath:`. This is the one I chose, mainly because it seemed the most straightforward. [One comment](https://github.com/material-components/material-components-ios/issues/6275#issuecomment-453650714) said we should be careful about this approach, and [another](https://github.com/material-components/material-components-ios/issues/6275#issuecomment-454204635) said we needed to look more into the "pre-fetching" behavior of these methods. It seems like a good approach to me... Scrolling through the tabs in the TabBarTextOnlyExample with print statements seems to work how I would expect it to. 👍
  • Loading branch information
andrewoverton committed Jun 7, 2019
1 parent b8ea0ba commit 08100bd
Show file tree
Hide file tree
Showing 6 changed files with 167 additions and 1 deletion.
13 changes: 13 additions & 0 deletions components/Tabs/examples/TabBarTextOnlyExample.m
Expand Up @@ -14,6 +14,7 @@

#import <UIKit/UIKit.h>

#import "MDCTabBarDisplayDelegate.h"
#import "MaterialAppBar.h"
#import "MaterialButtons.h"
#import "MaterialCollections.h"
Expand Down Expand Up @@ -72,6 +73,8 @@ - (void)loadTabBar {
self.tabBar.autoresizingMask =
UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleBottomMargin;
[self.tabBar sizeToFit];

self.tabBar.displayDelegate = self;
}

- (void)changeAlignment:(id)sender {
Expand Down Expand Up @@ -132,4 +135,14 @@ - (void)collectionView:(UICollectionView *)collectionView
}
}

#pragma mark - MDCTabBarDisplayDelegate

- (void)tabBar:(MDCTabBar *)tabBar willDisplayItem:(UITabBarItem *)item {
NSLog(@"Will display item: %@", item.title);
}

- (void)tabBar:(MDCTabBar *)tabBar didEndDisplayingItem:(nonnull UITabBarItem *)item {
NSLog(@"Did end displaying item: %@", item.title);
}

@end
Expand Up @@ -19,12 +19,13 @@

#import <UIKit/UIKit.h>

#import "MDCTabBarDisplayDelegate.h"
#import "MaterialAppBar.h"
#import "MaterialCollections.h"
#import "MaterialColorScheme.h"
#import "MaterialTabs.h"

@interface TabBarTextOnlyExample : MDCCollectionViewController
@interface TabBarTextOnlyExample : MDCCollectionViewController <MDCTabBarDisplayDelegate>

@property(nonatomic, nullable) MDCAppBarViewController *appBarViewController;
@property(nonatomic, nullable) MDCSemanticColorScheme *colorScheme;
Expand Down
5 changes: 5 additions & 0 deletions components/Tabs/src/MDCTabBar.m
Expand Up @@ -16,6 +16,7 @@

#import <MDFInternationalization/MDFInternationalization.h>

#import "MDCTabBarDisplayDelegate.h"
#import "MDCTabBarExtendedAlignment.h"
#import "MDCTabBarIndicatorTemplate.h"
#import "MDCTabBarSizeClassDelegate.h"
Expand Down Expand Up @@ -82,6 +83,10 @@ @interface MDCTabBar ()
@property(nonatomic, weak, nullable) id<MDCTabBarSizeClassDelegate> sizeClassDelegate;
@end

@interface MDCTabBar ()
@property(nonatomic, weak, nullable) id<MDCTabBarDisplayDelegate> displayDelegate;
@end

@interface MDCTabBar () <MDCItemBarDelegate>
@end

Expand Down
46 changes: 46 additions & 0 deletions components/Tabs/src/MDCTabBarDisplayDelegate.h
@@ -0,0 +1,46 @@
// 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 <Foundation/Foundation.h>
#import "MDCTabBar.h"

/**
An additional delegate protocol for MDCTabBar that provides information about when UITabBarItems
are about to be displayed and when they stop being displayed.
*/
@protocol MDCTabBarDisplayDelegate

/**
This method is called sometime before the tab's view is displayed.
*/
- (void)tabBar:(nonnull MDCTabBar *)tabBar willDisplayItem:(nonnull UITabBarItem *)item;

/**
This method is called sometime after the tab's view has stopped being displayed.
*/
- (void)tabBar:(nonnull MDCTabBar *)tabBar didEndDisplayingItem:(nonnull UITabBarItem *)item;

@end

@interface MDCTabBar (MDCTabBarDisplayDelegate)

/**
A delegate that allows implementers to receive updates on when UITabBarItems are about to be
displayed and when they stop being displayed.
@note This property may be removed in a future version and should be used with that understanding.
*/
@property(nonatomic, weak, nullable) NSObject<MDCTabBarDisplayDelegate> *displayDelegate;

@end
15 changes: 15 additions & 0 deletions components/Tabs/src/private/MDCItemBar.m
Expand Up @@ -18,6 +18,7 @@

#import "MDCItemBarCell.h"
#import "MDCItemBarStyle.h"
#import "MDCTabBarDisplayDelegate.h"
#import "MDCTabBarIndicatorAttributes.h"
#import "MDCTabBarIndicatorTemplate.h"
#import "MDCTabBarIndicatorView.h"
Expand Down Expand Up @@ -408,6 +409,20 @@ - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView
return itemCell;
}

- (void)collectionView:(UICollectionView *)collectionView
willDisplayCell:(UICollectionViewCell *)cell
forItemAtIndexPath:(NSIndexPath *)indexPath {
UITabBarItem *item = [self itemAtIndexPath:indexPath];
[self.tabBar.displayDelegate tabBar:self.tabBar willDisplayItem:item];
}

- (void)collectionView:(UICollectionView *)collectionView
didEndDisplayingCell:(UICollectionViewCell *)cell
forItemAtIndexPath:(NSIndexPath *)indexPath {
UITabBarItem *item = [self itemAtIndexPath:indexPath];
[self.tabBar.displayDelegate tabBar:self.tabBar didEndDisplayingItem:item];
}

#pragma mark - UICollectionViewDelegateFlowLayout

- (CGSize)collectionView:(UICollectionView *)collectionView
Expand Down
86 changes: 86 additions & 0 deletions components/Tabs/tests/unit/MDCTabBarDisplayDelegateTests.m
@@ -0,0 +1,86 @@
// 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 "MDCItemBar.h"
#import "MDCTabBarDisplayDelegate.h"
#import "MaterialTabs.h"

@interface MDCTabBarDisplayDelegate : NSObject <MDCTabBarDisplayDelegate>
@property(nonatomic, assign) BOOL willDisplayItemWasCalled;
@property(nonatomic, assign) BOOL didEndDisplayingItemWasCalled;
@end

@implementation MDCTabBarDisplayDelegate

- (void)tabBar:(MDCTabBar *)tabBar willDisplayItem:(UITabBarItem *)item {
self.willDisplayItemWasCalled = YES;
}

- (void)tabBar:(MDCTabBar *)tabBar didEndDisplayingItem:(UITabBarItem *)item {
self.didEndDisplayingItemWasCalled = YES;
}

@end

@interface MDCTabBarDisplayDelegateTests : XCTestCase
@end

@implementation MDCTabBarDisplayDelegateTests

- (void)testMDCTabBarDisplayDelegateTabBarWillDisplayItemWhenViewHasBeenLaidOut {
// Given
MDCTabBar *tabBar = [[MDCTabBar alloc] initWithFrame:CGRectZero];
MDCTabBarDisplayDelegate *displayDelegate = [[MDCTabBarDisplayDelegate alloc] init];
tabBar.displayDelegate = displayDelegate;
CGFloat tabBarHeight = [MDCTabBar defaultHeightForItemAppearance:tabBar.itemAppearance];
tabBar.frame = CGRectMake(0, 0, 200, tabBarHeight);

// When
UITabBarItem *item1 = [[UITabBarItem alloc] initWithTitle:@"first tab" image:nil tag:0];
UITabBarItem *item2 = [[UITabBarItem alloc] initWithTitle:@"second tab" image:nil tag:0];
tabBar.items = @[ item1, item2 ];
[tabBar setNeedsLayout];
[tabBar layoutIfNeeded];

// Then
XCTAssertTrue(displayDelegate.willDisplayItemWasCalled);
}

- (void)testMDCTabBarDisplayDelegateTabBarDidEndDisplayingItemWhenViewHasBeenLaidOut {
// Given
MDCTabBar *tabBar = [[MDCTabBar alloc] initWithFrame:CGRectZero];
MDCTabBarDisplayDelegate *displayDelegate = [[MDCTabBarDisplayDelegate alloc] init];
tabBar.displayDelegate = displayDelegate;
CGFloat tabBarHeight = [MDCTabBar defaultHeightForItemAppearance:tabBar.itemAppearance];
tabBar.frame = CGRectMake(0, 0, 200, tabBarHeight);
UITabBarItem *item1 = [[UITabBarItem alloc] initWithTitle:@"first tab" image:nil tag:0];
UITabBarItem *item2 = [[UITabBarItem alloc] initWithTitle:@"second tab" image:nil tag:0];
tabBar.items = @[ item1, item2 ];
[tabBar setNeedsLayout];
[tabBar layoutIfNeeded];

// When
UITabBarItem *item3 = [[UITabBarItem alloc] initWithTitle:@"third tab" image:nil tag:0];
UITabBarItem *item4 = [[UITabBarItem alloc] initWithTitle:@"fourth tab" image:nil tag:0];
tabBar.items = @[ item3, item4 ];
[tabBar setNeedsLayout];
[tabBar layoutIfNeeded];

// Then
XCTAssertTrue(displayDelegate.didEndDisplayingItemWasCalled);
}

@end

0 comments on commit 08100bd

Please sign in to comment.