blob: e2e7f5ab67f7c2ec3b3e5a3c120de989ecb2c668 [file] [log] [blame]
/*
Copyright 2018-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 "MDCSelfSizingStereoCellLayout.h"
#import "MDCSelfSizingStereoCellImageViewVerticalPosition.h"
static const CGFloat kVerticalMarginMin = 8.0;
static const CGFloat kVerticalMarginMax = 16.0;
static const CGFloat kHorizontalMargin = 16.0;
static const CGFloat kImageSideLengthMedium = 40.0;
static const CGFloat kImageSideLengthMax = 56.0;
static const CGFloat kInterLabelVerticalPadding = 6.0;
@interface MDCSelfSizingStereoCellLayout ()
@property(nonatomic, assign) CGFloat cellWidth;
@property(nonatomic, assign) CGFloat calculatedHeight;
@property(nonatomic, assign) CGRect textContainerFrame;
@property(nonatomic, assign) CGRect titleLabelFrame;
@property(nonatomic, assign) CGRect detailLabelFrame;
@property(nonatomic, assign) CGRect leadingImageViewFrame;
@property(nonatomic, assign) CGRect trailingImageViewFrame;
@end
@implementation MDCSelfSizingStereoCellLayout
- (instancetype)initWithLeadingImageView:(UIImageView *)leadingImageView
leadingImageViewVerticalPosition:
(MDCSelfSizingStereoCellImageViewVerticalPosition)leadingImageViewVerticalPosition
trailingImageView:(UIImageView *)trailingImageView
trailingImageViewVerticalPosition:
(MDCSelfSizingStereoCellImageViewVerticalPosition)trailingImageViewVerticalPosition
textContainer:(UIView *)textContainer
titleLabel:(UILabel *)titleLabel
detailLabel:(UILabel *)detailLabel
cellWidth:(CGFloat)cellWidth {
self = [super init];
if (self) {
[self calculateLayoutWithLeadingImageView:leadingImageView
leadingImageViewVerticalPosition:leadingImageViewVerticalPosition
trailingImageView:trailingImageView
trailingImageViewVerticalPosition:trailingImageViewVerticalPosition
textContainer:textContainer
titleLabel:titleLabel
detailLabel:detailLabel
cellWidth:cellWidth];
}
return self;
}
/**
This method calculates the frames for the subviews in the cell. It starts by determining the
leading and trailing image view frames. Then it uses those frames to determine the frame of the
title label, detail label, and the view that contains them. Then, it calculates the height of the
cell. Finally, it vertically centers the iamge views if necessary.
*/
- (void)calculateLayoutWithLeadingImageView:(UIImageView *)leadingImageView
leadingImageViewVerticalPosition:
(MDCSelfSizingStereoCellImageViewVerticalPosition)leadingImageViewVerticalPosition
trailingImageView:(UIImageView *)trailingImageView
trailingImageViewVerticalPosition:
(MDCSelfSizingStereoCellImageViewVerticalPosition)trailingImageViewVerticalPosition
textContainer:(UIView *)textContainer
titleLabel:(UILabel *)titleLabel
detailLabel:(UILabel *)detailLabel
cellWidth:(CGFloat)cellWidth {
// Initially assume an image view frame of .zero.
CGRect leadingImageViewFrame = CGRectZero;
CGSize leadingImageViewSize = [self sizeForImage:leadingImageView.image];
BOOL displaysLeadingImageView = !CGSizeEqualToSize(leadingImageViewSize, CGSizeZero);
if (displaysLeadingImageView) {
// Calculate non-zero image view frame because image exists and has a valid size.
CGFloat leadingImageViewMinX = kHorizontalMargin;
CGFloat leadingImageViewMinY = [self verticalMarginForImageViewOfSize:leadingImageViewSize];
leadingImageViewFrame = CGRectMake(leadingImageViewMinX, leadingImageViewMinY,
leadingImageViewSize.width, leadingImageViewSize.height);
}
// Initially assume an image view frame of .zero.
CGRect trailingImageViewFrame = CGRectZero;
CGSize trailingImageViewSize = [self sizeForImage:trailingImageView.image];
BOOL displaysTrailingImageView = !CGSizeEqualToSize(trailingImageViewSize, CGSizeZero);
if (displaysTrailingImageView) {
// Calculate non-zero image view frame because image exists and has a valid size.
CGFloat trailingImageViewMinX = cellWidth - kHorizontalMargin - trailingImageViewSize.width;
CGFloat trailingImageViewMinY = [self verticalMarginForImageViewOfSize:trailingImageViewSize];
trailingImageViewFrame = CGRectMake(trailingImageViewMinX, trailingImageViewMinY,
trailingImageViewSize.width, trailingImageViewSize.height);
}
// Initialize text-related view frame as .zero.
CGRect titleLabelFrame = CGRectZero;
CGRect detailLabelFrame = CGRectZero;
CGRect textContainerFrame = CGRectZero;
BOOL containsTitleText = titleLabel.text.length > 0;
BOOL containsDetailText = detailLabel.text.length > 0;
if (containsTitleText || containsDetailText) {
// Determine min x of text region
CGFloat textContainerMinX = kHorizontalMargin;
if (displaysLeadingImageView) {
textContainerMinX = CGRectGetMaxX(leadingImageViewFrame) + kHorizontalMargin;
}
// Determine max x of text region
CGFloat textContainerMaxX = cellWidth - kHorizontalMargin;
if (displaysTrailingImageView) {
textContainerMaxX = CGRectGetMinX(trailingImageViewFrame) - kHorizontalMargin;
}
// Begin determining the frame of the view that contains the title and detail labels.
// The final frame of this view must be calculated further down because it depends on the frames
// of the labels it contains.
CGFloat textContainerMinY = kVerticalMarginMax;
CGFloat textContainerWidth = textContainerMaxX - textContainerMinX;
CGFloat textContainerHeight = 0;
CGSize fittingSize = CGSizeMake(textContainerWidth, CGFLOAT_MAX);
// Determine title label size and then frame within container view
CGSize titleSize = [titleLabel sizeThatFits:fittingSize];
titleSize.width = textContainerWidth;
CGFloat titleLabelMinX = 0;
CGFloat titleLabelMinY = 0;
CGPoint titleOrigin = CGPointMake(titleLabelMinX, titleLabelMinY);
titleLabelFrame.origin = titleOrigin;
titleLabelFrame.size = titleSize;
// Determine detail label size and then frame within container view
CGSize detailSize = [detailLabel sizeThatFits:fittingSize];
detailSize.width = textContainerWidth;
CGFloat detailLabelMinX = 0;
CGFloat detailLabelMinY = CGRectGetMaxY(titleLabelFrame);
if (titleLabel.text.length > 0 && detailLabel.text.length > 0) {
detailLabelMinY += kInterLabelVerticalPadding;
}
CGPoint detailOrigin = CGPointMake(detailLabelMinX, detailLabelMinY);
detailLabelFrame.origin = detailOrigin;
detailLabelFrame.size = detailSize;
// Determine title and detail label container view height and then frame
textContainerHeight = CGRectGetMaxY(detailLabelFrame);
CGPoint textContainerOrigin = CGPointMake(textContainerMinX, textContainerMinY);
CGSize textContainerSize = CGSizeMake(textContainerWidth, textContainerHeight);
textContainerFrame.origin = textContainerOrigin;
textContainerFrame.size = textContainerSize;
// Usually the image views are positioned at the top. However, this looks funny if there is only
// one line of text. If there is only one line of text, make it have the same center Y as the
// image views.
BOOL hasOnlyTitleText = containsTitleText && !containsDetailText;
BOOL shouldVerticallyCenterTitleText = hasOnlyTitleText && displaysLeadingImageView;
if (shouldVerticallyCenterTitleText) {
CGFloat leadingImageViewCenterY = CGRectGetMidY(leadingImageViewFrame);
CGFloat textContainerCenterY = CGRectGetMidY(textContainerFrame);
CGFloat difference = textContainerCenterY - leadingImageViewCenterY;
CGRect offsetTextContainerRect = CGRectOffset(textContainerFrame, 0, -difference);
BOOL willExtendPastMargin = offsetTextContainerRect.origin.y < kVerticalMarginMax;
if (!willExtendPastMargin) {
textContainerFrame = offsetTextContainerRect;
}
}
}
// Calculate the height of the cell.
CGFloat calculatedHeight = [self calculateHeightWithLeadingImageViewFrame:leadingImageViewFrame
trailingImageViewFrame:trailingImageViewFrame
textContainerFrame:textContainerFrame];
// Center the image views if the user has specified that they want the image views centered.
if (displaysLeadingImageView &&
leadingImageViewVerticalPosition == MDCSelfSizingStereoCellImageViewVerticalPositionCenter) {
CGFloat verticallyCenteredLeadingImageViewMinY =
(0.5f * calculatedHeight) - (0.5f * leadingImageViewSize.height);
leadingImageViewFrame =
CGRectMake(CGRectGetMinX(leadingImageViewFrame), verticallyCenteredLeadingImageViewMinY,
leadingImageViewSize.width, leadingImageViewSize.height);
}
if (displaysTrailingImageView &&
trailingImageViewVerticalPosition == MDCSelfSizingStereoCellImageViewVerticalPositionCenter) {
CGFloat verticallyCenteredTrailingImageViewMinY =
(0.5f * calculatedHeight) - (0.5f * trailingImageViewSize.height);
trailingImageViewFrame =
CGRectMake(CGRectGetMinX(trailingImageViewFrame), verticallyCenteredTrailingImageViewMinY,
trailingImageViewSize.width, trailingImageViewSize.height);
}
// Set the properties to be read by the cell.
self.textContainerFrame = textContainerFrame;
self.titleLabelFrame = titleLabelFrame;
self.detailLabelFrame = detailLabelFrame;
self.cellWidth = cellWidth;
self.leadingImageViewFrame = leadingImageViewFrame;
self.trailingImageViewFrame = trailingImageViewFrame;
self.calculatedHeight = calculatedHeight;
}
- (CGFloat)calculateHeightWithLeadingImageViewFrame:(CGRect)leadingImageViewFrame
trailingImageViewFrame:(CGRect)trailingImageViewFrame
textContainerFrame:(CGRect)textContainerFrame {
CGFloat maxHeight = 0;
CGFloat leadingImageViewRequiredVerticalSpace = 0;
CGFloat trailingImageViewRequiredVerticalSpace = 0;
CGFloat textContainerRequiredVerticalSpace = 0;
if (!CGRectEqualToRect(leadingImageViewFrame, CGRectZero)) {
leadingImageViewRequiredVerticalSpace =
CGRectGetMaxY(leadingImageViewFrame) +
[self verticalMarginForImageViewOfSize:leadingImageViewFrame.size];
if (leadingImageViewRequiredVerticalSpace > maxHeight) {
maxHeight = leadingImageViewRequiredVerticalSpace;
}
}
if (!CGRectEqualToRect(trailingImageViewFrame, CGRectZero)) {
trailingImageViewRequiredVerticalSpace =
CGRectGetMaxY(trailingImageViewFrame) +
[self verticalMarginForImageViewOfSize:trailingImageViewFrame.size];
if (trailingImageViewRequiredVerticalSpace > maxHeight) {
maxHeight = trailingImageViewRequiredVerticalSpace;
}
}
if (!CGRectEqualToRect(textContainerFrame, CGRectZero)) {
textContainerRequiredVerticalSpace = CGRectGetMaxY(textContainerFrame) + kVerticalMarginMax;
if (textContainerRequiredVerticalSpace > maxHeight) {
maxHeight = textContainerRequiredVerticalSpace;
}
}
CGFloat calculatedHeight = (CGFloat)ceil((double)maxHeight);
return calculatedHeight;
}
- (CGSize)sizeForImage:(UIImage *)image {
CGSize maxSize = CGSizeMake(kImageSideLengthMax, kImageSideLengthMax);
if (!image || image.size.width <= 0 || image.size.height <= 0) {
return CGSizeZero;
} else if (image.size.width > maxSize.width || image.size.height > maxSize.height) {
CGFloat aspectWidth = maxSize.width / image.size.width;
CGFloat aspectHeight = maxSize.height / image.size.height;
CGFloat aspectRatio = MIN(aspectWidth, aspectHeight);
return CGSizeMake(image.size.width * aspectRatio, image.size.height * aspectRatio);
} else {
return image.size;
}
}
- (CGFloat)verticalMarginForImageViewOfSize:(CGSize)size {
if (size.height == 0) {
return 0;
} else if (size.height > 0 && size.height <= kImageSideLengthMedium) {
return kVerticalMarginMax;
} else {
return kVerticalMarginMin;
}
}
@end