Initial commit
This commit is contained in:
+49
@@ -0,0 +1,49 @@
|
||||
// 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 <UIKit/UIKit.h>
|
||||
|
||||
@interface FDIndexPathHeightCache : NSObject
|
||||
|
||||
// Enable automatically if you're using index path driven height cache
|
||||
@property (nonatomic, assign) BOOL automaticallyInvalidateEnabled;
|
||||
|
||||
// Height cache
|
||||
- (BOOL)existsHeightAtIndexPath:(NSIndexPath *)indexPath;
|
||||
- (void)cacheHeight:(CGFloat)height byIndexPath:(NSIndexPath *)indexPath;
|
||||
- (CGFloat)heightForIndexPath:(NSIndexPath *)indexPath;
|
||||
- (void)invalidateHeightAtIndexPath:(NSIndexPath *)indexPath;
|
||||
- (void)invalidateAllHeightCache;
|
||||
|
||||
@end
|
||||
|
||||
@interface UITableView (FDIndexPathHeightCache)
|
||||
/// Height cache by index path. Generally, you don't need to use it directly.
|
||||
@property (nonatomic, strong, readonly) FDIndexPathHeightCache *fd_indexPathHeightCache;
|
||||
@end
|
||||
|
||||
@interface UITableView (FDIndexPathHeightCacheInvalidation)
|
||||
/// Call this method when you want to reload data but don't want to invalidate
|
||||
/// all height cache by index path, for example, load more data at the bottom of
|
||||
/// table view.
|
||||
- (void)fd_reloadDataWithoutInvalidateIndexPathHeightCache;
|
||||
@end
|
||||
+288
@@ -0,0 +1,288 @@
|
||||
// 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+FDIndexPathHeightCache.h"
|
||||
#import <objc/runtime.h>
|
||||
|
||||
typedef NSMutableArray<NSMutableArray<NSNumber *> *> FDIndexPathHeightsBySection;
|
||||
|
||||
@interface FDIndexPathHeightCache ()
|
||||
@property (nonatomic, strong) FDIndexPathHeightsBySection *heightsBySectionForPortrait;
|
||||
@property (nonatomic, strong) FDIndexPathHeightsBySection *heightsBySectionForLandscape;
|
||||
@end
|
||||
|
||||
@implementation FDIndexPathHeightCache
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_heightsBySectionForPortrait = [NSMutableArray array];
|
||||
_heightsBySectionForLandscape = [NSMutableArray array];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (FDIndexPathHeightsBySection *)heightsBySectionForCurrentOrientation {
|
||||
return UIDeviceOrientationIsPortrait([UIDevice currentDevice].orientation) ? self.heightsBySectionForPortrait: self.heightsBySectionForLandscape;
|
||||
}
|
||||
|
||||
- (void)enumerateAllOrientationsUsingBlock:(void (^)(FDIndexPathHeightsBySection *heightsBySection))block {
|
||||
block(self.heightsBySectionForPortrait);
|
||||
block(self.heightsBySectionForLandscape);
|
||||
}
|
||||
|
||||
- (BOOL)existsHeightAtIndexPath:(NSIndexPath *)indexPath {
|
||||
[self buildCachesAtIndexPathsIfNeeded:@[indexPath]];
|
||||
NSNumber *number = self.heightsBySectionForCurrentOrientation[indexPath.section][indexPath.row];
|
||||
return ![number isEqualToNumber:@-1];
|
||||
}
|
||||
|
||||
- (void)cacheHeight:(CGFloat)height byIndexPath:(NSIndexPath *)indexPath {
|
||||
self.automaticallyInvalidateEnabled = YES;
|
||||
[self buildCachesAtIndexPathsIfNeeded:@[indexPath]];
|
||||
self.heightsBySectionForCurrentOrientation[indexPath.section][indexPath.row] = @(height);
|
||||
}
|
||||
|
||||
- (CGFloat)heightForIndexPath:(NSIndexPath *)indexPath {
|
||||
[self buildCachesAtIndexPathsIfNeeded:@[indexPath]];
|
||||
NSNumber *number = self.heightsBySectionForCurrentOrientation[indexPath.section][indexPath.row];
|
||||
#if CGFLOAT_IS_DOUBLE
|
||||
return number.doubleValue;
|
||||
#else
|
||||
return number.floatValue;
|
||||
#endif
|
||||
}
|
||||
|
||||
- (void)invalidateHeightAtIndexPath:(NSIndexPath *)indexPath {
|
||||
[self buildCachesAtIndexPathsIfNeeded:@[indexPath]];
|
||||
[self enumerateAllOrientationsUsingBlock:^(FDIndexPathHeightsBySection *heightsBySection) {
|
||||
heightsBySection[indexPath.section][indexPath.row] = @-1;
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)invalidateAllHeightCache {
|
||||
[self enumerateAllOrientationsUsingBlock:^(FDIndexPathHeightsBySection *heightsBySection) {
|
||||
[heightsBySection removeAllObjects];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)buildCachesAtIndexPathsIfNeeded:(NSArray *)indexPaths {
|
||||
// Build every section array or row array which is smaller than given index path.
|
||||
[indexPaths enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, BOOL *stop) {
|
||||
[self buildSectionsIfNeeded:indexPath.section];
|
||||
[self buildRowsIfNeeded:indexPath.row inExistSection:indexPath.section];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)buildSectionsIfNeeded:(NSInteger)targetSection {
|
||||
[self enumerateAllOrientationsUsingBlock:^(FDIndexPathHeightsBySection *heightsBySection) {
|
||||
for (NSInteger section = 0; section <= targetSection; ++section) {
|
||||
if (section >= heightsBySection.count) {
|
||||
heightsBySection[section] = [NSMutableArray array];
|
||||
}
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)buildRowsIfNeeded:(NSInteger)targetRow inExistSection:(NSInteger)section {
|
||||
[self enumerateAllOrientationsUsingBlock:^(FDIndexPathHeightsBySection *heightsBySection) {
|
||||
NSMutableArray<NSNumber *> *heightsByRow = heightsBySection[section];
|
||||
for (NSInteger row = 0; row <= targetRow; ++row) {
|
||||
if (row >= heightsByRow.count) {
|
||||
heightsByRow[row] = @-1;
|
||||
}
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation UITableView (FDIndexPathHeightCache)
|
||||
|
||||
- (FDIndexPathHeightCache *)fd_indexPathHeightCache {
|
||||
FDIndexPathHeightCache *cache = objc_getAssociatedObject(self, _cmd);
|
||||
if (!cache) {
|
||||
cache = [FDIndexPathHeightCache new];
|
||||
objc_setAssociatedObject(self, _cmd, cache, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
||||
}
|
||||
return cache;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
// We just forward primary call, in crash report, top most method in stack maybe FD's,
|
||||
// but it's really not our bug, you should check whether your table view's data source and
|
||||
// displaying cells are not matched when reloading.
|
||||
static void __FD_TEMPLATE_LAYOUT_CELL_PRIMARY_CALL_IF_CRASH_NOT_OUR_BUG__(void (^callout)(void)) {
|
||||
callout();
|
||||
}
|
||||
#define FDPrimaryCall(...) do {__FD_TEMPLATE_LAYOUT_CELL_PRIMARY_CALL_IF_CRASH_NOT_OUR_BUG__(^{__VA_ARGS__});} while(0)
|
||||
|
||||
@implementation UITableView (FDIndexPathHeightCacheInvalidation)
|
||||
|
||||
- (void)fd_reloadDataWithoutInvalidateIndexPathHeightCache {
|
||||
FDPrimaryCall([self fd_reloadData];);
|
||||
}
|
||||
|
||||
+ (void)load {
|
||||
// All methods that trigger height cache's invalidation
|
||||
SEL selectors[] = {
|
||||
@selector(reloadData),
|
||||
@selector(insertSections:withRowAnimation:),
|
||||
@selector(deleteSections:withRowAnimation:),
|
||||
@selector(reloadSections:withRowAnimation:),
|
||||
@selector(moveSection:toSection:),
|
||||
@selector(insertRowsAtIndexPaths:withRowAnimation:),
|
||||
@selector(deleteRowsAtIndexPaths:withRowAnimation:),
|
||||
@selector(reloadRowsAtIndexPaths:withRowAnimation:),
|
||||
@selector(moveRowAtIndexPath:toIndexPath:)
|
||||
};
|
||||
|
||||
for (NSUInteger index = 0; index < sizeof(selectors) / sizeof(SEL); ++index) {
|
||||
SEL originalSelector = selectors[index];
|
||||
SEL swizzledSelector = NSSelectorFromString([@"fd_" stringByAppendingString:NSStringFromSelector(originalSelector)]);
|
||||
Method originalMethod = class_getInstanceMethod(self, originalSelector);
|
||||
Method swizzledMethod = class_getInstanceMethod(self, swizzledSelector);
|
||||
method_exchangeImplementations(originalMethod, swizzledMethod);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)fd_reloadData {
|
||||
if (self.fd_indexPathHeightCache.automaticallyInvalidateEnabled) {
|
||||
[self.fd_indexPathHeightCache enumerateAllOrientationsUsingBlock:^(FDIndexPathHeightsBySection *heightsBySection) {
|
||||
[heightsBySection removeAllObjects];
|
||||
}];
|
||||
}
|
||||
FDPrimaryCall([self fd_reloadData];);
|
||||
}
|
||||
|
||||
- (void)fd_insertSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation {
|
||||
if (self.fd_indexPathHeightCache.automaticallyInvalidateEnabled) {
|
||||
[sections enumerateIndexesUsingBlock:^(NSUInteger section, BOOL *stop) {
|
||||
[self.fd_indexPathHeightCache buildSectionsIfNeeded:section];
|
||||
[self.fd_indexPathHeightCache enumerateAllOrientationsUsingBlock:^(FDIndexPathHeightsBySection *heightsBySection) {
|
||||
[heightsBySection insertObject:[NSMutableArray array] atIndex:section];
|
||||
}];
|
||||
}];
|
||||
}
|
||||
FDPrimaryCall([self fd_insertSections:sections withRowAnimation:animation];);
|
||||
}
|
||||
|
||||
- (void)fd_deleteSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation {
|
||||
if (self.fd_indexPathHeightCache.automaticallyInvalidateEnabled) {
|
||||
[sections enumerateIndexesUsingBlock:^(NSUInteger section, BOOL *stop) {
|
||||
[self.fd_indexPathHeightCache buildSectionsIfNeeded:section];
|
||||
[self.fd_indexPathHeightCache enumerateAllOrientationsUsingBlock:^(FDIndexPathHeightsBySection *heightsBySection) {
|
||||
[heightsBySection removeObjectAtIndex:section];
|
||||
}];
|
||||
}];
|
||||
}
|
||||
FDPrimaryCall([self fd_deleteSections:sections withRowAnimation:animation];);
|
||||
}
|
||||
|
||||
- (void)fd_reloadSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation {
|
||||
if (self.fd_indexPathHeightCache.automaticallyInvalidateEnabled) {
|
||||
[sections enumerateIndexesUsingBlock: ^(NSUInteger section, BOOL *stop) {
|
||||
[self.fd_indexPathHeightCache buildSectionsIfNeeded:section];
|
||||
[self.fd_indexPathHeightCache enumerateAllOrientationsUsingBlock:^(FDIndexPathHeightsBySection *heightsBySection) {
|
||||
[heightsBySection[section] removeAllObjects];
|
||||
}];
|
||||
|
||||
}];
|
||||
}
|
||||
FDPrimaryCall([self fd_reloadSections:sections withRowAnimation:animation];);
|
||||
}
|
||||
|
||||
- (void)fd_moveSection:(NSInteger)section toSection:(NSInteger)newSection {
|
||||
if (self.fd_indexPathHeightCache.automaticallyInvalidateEnabled) {
|
||||
[self.fd_indexPathHeightCache buildSectionsIfNeeded:section];
|
||||
[self.fd_indexPathHeightCache buildSectionsIfNeeded:newSection];
|
||||
[self.fd_indexPathHeightCache enumerateAllOrientationsUsingBlock:^(FDIndexPathHeightsBySection *heightsBySection) {
|
||||
[heightsBySection exchangeObjectAtIndex:section withObjectAtIndex:newSection];
|
||||
}];
|
||||
}
|
||||
FDPrimaryCall([self fd_moveSection:section toSection:newSection];);
|
||||
}
|
||||
|
||||
- (void)fd_insertRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation {
|
||||
if (self.fd_indexPathHeightCache.automaticallyInvalidateEnabled) {
|
||||
[self.fd_indexPathHeightCache buildCachesAtIndexPathsIfNeeded:indexPaths];
|
||||
[indexPaths enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, BOOL *stop) {
|
||||
[self.fd_indexPathHeightCache enumerateAllOrientationsUsingBlock:^(FDIndexPathHeightsBySection *heightsBySection) {
|
||||
[heightsBySection[indexPath.section] insertObject:@-1 atIndex:indexPath.row];
|
||||
}];
|
||||
}];
|
||||
}
|
||||
FDPrimaryCall([self fd_insertRowsAtIndexPaths:indexPaths withRowAnimation:animation];);
|
||||
}
|
||||
|
||||
- (void)fd_deleteRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation {
|
||||
if (self.fd_indexPathHeightCache.automaticallyInvalidateEnabled) {
|
||||
[self.fd_indexPathHeightCache buildCachesAtIndexPathsIfNeeded:indexPaths];
|
||||
|
||||
NSMutableDictionary<NSNumber *, NSMutableIndexSet *> *mutableIndexSetsToRemove = [NSMutableDictionary dictionary];
|
||||
[indexPaths enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, BOOL *stop) {
|
||||
NSMutableIndexSet *mutableIndexSet = mutableIndexSetsToRemove[@(indexPath.section)];
|
||||
if (!mutableIndexSet) {
|
||||
mutableIndexSet = [NSMutableIndexSet indexSet];
|
||||
mutableIndexSetsToRemove[@(indexPath.section)] = mutableIndexSet;
|
||||
}
|
||||
[mutableIndexSet addIndex:indexPath.row];
|
||||
}];
|
||||
|
||||
[mutableIndexSetsToRemove enumerateKeysAndObjectsUsingBlock:^(NSNumber *key, NSIndexSet *indexSet, BOOL *stop) {
|
||||
[self.fd_indexPathHeightCache enumerateAllOrientationsUsingBlock:^(FDIndexPathHeightsBySection *heightsBySection) {
|
||||
[heightsBySection[key.integerValue] removeObjectsAtIndexes:indexSet];
|
||||
}];
|
||||
}];
|
||||
}
|
||||
FDPrimaryCall([self fd_deleteRowsAtIndexPaths:indexPaths withRowAnimation:animation];);
|
||||
}
|
||||
|
||||
- (void)fd_reloadRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation {
|
||||
if (self.fd_indexPathHeightCache.automaticallyInvalidateEnabled) {
|
||||
[self.fd_indexPathHeightCache buildCachesAtIndexPathsIfNeeded:indexPaths];
|
||||
[indexPaths enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, BOOL *stop) {
|
||||
[self.fd_indexPathHeightCache enumerateAllOrientationsUsingBlock:^(FDIndexPathHeightsBySection *heightsBySection) {
|
||||
heightsBySection[indexPath.section][indexPath.row] = @-1;
|
||||
}];
|
||||
}];
|
||||
}
|
||||
FDPrimaryCall([self fd_reloadRowsAtIndexPaths:indexPaths withRowAnimation:animation];);
|
||||
}
|
||||
|
||||
- (void)fd_moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath {
|
||||
if (self.fd_indexPathHeightCache.automaticallyInvalidateEnabled) {
|
||||
[self.fd_indexPathHeightCache buildCachesAtIndexPathsIfNeeded:@[sourceIndexPath, destinationIndexPath]];
|
||||
[self.fd_indexPathHeightCache enumerateAllOrientationsUsingBlock:^(FDIndexPathHeightsBySection *heightsBySection) {
|
||||
NSMutableArray<NSNumber *> *sourceRows = heightsBySection[sourceIndexPath.section];
|
||||
NSMutableArray<NSNumber *> *destinationRows = heightsBySection[destinationIndexPath.section];
|
||||
NSNumber *sourceValue = sourceRows[sourceIndexPath.row];
|
||||
NSNumber *destinationValue = destinationRows[destinationIndexPath.row];
|
||||
sourceRows[sourceIndexPath.row] = destinationValue;
|
||||
destinationRows[destinationIndexPath.row] = sourceValue;
|
||||
}];
|
||||
}
|
||||
FDPrimaryCall([self fd_moveRowAtIndexPath:sourceIndexPath toIndexPath:destinationIndexPath];);
|
||||
}
|
||||
|
||||
@end
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
// 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 <UIKit/UIKit.h>
|
||||
|
||||
@interface FDKeyedHeightCache : NSObject
|
||||
|
||||
- (BOOL)existsHeightForKey:(id<NSCopying>)key;
|
||||
- (void)cacheHeight:(CGFloat)height byKey:(id<NSCopying>)key;
|
||||
- (CGFloat)heightForKey:(id<NSCopying>)key;
|
||||
|
||||
// Invalidation
|
||||
- (void)invalidateHeightForKey:(id<NSCopying>)key;
|
||||
- (void)invalidateAllHeightCache;
|
||||
@end
|
||||
|
||||
@interface UITableView (FDKeyedHeightCache)
|
||||
|
||||
/// Height cache by key. Generally, you don't need to use it directly.
|
||||
@property (nonatomic, strong, readonly) FDKeyedHeightCache *fd_keyedHeightCache;
|
||||
@end
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
// 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+FDKeyedHeightCache.h"
|
||||
#import <objc/runtime.h>
|
||||
|
||||
@interface FDKeyedHeightCache ()
|
||||
@property (nonatomic, strong) NSMutableDictionary<id<NSCopying>, NSNumber *> *mutableHeightsByKeyForPortrait;
|
||||
@property (nonatomic, strong) NSMutableDictionary<id<NSCopying>, NSNumber *> *mutableHeightsByKeyForLandscape;
|
||||
@end
|
||||
|
||||
@implementation FDKeyedHeightCache
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_mutableHeightsByKeyForPortrait = [NSMutableDictionary dictionary];
|
||||
_mutableHeightsByKeyForLandscape = [NSMutableDictionary dictionary];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSMutableDictionary<id<NSCopying>, NSNumber *> *)mutableHeightsByKeyForCurrentOrientation {
|
||||
return UIDeviceOrientationIsPortrait([UIDevice currentDevice].orientation) ? self.mutableHeightsByKeyForPortrait: self.mutableHeightsByKeyForLandscape;
|
||||
}
|
||||
|
||||
- (BOOL)existsHeightForKey:(id<NSCopying>)key {
|
||||
NSNumber *number = self.mutableHeightsByKeyForCurrentOrientation[key];
|
||||
return number && ![number isEqualToNumber:@-1];
|
||||
}
|
||||
|
||||
- (void)cacheHeight:(CGFloat)height byKey:(id<NSCopying>)key {
|
||||
self.mutableHeightsByKeyForCurrentOrientation[key] = @(height);
|
||||
}
|
||||
|
||||
- (CGFloat)heightForKey:(id<NSCopying>)key {
|
||||
#if CGFLOAT_IS_DOUBLE
|
||||
return [self.mutableHeightsByKeyForCurrentOrientation[key] doubleValue];
|
||||
#else
|
||||
return [self.mutableHeightsByKeyForCurrentOrientation[key] floatValue];
|
||||
#endif
|
||||
}
|
||||
|
||||
- (void)invalidateHeightForKey:(id<NSCopying>)key {
|
||||
[self.mutableHeightsByKeyForPortrait removeObjectForKey:key];
|
||||
[self.mutableHeightsByKeyForLandscape removeObjectForKey:key];
|
||||
}
|
||||
|
||||
- (void)invalidateAllHeightCache {
|
||||
[self.mutableHeightsByKeyForPortrait removeAllObjects];
|
||||
[self.mutableHeightsByKeyForLandscape removeAllObjects];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation UITableView (FDKeyedHeightCache)
|
||||
|
||||
- (FDKeyedHeightCache *)fd_keyedHeightCache {
|
||||
FDKeyedHeightCache *cache = objc_getAssociatedObject(self, _cmd);
|
||||
if (!cache) {
|
||||
cache = [FDKeyedHeightCache new];
|
||||
objc_setAssociatedObject(self, _cmd, cache, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
||||
}
|
||||
return cache;
|
||||
}
|
||||
|
||||
@end
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
// 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 <UIKit/UIKit.h>
|
||||
#import "UITableView+FDKeyedHeightCache.h"
|
||||
#import "UITableView+FDIndexPathHeightCache.h"
|
||||
#import "UITableView+FDTemplateLayoutCellDebug.h"
|
||||
|
||||
@interface UITableView (FDTemplateLayoutCell)
|
||||
|
||||
/// Access to internal template layout cell for given reuse identifier.
|
||||
/// Generally, you don't need to know these template layout cells.
|
||||
///
|
||||
/// @param identifier Reuse identifier for cell which must be registered.
|
||||
///
|
||||
- (__kindof UITableViewCell *)fd_templateCellForReuseIdentifier:(NSString *)identifier;
|
||||
|
||||
/// Returns height of cell of type specifed by a reuse identifier and configured
|
||||
/// by the configuration block.
|
||||
///
|
||||
/// The cell would be layed out on a fixed-width, vertically expanding basis with
|
||||
/// respect to its dynamic content, using auto layout. Thus, it is imperative that
|
||||
/// the cell was set up to be self-satisfied, i.e. its content always determines
|
||||
/// its height given the width is equal to the tableview's.
|
||||
///
|
||||
/// @param identifier A string identifier for retrieving and maintaining template
|
||||
/// cells with system's "-dequeueReusableCellWithIdentifier:" call.
|
||||
/// @param configuration An optional block for configuring and providing content
|
||||
/// to the template cell. The configuration should be minimal for scrolling
|
||||
/// performance yet sufficient for calculating cell's height.
|
||||
///
|
||||
- (CGFloat)fd_heightForCellWithIdentifier:(NSString *)identifier configuration:(void (^)(id cell))configuration;
|
||||
|
||||
/// This method does what "-fd_heightForCellWithIdentifier:configuration" does, and
|
||||
/// calculated height will be cached by its index path, returns a cached height
|
||||
/// when needed. Therefore lots of extra height calculations could be saved.
|
||||
///
|
||||
/// No need to worry about invalidating cached heights when data source changes, it
|
||||
/// will be done automatically when you call "-reloadData" or any method that triggers
|
||||
/// UITableView's reloading.
|
||||
///
|
||||
/// @param indexPath where this cell's height cache belongs.
|
||||
///
|
||||
- (CGFloat)fd_heightForCellWithIdentifier:(NSString *)identifier cacheByIndexPath:(NSIndexPath *)indexPath configuration:(void (^)(id cell))configuration;
|
||||
|
||||
/// This method caches height by your model entity's identifier.
|
||||
/// If your model's changed, call "-invalidateHeightForKey:(id <NSCopying>)key" to
|
||||
/// invalidate cache and re-calculate, it's much cheaper and effective than "cacheByIndexPath".
|
||||
///
|
||||
/// @param key model entity's identifier whose data configures a cell.
|
||||
///
|
||||
- (CGFloat)fd_heightForCellWithIdentifier:(NSString *)identifier cacheByKey:(id<NSCopying>)key configuration:(void (^)(id cell))configuration;
|
||||
|
||||
@end
|
||||
|
||||
@interface UITableView (FDTemplateLayoutHeaderFooterView)
|
||||
|
||||
/// Returns header or footer view's height that registered in table view with reuse identifier.
|
||||
///
|
||||
/// Use it after calling "-[UITableView registerNib/Class:forHeaderFooterViewReuseIdentifier]",
|
||||
/// same with "-fd_heightForCellWithIdentifier:configuration:", it will call "-sizeThatFits:" for
|
||||
/// subclass of UITableViewHeaderFooterView which is not using Auto Layout.
|
||||
///
|
||||
- (CGFloat)fd_heightForHeaderFooterViewWithIdentifier:(NSString *)identifier configuration:(void (^)(id headerFooterView))configuration;
|
||||
|
||||
@end
|
||||
|
||||
@interface UITableViewCell (FDTemplateLayoutCell)
|
||||
|
||||
/// Indicate this is a template layout cell for calculation only.
|
||||
/// You may need this when there are non-UI side effects when configure a cell.
|
||||
/// Like:
|
||||
/// - (void)configureCell:(FooCell *)cell atIndexPath:(NSIndexPath *)indexPath {
|
||||
/// cell.entity = [self entityAtIndexPath:indexPath];
|
||||
/// if (!cell.fd_isTemplateLayoutCell) {
|
||||
/// [self notifySomething]; // non-UI side effects
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
@property (nonatomic, assign) BOOL fd_isTemplateLayoutCell;
|
||||
|
||||
/// Enable to enforce this template layout cell to use "frame layout" rather than "auto layout",
|
||||
/// and will ask cell's height by calling "-sizeThatFits:", so you must override this method.
|
||||
/// Use this property only when you want to manually control this template layout cell's height
|
||||
/// calculation mode, default to NO.
|
||||
///
|
||||
@property (nonatomic, assign) BOOL fd_enforceFrameLayout;
|
||||
|
||||
@end
|
||||
+266
@@ -0,0 +1,266 @@
|
||||
// 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
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
// 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 <UIKit/UIKit.h>
|
||||
|
||||
@interface UITableView (FDTemplateLayoutCellDebug)
|
||||
|
||||
/// Helps to debug or inspect what is this "FDTemplateLayoutCell" extention doing,
|
||||
/// turning on to print logs when "creating", "calculating", "precaching" or "hitting cache".
|
||||
///
|
||||
/// Default to NO, log by NSLog.
|
||||
///
|
||||
@property (nonatomic, assign) BOOL fd_debugLogEnabled;
|
||||
|
||||
/// Debug log controlled by "fd_debugLogEnabled".
|
||||
- (void)fd_debugLog:(NSString *)message;
|
||||
|
||||
@end
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
// 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+FDTemplateLayoutCellDebug.h"
|
||||
#import <objc/runtime.h>
|
||||
|
||||
@implementation UITableView (FDTemplateLayoutCellDebug)
|
||||
|
||||
- (BOOL)fd_debugLogEnabled {
|
||||
return [objc_getAssociatedObject(self, _cmd) boolValue];
|
||||
}
|
||||
|
||||
- (void)setFd_debugLogEnabled:(BOOL)debugLogEnabled {
|
||||
objc_setAssociatedObject(self, @selector(fd_debugLogEnabled), @(debugLogEnabled), OBJC_ASSOCIATION_RETAIN);
|
||||
}
|
||||
|
||||
- (void)fd_debugLog:(NSString *)message {
|
||||
if (self.fd_debugLogEnabled) {
|
||||
NSLog(@"** FDTemplateLayoutCell ** %@", message);
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015
|
||||
|
||||
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.
|
||||
|
||||
+154
@@ -0,0 +1,154 @@
|
||||
# UITableView-FDTemplateLayoutCell
|
||||
<img src="https://cloud.githubusercontent.com/assets/219689/7244961/4209de32-e816-11e4-87bc-b161c442d348.png" width="640">
|
||||
|
||||
## Overview
|
||||
Template auto layout cell for **automatically** UITableViewCell height calculating.
|
||||
|
||||

|
||||
|
||||
## Basic usage
|
||||
|
||||
If you have a **self-satisfied** cell, then all you have to do is:
|
||||
|
||||
``` objc
|
||||
#import "UITableView+FDTemplateLayoutCell.h"
|
||||
|
||||
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
return [tableView fd_heightForCellWithIdentifier:@"reuse identifer" configuration:^(id cell) {
|
||||
// Configure this cell with data, same as what you've done in "-tableView:cellForRowAtIndexPath:"
|
||||
// Like:
|
||||
// cell.entity = self.feedEntities[indexPath.row];
|
||||
}];
|
||||
}
|
||||
```
|
||||
|
||||
## Height Caching API
|
||||
|
||||
Since iOS8, `-tableView:heightForRowAtIndexPath:` will be called more times than we expect, we can feel these extra calculations when scrolling. So we provide another API with cache by index path:
|
||||
|
||||
``` objc
|
||||
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
return [tableView fd_heightForCellWithIdentifier:@"identifer" cacheByIndexPath:indexPath configuration:^(id cell) {
|
||||
// configurations
|
||||
}];
|
||||
}
|
||||
```
|
||||
|
||||
Or, if your entity has an unique identifier, use cache by key API:
|
||||
|
||||
``` objc
|
||||
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
Entity *entity = self.entities[indexPath.row];
|
||||
return [tableView fd_heightForCellWithIdentifier:@"identifer" cacheByKey:entity.uid configuration:^(id cell) {
|
||||
// configurations
|
||||
}];
|
||||
}
|
||||
```
|
||||
|
||||
## Frame layout mode
|
||||
|
||||
`FDTemplateLayoutCell` offers 2 modes for asking cell's height.
|
||||
|
||||
1. Auto layout mode using "-systemLayoutSizeFittingSize:"
|
||||
2. Frame layout mode using "-sizeThatFits:"
|
||||
|
||||
Generally, no need to care about modes, it will **automatically** choose a proper mode by whether you have set auto layout constrants on cell's content view. If you want to enforce frame layout mode, enable this property in your cell's configuration block:
|
||||
|
||||
``` objc
|
||||
cell.fd_enforceFrameLayout = YES;
|
||||
```
|
||||
And if you're using frame layout mode, you must override `-sizeThatFits:` in your customized cell and return content view's height (separator excluded)
|
||||
|
||||
```
|
||||
- (CGSize)sizeThatFits:(CGSize)size {
|
||||
return CGSizeMake(size.width, A+B+C+D+E+....);
|
||||
}
|
||||
```
|
||||
|
||||
## Debug log
|
||||
|
||||
Debug log helps to debug or inspect what is this "FDTemplateLayoutCell" extention doing, turning on to print logs when "calculating", "precaching" or "hitting cache".Default to "NO", log by "NSLog".
|
||||
|
||||
``` objc
|
||||
self.tableView.fd_debugLogEnabled = YES;
|
||||
```
|
||||
|
||||
It will print like this:
|
||||
|
||||
``` objc
|
||||
** FDTemplateLayoutCell ** layout cell created - FDFeedCell
|
||||
** FDTemplateLayoutCell ** calculate - [0:0] 233.5
|
||||
** FDTemplateLayoutCell ** calculate - [0:1] 155.5
|
||||
** FDTemplateLayoutCell ** calculate - [0:2] 258
|
||||
** FDTemplateLayoutCell ** calculate - [0:3] 284
|
||||
** FDTemplateLayoutCell ** precached - [0:3] 284
|
||||
** FDTemplateLayoutCell ** calculate - [0:4] 278.5
|
||||
** FDTemplateLayoutCell ** precached - [0:4] 278.5
|
||||
** FDTemplateLayoutCell ** hit cache - [0:3] 284
|
||||
** FDTemplateLayoutCell ** hit cache - [0:4] 278.5
|
||||
** FDTemplateLayoutCell ** hit cache - [0:5] 156
|
||||
** FDTemplateLayoutCell ** hit cache - [0:6] 165
|
||||
```
|
||||
|
||||
## About self-satisfied cell
|
||||
|
||||
a fully **self-satisfied** cell is constrainted by auto layout and each edge("top", "left", "bottom", "right") has at least one layout constraint against it. It's the same concept introduced as "self-sizing cell" in iOS8 using auto layout.
|
||||
|
||||
A bad one :( - missing right and bottom
|
||||

|
||||
|
||||
A good one :)
|
||||

|
||||
|
||||
## Notes
|
||||
|
||||
A template layout cell is created by `-dequeueReusableCellWithIdentifier:` method, it means that you MUST have registered this cell reuse identifier by one of:
|
||||
|
||||
- A prototype cell of UITableView in storyboard.
|
||||
- Use `-registerNib:forCellReuseIdentifier:`
|
||||
- Use `-registerClass:forCellReuseIdentifier:`
|
||||
|
||||
## 如果你在天朝
|
||||
可以看这篇中文博客:
|
||||
[http://blog.sunnyxx.com/2015/05/17/cell-height-calculation/](http://blog.sunnyxx.com/2015/05/17/cell-height-calculation/)
|
||||
|
||||
## Installation
|
||||
|
||||
Latest version: **1.6**
|
||||
|
||||
```
|
||||
pod search UITableView+FDTemplateLayoutCell
|
||||
```
|
||||
If you cannot search out the latest version, try:
|
||||
|
||||
```
|
||||
pod setup
|
||||
```
|
||||
|
||||
## Release Notes
|
||||
|
||||
We recommend to use the latest release in cocoapods.
|
||||
|
||||
- 1.6
|
||||
fix bug in iOS 10
|
||||
|
||||
- 1.4
|
||||
Refactor, add "cacheByKey" mode, bug fixed
|
||||
|
||||
- 1.3
|
||||
Frame layout mode, handle cell's accessory view/type
|
||||
|
||||
- 1.2
|
||||
Precache and auto cache invalidation
|
||||
|
||||
- 1.1
|
||||
Height cache
|
||||
|
||||
- 1.0
|
||||
Basic automatically height calculation
|
||||
|
||||
## License
|
||||
MIT
|
||||
|
||||
|
||||
Reference in New Issue
Block a user