Skip to content

Commit

Permalink
[Tabs] Fix contentFrame for item view. (#7782)
Browse files Browse the repository at this point in the history
The content frame should be:

* `titleLabel` frame using its `intrinsicContentSize` if there is no image.
* `iconImageView` frame if there is no title text.
* `titleLabel` fitting size for width, a height that covers both the image and title view.

Closes #7781
  • Loading branch information
Robert Moore committed Jul 3, 2019
1 parent 88e5a52 commit 0881659
Show file tree
Hide file tree
Showing 6 changed files with 234 additions and 6 deletions.
67 changes: 61 additions & 6 deletions components/Tabs/src/TabBarView/private/MDCTabBarViewItemView.m
Expand Up @@ -99,7 +99,16 @@ - (void)commonMDCTabBarViewItemViewInit {
}

- (CGRect)contentFrame {
return self.contentView.frame;
if (!self.iconImageView.image) {
if (self.titleLabel.text.length) {
return [self contentFrameForTitleLabelInTextOnlyLayout];
}
return CGRectZero;
}
if (self.titleLabel.text.length) {
return [self contentFrameForTitleLabelInTextAndImageLayout];
}
return [self contentFrameForImageViewInImageOnlyLayout];
}

#pragma mark - UIView
Expand All @@ -122,6 +131,18 @@ - (void)layoutSubviews {
}
}

- (CGRect)contentFrameForTitleLabelInTextOnlyLayout {
CGRect contentFrame = UIEdgeInsetsInsetRect(self.bounds, kEdgeInsetsTextOnly);

CGSize contentSize = CGSizeMake(CGRectGetWidth(contentFrame), CGRectGetHeight(contentFrame));
CGSize labelWidthFitSize = [self.titleLabel sizeThatFits:contentSize];
CGSize labelSize =
CGSizeMake(labelWidthFitSize.width, MIN(contentSize.height, labelWidthFitSize.height));
return CGRectMake(CGRectGetMidX(contentFrame) - (labelWidthFitSize.width / 2),
CGRectGetMidY(contentFrame) - (labelWidthFitSize.height / 2), labelSize.width,
labelSize.height);
}

- (void)layoutSubviewsTextOnly {
CGRect contentFrame = UIEdgeInsetsInsetRect(self.bounds, kEdgeInsetsTextOnly);
self.contentView.frame = contentFrame;
Expand All @@ -136,6 +157,18 @@ - (void)layoutSubviewsTextOnly {
CGPointMake(CGRectGetMidX(self.contentView.bounds), CGRectGetMidY(self.contentView.bounds));
}

- (CGRect)contentFrameForImageViewInImageOnlyLayout {
CGRect contentFrame = UIEdgeInsetsInsetRect(self.bounds, kEdgeInsetsImageOnly);

CGSize contentSize = CGSizeMake(CGRectGetWidth(contentFrame), CGRectGetHeight(contentFrame));
CGSize imageIntrinsicContentSize = self.iconImageView.intrinsicContentSize;
CGSize imageFinalSize = CGSizeMake(MIN(contentSize.width, imageIntrinsicContentSize.width),
MIN(contentSize.height, imageIntrinsicContentSize.height));
return CGRectMake(CGRectGetMidX(contentFrame) - (imageFinalSize.width / 2),
CGRectGetMidY(contentFrame) - (imageFinalSize.height / 2), imageFinalSize.width,
imageFinalSize.height);
}

- (void)layoutSubviewsImageOnly {
CGRect contentFrame = UIEdgeInsetsInsetRect(self.bounds, kEdgeInsetsImageOnly);
self.contentView.frame = contentFrame;
Expand All @@ -150,14 +183,36 @@ - (void)layoutSubviewsImageOnly {
CGPointMake(CGRectGetMidX(self.contentView.bounds), CGRectGetMidY(self.contentView.bounds));
}

- (CGRect)contentFrameForTitleLabelInTextAndImageLayout {
CGRect contentFrame = UIEdgeInsetsInsetRect(self.bounds, kEdgeInsetsTextAndImage);

CGSize contentSize = CGSizeMake(CGRectGetWidth(contentFrame), CGRectGetHeight(contentFrame));
CGSize labelSingleLineSize = self.titleLabel.intrinsicContentSize;
CGSize availableIconSize = CGSizeMake(
contentSize.width, contentSize.height - (kImageTitlePadding + labelSingleLineSize.height));

// Position the image, limiting it so that at least 1 line of text remains.
CGSize imageIntrinsicContentSize = self.iconImageView.intrinsicContentSize;
CGSize imageFinalSize =
CGSizeMake(MIN(imageIntrinsicContentSize.width, availableIconSize.width),
MIN(imageIntrinsicContentSize.height, availableIconSize.height));

// Now position the label from the bottom.
CGSize availableLabelSize = CGSizeMake(
contentSize.width, contentSize.height - (imageFinalSize.height + kImageTitlePadding));
CGSize finalLabelSize = [self.titleLabel sizeThatFits:availableLabelSize];
return CGRectMake(CGRectGetMidX(contentFrame) - (finalLabelSize.width / 2),
CGRectGetMinY(contentFrame), finalLabelSize.width, contentSize.height);
}

- (void)layoutSubviewsTextAndImage {
CGRect contentFrame = UIEdgeInsetsInsetRect(self.bounds, kEdgeInsetsTextAndImage);
self.contentView.frame = contentFrame;

CGSize contentSize = CGSizeMake(CGRectGetWidth(contentFrame), CGRectGetHeight(contentFrame));
CGSize labelSingleLineSize = self.titleLabel.intrinsicContentSize;
CGSize availableIconSize = CGSizeMake(
contentSize.width, contentSize.height - kImageTitlePadding - labelSingleLineSize.height);
contentSize.width, contentSize.height - (kImageTitlePadding + labelSingleLineSize.height));

// Position the image, limiting it so that at least 1 line of text remains.
CGSize imageIntrinsicContentSize = self.iconImageView.intrinsicContentSize;
Expand All @@ -169,9 +224,9 @@ - (void)layoutSubviewsTextAndImage {
CGPointMake(CGRectGetMidX(self.contentView.bounds),
CGRectGetMinY(self.contentView.bounds) + imageFinalSize.height / 2);

// Now position the label from the bottom.g
// Now position the label from the bottom.
CGSize availableLabelSize = CGSizeMake(
contentSize.width, contentSize.height - imageFinalSize.height - kImageTitlePadding);
contentSize.width, contentSize.height - (imageFinalSize.height + kImageTitlePadding));
CGSize finalLabelSize = [self.titleLabel sizeThatFits:availableLabelSize];
self.titleLabel.bounds = CGRectMake(0, 0, finalLabelSize.width, finalLabelSize.height);
self.titleLabel.center =
Expand All @@ -197,7 +252,7 @@ - (CGSize)sizeThatFits:(CGSize)size {

- (CGSize)sizeThatFitsTextOnly:(CGSize)size {
CGSize maxSize =
CGSizeMake(kMaxWidth - kEdgeInsetsTextOnly.left - kEdgeInsetsTextOnly.right, CGFLOAT_MAX);
CGSizeMake(kMaxWidth - (kEdgeInsetsTextOnly.left + kEdgeInsetsTextOnly.right), CGFLOAT_MAX);
CGSize labelSize = [self.titleLabel sizeThatFits:maxSize];
return CGSizeMake(
MAX(kMinWidth, labelSize.width + kEdgeInsetsTextOnly.left + kEdgeInsetsTextOnly.right),
Expand All @@ -216,7 +271,7 @@ - (CGSize)sizeThatFitsImageOnly:(CGSize)size {

- (CGSize)sizeThatFitsTextAndImage:(CGSize)size {
CGSize maxSize = CGSizeMake(
kMaxWidth - kEdgeInsetsTextAndImage.left - kEdgeInsetsTextAndImage.right, CGFLOAT_MAX);
kMaxWidth - (kEdgeInsetsTextAndImage.left + kEdgeInsetsTextAndImage.right), CGFLOAT_MAX);
CGSize labelFitSize = [self.titleLabel sizeThatFits:maxSize];
CGSize imageFitSize = self.iconImageView.intrinsicContentSize;
return CGSizeMake(MAX(kMinWidth, MIN(kMaxWidth, kEdgeInsetsTextAndImage.left +
Expand Down
Expand Up @@ -563,4 +563,71 @@ - (void)testRippleAppearanceWhenFullyPressed {
[self generateSnapshotAndVerifyForView:self.itemView];
}

#pragma mark - Selection Indicator Support

- (void)testContentFrameForTextOnly {
// Given
self.itemView.titleLabel.text = @"1";
self.itemView.iconImageView.image = nil;
[self.itemView sizeToFit];
CGRect contentFrame = self.itemView.contentFrame;

// When
UIView *contentFrameOverlayView = [[UIView alloc] init];
contentFrameOverlayView.translatesAutoresizingMaskIntoConstraints = NO;
contentFrameOverlayView.backgroundColor =
[UIColor.blueColor colorWithAlphaComponent:(CGFloat)0.25];
contentFrameOverlayView.bounds =
CGRectMake(0, 0, CGRectGetWidth(contentFrame), CGRectGetHeight(contentFrame));
contentFrameOverlayView.center =
CGPointMake(CGRectGetMidX(contentFrame), CGRectGetMidY(contentFrame));
[self.itemView addSubview:contentFrameOverlayView];

// Then
[self generateSnapshotAndVerifyForView:self.itemView];
}

- (void)testContentFrameForImageOnly {
// Given
self.itemView.titleLabel.text = @"1";
self.itemView.titleLabel.text = nil;
[self.itemView sizeToFit];
CGRect contentFrame = self.itemView.contentFrame;

// When
UIView *contentFrameOverlayView = [[UIView alloc] init];
contentFrameOverlayView.translatesAutoresizingMaskIntoConstraints = NO;
contentFrameOverlayView.backgroundColor =
[UIColor.blueColor colorWithAlphaComponent:(CGFloat)0.25];
contentFrameOverlayView.bounds =
CGRectMake(0, 0, CGRectGetWidth(contentFrame), CGRectGetHeight(contentFrame));
contentFrameOverlayView.center =
CGPointMake(CGRectGetMidX(contentFrame), CGRectGetMidY(contentFrame));
[self.itemView addSubview:contentFrameOverlayView];

// Then
[self generateSnapshotAndVerifyForView:self.itemView];
}

- (void)testContentFrameForTextAndImage {
// Given
self.itemView.titleLabel.text = @"1";
[self.itemView sizeToFit];
CGRect contentFrame = self.itemView.contentFrame;

// When
UIView *contentFrameOverlayView = [[UIView alloc] init];
contentFrameOverlayView.translatesAutoresizingMaskIntoConstraints = NO;
contentFrameOverlayView.backgroundColor =
[UIColor.blueColor colorWithAlphaComponent:(CGFloat)0.25];
contentFrameOverlayView.bounds =
CGRectMake(0, 0, CGRectGetWidth(contentFrame), CGRectGetHeight(contentFrame));
contentFrameOverlayView.center =
CGPointMake(CGRectGetMidX(contentFrame), CGRectGetMidY(contentFrame));
[self.itemView addSubview:contentFrameOverlayView];

// Then
[self generateSnapshotAndVerifyForView:self.itemView];
}

@end
106 changes: 106 additions & 0 deletions components/Tabs/tests/unit/TabBarView/MDCTabBarViewItemViewTests.m
Expand Up @@ -217,4 +217,110 @@ - (void)testSizeThatFitsForShortTitleExpectedImageLargerDimensionsLimitsHeight {
XCTAssertEqualWithAccuracy(fitSize.height, kMinHeightOfTitleAndImageView, 0.001);
}

#pragma mark - -contentFrame

- (void)testContentFrameForTextOnlyViewReturnsTitleLabelFittingFrame {
// Given
MDCTabBarViewItemView *itemView = [[MDCTabBarViewItemView alloc] init];

// When
itemView.titleLabel.text = @"Favorites";
[itemView sizeToFit];

// Then
// Grab the `contentFrame` before layout to be sure it's calculated correctly at any time.
CGRect contentFrame = itemView.contentFrame;
[itemView layoutIfNeeded];
XCTAssertFalse(CGRectEqualToRect(contentFrame, CGRectZero), @"(%@) is equal to (%@))",
NSStringFromCGRect(contentFrame), NSStringFromCGRect(CGRectZero));
XCTAssertEqualWithAccuracy(CGRectGetHeight(contentFrame),
CGRectGetHeight(itemView.titleLabel.bounds), 0.001);
XCTAssertLessThanOrEqual(CGRectGetWidth(contentFrame),
CGRectGetWidth(itemView.titleLabel.bounds));
CGPoint itemViewTopCenter =
CGPointMake(CGRectGetMidX(itemView.bounds), CGRectGetMinY(itemView.bounds));
XCTAssertFalse(CGRectContainsPoint(contentFrame, itemViewTopCenter),
@"(%@) does not contain (%@)", NSStringFromCGRect(contentFrame),
NSStringFromCGPoint(itemViewTopCenter));
CGPoint itemViewMidBounds =
CGPointMake(CGRectGetMidX(itemView.bounds), CGRectGetMidY(itemView.bounds));
XCTAssertTrue(CGRectContainsPoint(contentFrame, itemViewMidBounds), @"(%@) does not contain (%@)",
NSStringFromCGRect(contentFrame), NSStringFromCGPoint(itemViewMidBounds));

CGPoint itemViewBottomCenter =
CGPointMake(CGRectGetMidX(itemView.bounds), CGRectGetMaxY(itemView.bounds));
XCTAssertFalse(CGRectContainsPoint(contentFrame, itemViewBottomCenter),
@"(%@) does not contain (%@)", NSStringFromCGRect(contentFrame),
NSStringFromCGPoint(itemViewBottomCenter));
}

- (void)testContentFrameForIconOnlyViewReturnsIconImageViewFittingFrame {
// Given
MDCTabBarViewItemView *itemView = [[MDCTabBarViewItemView alloc] init];

// When
itemView.iconImageView.image = fakeImage(24, 24);
[itemView sizeToFit];

// Then
// Grab the `contentFrame` before layout to be sure it's calculated correctly at any time.
CGRect contentFrame = itemView.contentFrame;
[itemView layoutIfNeeded];
XCTAssertFalse(CGRectEqualToRect(contentFrame, CGRectZero), @"(%@) is equal to (%@)",
NSStringFromCGRect(contentFrame), NSStringFromCGRect(CGRectZero));
XCTAssertEqualWithAccuracy(CGRectGetHeight(contentFrame),
CGRectGetHeight(itemView.iconImageView.bounds), 0.001);
XCTAssertLessThanOrEqual(CGRectGetWidth(contentFrame),
CGRectGetWidth(itemView.iconImageView.bounds));
CGPoint itemViewTopCenter =
CGPointMake(CGRectGetMidX(itemView.bounds), CGRectGetMinY(itemView.bounds));
XCTAssertFalse(CGRectContainsPoint(contentFrame, itemViewTopCenter),
@"(%@) does not contain (%@)", NSStringFromCGRect(contentFrame),
NSStringFromCGPoint(itemViewTopCenter));
CGPoint itemViewMidBounds =
CGPointMake(CGRectGetMidX(itemView.bounds), CGRectGetMidY(itemView.bounds));
XCTAssertTrue(CGRectContainsPoint(contentFrame, itemViewMidBounds), @"(%@) does not contain (%@)",
NSStringFromCGRect(contentFrame), NSStringFromCGPoint(itemViewMidBounds));

CGPoint itemViewBottomCenter =
CGPointMake(CGRectGetMidX(itemView.bounds), CGRectGetMaxY(itemView.bounds));
XCTAssertFalse(CGRectContainsPoint(contentFrame, itemViewBottomCenter),
@"(%@) does not contain (%@)", NSStringFromCGRect(contentFrame),
NSStringFromCGPoint(itemViewBottomCenter));
}

- (void)testContentFrameForTextAndIconViewReturnsFrameWithTitleLabelWidthAndTitleImageHeight {
// Given
MDCTabBarViewItemView *itemView = [[MDCTabBarViewItemView alloc] init];

// When
itemView.iconImageView.image = fakeImage(24, 24);
itemView.titleLabel.text = @"Favorites";
[itemView sizeToFit];

// Then
// Grab the `contentFrame` before layout to be sure it's calculated correctly at any time.
CGRect contentFrame = itemView.contentFrame;
[itemView layoutIfNeeded];
XCTAssertFalse(CGRectEqualToRect(contentFrame, CGRectZero), @"(%@) is equal to (%@)",
NSStringFromCGRect(contentFrame), NSStringFromCGRect(CGRectZero));
XCTAssertGreaterThan(CGRectGetHeight(contentFrame), CGRectGetHeight(itemView.titleLabel.bounds));
XCTAssertLessThanOrEqual(CGRectGetWidth(contentFrame),
CGRectGetWidth(itemView.titleLabel.bounds));
CGPoint itemViewTopCenter =
CGPointMake(CGRectGetMidX(itemView.bounds), CGRectGetMinY(itemView.bounds));
XCTAssertFalse(CGRectContainsPoint(contentFrame, itemViewTopCenter),
@"(%@) does not contain (%@)", NSStringFromCGRect(contentFrame),
NSStringFromCGPoint(itemViewTopCenter));
CGPoint itemViewMidBounds =
CGPointMake(CGRectGetMidX(itemView.bounds), CGRectGetMidY(itemView.bounds));
XCTAssertTrue(CGRectContainsPoint(contentFrame, itemViewMidBounds), @"(%@) does not contain (%@)",
NSStringFromCGRect(contentFrame), NSStringFromCGPoint(itemViewMidBounds));
CGPoint itemViewBottomCenter =
CGPointMake(CGRectGetMidX(itemView.bounds), CGRectGetMaxY(itemView.bounds));
XCTAssertFalse(CGRectContainsPoint(contentFrame, itemViewBottomCenter),
@"(%@) does not contain (%@)", NSStringFromCGRect(contentFrame),
NSStringFromCGPoint(itemViewBottomCenter));
}

@end
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 0881659

Please sign in to comment.