jiGuangXieZuo/ProductApp/Pods/UITableView+FDTemplateLayou.../Classes/UITableView+FDTemplateLayou...

267 lines
13 KiB
Mathematica
Raw Normal View History

2024-09-20 18:32:04 +08:00
// The MIT License (MIT)
//
// Copyright (c) 2015-2016 forkingdog ( https://github.com/forkingdog )
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#import "UITableView+FDTemplateLayoutCell.h"
#import <objc/runtime.h>
@implementation UITableView (FDTemplateLayoutCell)
- (CGFloat)fd_systemFittingHeightForConfiguratedCell:(UITableViewCell *)cell {
CGFloat contentViewWidth = CGRectGetWidth(self.frame);
// If a cell has accessory view or system accessory type, its content view's width is smaller
// than cell's by some fixed values.
if (cell.accessoryView) {
contentViewWidth -= 16 + CGRectGetWidth(cell.accessoryView.frame);
} else {
static const CGFloat systemAccessoryWidths[] = {
[UITableViewCellAccessoryNone] = 0,
[UITableViewCellAccessoryDisclosureIndicator] = 34,
[UITableViewCellAccessoryDetailDisclosureButton] = 68,
[UITableViewCellAccessoryCheckmark] = 40,
[UITableViewCellAccessoryDetailButton] = 48
};
contentViewWidth -= systemAccessoryWidths[cell.accessoryType];
}
// If not using auto layout, you have to override "-sizeThatFits:" to provide a fitting size by yourself.
// This is the same height calculation passes used in iOS8 self-sizing cell's implementation.
//
// 1. Try "- systemLayoutSizeFittingSize:" first. (skip this step if 'fd_enforceFrameLayout' set to YES.)
// 2. Warning once if step 1 still returns 0 when using AutoLayout
// 3. Try "- sizeThatFits:" if step 1 returns 0
// 4. Use a valid height or default row height (44) if not exist one
CGFloat fittingHeight = 0;
if (!cell.fd_enforceFrameLayout && contentViewWidth > 0) {
// Add a hard width constraint to make dynamic content views (like labels) expand vertically instead
// of growing horizontally, in a flow-layout manner.
NSLayoutConstraint *widthFenceConstraint = [NSLayoutConstraint constraintWithItem:cell.contentView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:contentViewWidth];
// [bug fix] after iOS 10.3, Auto Layout engine will add an additional 0 width constraint onto cell's content view, to avoid that, we add constraints to content view's left, right, top and bottom.
static BOOL isSystemVersionEqualOrGreaterThen10_2 = NO;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
isSystemVersionEqualOrGreaterThen10_2 = [UIDevice.currentDevice.systemVersion compare:@"10.2" options:NSNumericSearch] != NSOrderedAscending;
});
NSArray<NSLayoutConstraint *> *edgeConstraints;
if (isSystemVersionEqualOrGreaterThen10_2) {
// To avoid confilicts, make width constraint softer than required (1000)
widthFenceConstraint.priority = UILayoutPriorityRequired - 1;
// Build edge constraints
NSLayoutConstraint *leftConstraint = [NSLayoutConstraint constraintWithItem:cell.contentView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:cell attribute:NSLayoutAttributeLeft multiplier:1.0 constant:0];
NSLayoutConstraint *rightConstraint = [NSLayoutConstraint constraintWithItem:cell.contentView attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:cell attribute:NSLayoutAttributeRight multiplier:1.0 constant:0];
NSLayoutConstraint *topConstraint = [NSLayoutConstraint constraintWithItem:cell.contentView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:cell attribute:NSLayoutAttributeTop multiplier:1.0 constant:0];
NSLayoutConstraint *bottomConstraint = [NSLayoutConstraint constraintWithItem:cell.contentView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:cell attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0];
edgeConstraints = @[leftConstraint, rightConstraint, topConstraint, bottomConstraint];
[cell addConstraints:edgeConstraints];
}
[cell.contentView addConstraint:widthFenceConstraint];
// Auto layout engine does its math
fittingHeight = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
// Clean-ups
[cell.contentView removeConstraint:widthFenceConstraint];
if (isSystemVersionEqualOrGreaterThen10_2) {
[cell removeConstraints:edgeConstraints];
}
[self fd_debugLog:[NSString stringWithFormat:@"calculate using system fitting size (AutoLayout) - %@", @(fittingHeight)]];
}
if (fittingHeight == 0) {
#if DEBUG
// Warn if using AutoLayout but get zero height.
if (cell.contentView.constraints.count > 0) {
if (!objc_getAssociatedObject(self, _cmd)) {
NSLog(@"[FDTemplateLayoutCell] Warning once only: Cannot get a proper cell height (now 0) from '- systemFittingSize:'(AutoLayout). You should check how constraints are built in cell, making it into 'self-sizing' cell.");
objc_setAssociatedObject(self, _cmd, @YES, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
}
#endif
// Try '- sizeThatFits:' for frame layout.
// Note: fitting height should not include separator view.
fittingHeight = [cell sizeThatFits:CGSizeMake(contentViewWidth, 0)].height;
[self fd_debugLog:[NSString stringWithFormat:@"calculate using sizeThatFits - %@", @(fittingHeight)]];
}
// Still zero height after all above.
if (fittingHeight == 0) {
// Use default row height.
fittingHeight = 44;
}
// Add 1px extra space for separator line if needed, simulating default UITableViewCell.
if (self.separatorStyle != UITableViewCellSeparatorStyleNone) {
fittingHeight += 1.0 / [UIScreen mainScreen].scale;
}
return fittingHeight;
}
- (__kindof UITableViewCell *)fd_templateCellForReuseIdentifier:(NSString *)identifier {
NSAssert(identifier.length > 0, @"Expect a valid identifier - %@", identifier);
NSMutableDictionary<NSString *, UITableViewCell *> *templateCellsByIdentifiers = objc_getAssociatedObject(self, _cmd);
if (!templateCellsByIdentifiers) {
templateCellsByIdentifiers = @{}.mutableCopy;
objc_setAssociatedObject(self, _cmd, templateCellsByIdentifiers, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
UITableViewCell *templateCell = templateCellsByIdentifiers[identifier];
if (!templateCell) {
templateCell = [self dequeueReusableCellWithIdentifier:identifier];
NSAssert(templateCell != nil, @"Cell must be registered to table view for identifier - %@", identifier);
templateCell.fd_isTemplateLayoutCell = YES;
templateCell.contentView.translatesAutoresizingMaskIntoConstraints = NO;
templateCellsByIdentifiers[identifier] = templateCell;
[self fd_debugLog:[NSString stringWithFormat:@"layout cell created - %@", identifier]];
}
return templateCell;
}
- (CGFloat)fd_heightForCellWithIdentifier:(NSString *)identifier configuration:(void (^)(id cell))configuration {
if (!identifier) {
return 0;
}
UITableViewCell *templateLayoutCell = [self fd_templateCellForReuseIdentifier:identifier];
// Manually calls to ensure consistent behavior with actual cells. (that are displayed on screen)
[templateLayoutCell prepareForReuse];
// Customize and provide content for our template cell.
if (configuration) {
configuration(templateLayoutCell);
}
return [self fd_systemFittingHeightForConfiguratedCell:templateLayoutCell];
}
- (CGFloat)fd_heightForCellWithIdentifier:(NSString *)identifier cacheByIndexPath:(NSIndexPath *)indexPath configuration:(void (^)(id cell))configuration {
if (!identifier || !indexPath) {
return 0;
}
// Hit cache
if ([self.fd_indexPathHeightCache existsHeightAtIndexPath:indexPath]) {
[self fd_debugLog:[NSString stringWithFormat:@"hit cache by index path[%@:%@] - %@", @(indexPath.section), @(indexPath.row), @([self.fd_indexPathHeightCache heightForIndexPath:indexPath])]];
return [self.fd_indexPathHeightCache heightForIndexPath:indexPath];
}
CGFloat height = [self fd_heightForCellWithIdentifier:identifier configuration:configuration];
[self.fd_indexPathHeightCache cacheHeight:height byIndexPath:indexPath];
[self fd_debugLog:[NSString stringWithFormat: @"cached by index path[%@:%@] - %@", @(indexPath.section), @(indexPath.row), @(height)]];
return height;
}
- (CGFloat)fd_heightForCellWithIdentifier:(NSString *)identifier cacheByKey:(id<NSCopying>)key configuration:(void (^)(id cell))configuration {
if (!identifier || !key) {
return 0;
}
// Hit cache
if ([self.fd_keyedHeightCache existsHeightForKey:key]) {
CGFloat cachedHeight = [self.fd_keyedHeightCache heightForKey:key];
[self fd_debugLog:[NSString stringWithFormat:@"hit cache by key[%@] - %@", key, @(cachedHeight)]];
return cachedHeight;
}
CGFloat height = [self fd_heightForCellWithIdentifier:identifier configuration:configuration];
[self.fd_keyedHeightCache cacheHeight:height byKey:key];
[self fd_debugLog:[NSString stringWithFormat:@"cached by key[%@] - %@", key, @(height)]];
return height;
}
@end
@implementation UITableView (FDTemplateLayoutHeaderFooterView)
- (__kindof UITableViewHeaderFooterView *)fd_templateHeaderFooterViewForReuseIdentifier:(NSString *)identifier {
NSAssert(identifier.length > 0, @"Expect a valid identifier - %@", identifier);
NSMutableDictionary<NSString *, UITableViewHeaderFooterView *> *templateHeaderFooterViews = objc_getAssociatedObject(self, _cmd);
if (!templateHeaderFooterViews) {
templateHeaderFooterViews = @{}.mutableCopy;
objc_setAssociatedObject(self, _cmd, templateHeaderFooterViews, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
UITableViewHeaderFooterView *templateHeaderFooterView = templateHeaderFooterViews[identifier];
if (!templateHeaderFooterView) {
templateHeaderFooterView = [self dequeueReusableHeaderFooterViewWithIdentifier:identifier];
NSAssert(templateHeaderFooterView != nil, @"HeaderFooterView must be registered to table view for identifier - %@", identifier);
templateHeaderFooterView.contentView.translatesAutoresizingMaskIntoConstraints = NO;
templateHeaderFooterViews[identifier] = templateHeaderFooterView;
[self fd_debugLog:[NSString stringWithFormat:@"layout header footer view created - %@", identifier]];
}
return templateHeaderFooterView;
}
- (CGFloat)fd_heightForHeaderFooterViewWithIdentifier:(NSString *)identifier configuration:(void (^)(id))configuration {
UITableViewHeaderFooterView *templateHeaderFooterView = [self fd_templateHeaderFooterViewForReuseIdentifier:identifier];
NSLayoutConstraint *widthFenceConstraint = [NSLayoutConstraint constraintWithItem:templateHeaderFooterView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:CGRectGetWidth(self.frame)];
[templateHeaderFooterView addConstraint:widthFenceConstraint];
CGFloat fittingHeight = [templateHeaderFooterView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
[templateHeaderFooterView removeConstraint:widthFenceConstraint];
if (fittingHeight == 0) {
fittingHeight = [templateHeaderFooterView sizeThatFits:CGSizeMake(CGRectGetWidth(self.frame), 0)].height;
}
return fittingHeight;
}
@end
@implementation UITableViewCell (FDTemplateLayoutCell)
- (BOOL)fd_isTemplateLayoutCell {
return [objc_getAssociatedObject(self, _cmd) boolValue];
}
- (void)setFd_isTemplateLayoutCell:(BOOL)isTemplateLayoutCell {
objc_setAssociatedObject(self, @selector(fd_isTemplateLayoutCell), @(isTemplateLayoutCell), OBJC_ASSOCIATION_RETAIN);
}
- (BOOL)fd_enforceFrameLayout {
return [objc_getAssociatedObject(self, _cmd) boolValue];
}
- (void)setFd_enforceFrameLayout:(BOOL)enforceFrameLayout {
objc_setAssociatedObject(self, @selector(fd_enforceFrameLayout), @(enforceFrameLayout), OBJC_ASSOCIATION_RETAIN);
}
@end