Skip to content

Commit db10757

Browse files
committed
Merge branch 'master' into pr/92
# Conflicts: # FDTemplateCell.xcworkspace/contents.xcworkspacedata
2 parents d6a20df + d702633 commit db10757

18 files changed

Lines changed: 805 additions & 616 deletions
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// The MIT License (MIT)
2+
//
3+
// Copyright (c) 2015-2016 forkingdog ( https://github.com/forkingdog )
4+
//
5+
// Permission is hereby granted, free of charge, to any person obtaining a copy
6+
// of this software and associated documentation files (the "Software"), to deal
7+
// in the Software without restriction, including without limitation the rights
8+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
// copies of the Software, and to permit persons to whom the Software is
10+
// furnished to do so, subject to the following conditions:
11+
//
12+
// The above copyright notice and this permission notice shall be included in all
13+
// copies or substantial portions of the Software.
14+
//
15+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
// SOFTWARE.
22+
23+
#import <UIKit/UIKit.h>
24+
25+
@interface FDIndexPathHeightCache : NSObject
26+
27+
// Enable automatically if you're using index path driven height cache
28+
@property (nonatomic, assign) BOOL automaticallyInvalidateEnabled;
29+
30+
// Height cache
31+
- (BOOL)existsHeightAtIndexPath:(NSIndexPath *)indexPath;
32+
- (void)cacheHeight:(CGFloat)height byIndexPath:(NSIndexPath *)indexPath;
33+
- (CGFloat)heightForIndexPath:(NSIndexPath *)indexPath;
34+
- (void)invalidateHeightAtIndexPath:(NSIndexPath *)indexPath;
35+
- (void)invalidateAllHeightCache;
36+
37+
@end
38+
39+
@interface UITableView (FDIndexPathHeightCache)
40+
/// Height cache by index path. Generally, you don't need to use it directly.
41+
@property (nonatomic, strong, readonly) FDIndexPathHeightCache *fd_indexPathHeightCache;
42+
@end
43+
44+
@interface UITableView (FDIndexPathHeightCacheInvalidation)
45+
/// Call this method when you want to reload data but don't want to invalidate
46+
/// all height cache by index path, for example, load more data at the bottom of
47+
/// table view.
48+
- (void)fd_reloadDataWithoutInvalidateIndexPathHeightCache;
49+
@end
Lines changed: 289 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,289 @@
1+
// The MIT License (MIT)
2+
//
3+
// Copyright (c) 2015-2016 forkingdog ( https://github.com/forkingdog )
4+
//
5+
// Permission is hereby granted, free of charge, to any person obtaining a copy
6+
// of this software and associated documentation files (the "Software"), to deal
7+
// in the Software without restriction, including without limitation the rights
8+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
// copies of the Software, and to permit persons to whom the Software is
10+
// furnished to do so, subject to the following conditions:
11+
//
12+
// The above copyright notice and this permission notice shall be included in all
13+
// copies or substantial portions of the Software.
14+
//
15+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
// SOFTWARE.
22+
23+
#import "UITableView+FDIndexPathHeightCache.h"
24+
#import <objc/runtime.h>
25+
26+
typedef NSMutableArray<NSMutableArray<NSNumber *> *> FDIndexPathHeightsBySection;
27+
28+
@interface FDIndexPathHeightCache ()
29+
@property (nonatomic, strong) FDIndexPathHeightsBySection *heightsBySectionForPortrait;
30+
@property (nonatomic, strong) FDIndexPathHeightsBySection *heightsBySectionForLandscape;
31+
@end
32+
33+
@implementation FDIndexPathHeightCache
34+
35+
- (instancetype)init {
36+
self = [super init];
37+
if (self) {
38+
_heightsBySectionForPortrait = [NSMutableArray array];
39+
_heightsBySectionForLandscape = [NSMutableArray array];
40+
}
41+
return self;
42+
}
43+
44+
- (FDIndexPathHeightsBySection *)heightsBySectionForCurrentOrientation {
45+
return UIDeviceOrientationIsPortrait([UIDevice currentDevice].orientation) ? self.heightsBySectionForPortrait: self.heightsBySectionForLandscape;
46+
}
47+
48+
- (void)enumerateAllOrientationsUsingBlock:(void (^)(FDIndexPathHeightsBySection *heightsBySection))block {
49+
block(self.heightsBySectionForPortrait);
50+
block(self.heightsBySectionForLandscape);
51+
}
52+
53+
- (BOOL)existsHeightAtIndexPath:(NSIndexPath *)indexPath {
54+
[self buildCachesAtIndexPathsIfNeeded:@[indexPath]];
55+
NSNumber *number = self.heightsBySectionForCurrentOrientation[indexPath.section][indexPath.row];
56+
return ![number isEqualToNumber:@-1];
57+
}
58+
59+
- (void)cacheHeight:(CGFloat)height byIndexPath:(NSIndexPath *)indexPath {
60+
self.automaticallyInvalidateEnabled = YES;
61+
[self buildCachesAtIndexPathsIfNeeded:@[indexPath]];
62+
self.heightsBySectionForCurrentOrientation[indexPath.section][indexPath.row] = @(height);
63+
}
64+
65+
- (CGFloat)heightForIndexPath:(NSIndexPath *)indexPath {
66+
[self buildCachesAtIndexPathsIfNeeded:@[indexPath]];
67+
NSNumber *number = self.heightsBySectionForCurrentOrientation[indexPath.section][indexPath.row];
68+
#if CGFLOAT_IS_DOUBLE
69+
return number.doubleValue;
70+
#else
71+
return number.floatValue;
72+
#endif
73+
}
74+
75+
- (void)invalidateHeightAtIndexPath:(NSIndexPath *)indexPath {
76+
[self buildCachesAtIndexPathsIfNeeded:@[indexPath]];
77+
[self enumerateAllOrientationsUsingBlock:^(FDIndexPathHeightsBySection *heightsBySection) {
78+
heightsBySection[indexPath.section][indexPath.row] = @-1;
79+
}];
80+
}
81+
82+
- (void)invalidateAllHeightCache {
83+
[self enumerateAllOrientationsUsingBlock:^(FDIndexPathHeightsBySection *heightsBySection) {
84+
[heightsBySection removeAllObjects];
85+
}];
86+
}
87+
88+
- (void)buildCachesAtIndexPathsIfNeeded:(NSArray *)indexPaths {
89+
// Build every section array or row array which is smaller than given index path.
90+
[indexPaths enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, BOOL *stop) {
91+
[self buildSectionsIfNeeded:indexPath.section];
92+
[self buildRowsIfNeeded:indexPath.row inExistSection:indexPath.section];
93+
}];
94+
}
95+
96+
- (void)buildSectionsIfNeeded:(NSInteger)targetSection {
97+
[self enumerateAllOrientationsUsingBlock:^(FDIndexPathHeightsBySection *heightsBySection) {
98+
for (NSInteger section = 0; section <= targetSection; ++section) {
99+
if (section >= heightsBySection.count) {
100+
heightsBySection[section] = [NSMutableArray array];
101+
}
102+
}
103+
}];
104+
}
105+
106+
- (void)buildRowsIfNeeded:(NSInteger)targetRow inExistSection:(NSInteger)section {
107+
[self enumerateAllOrientationsUsingBlock:^(FDIndexPathHeightsBySection *heightsBySection) {
108+
NSMutableArray<NSNumber *> *heightsByRow = heightsBySection[section];
109+
for (NSInteger row = 0; row <= targetRow; ++row) {
110+
if (row >= heightsByRow.count) {
111+
heightsByRow[row] = @-1;
112+
}
113+
}
114+
}];
115+
}
116+
117+
@end
118+
119+
@implementation UITableView (FDIndexPathHeightCache)
120+
121+
- (FDIndexPathHeightCache *)fd_indexPathHeightCache {
122+
FDIndexPathHeightCache *cache = objc_getAssociatedObject(self, _cmd);
123+
if (!cache) {
124+
[self methodSignatureForSelector:nil];
125+
cache = [FDIndexPathHeightCache new];
126+
objc_setAssociatedObject(self, _cmd, cache, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
127+
}
128+
return cache;
129+
}
130+
131+
@end
132+
133+
// We just for forward primary call, in crash report, top most method in stack maybe FD's,
134+
// but it's not our bug, you should check whether your table view's data source and displaying
135+
// cells are not match when reloading.
136+
static void __FD_TEMPLATE_LAYOUT_CELL_PRIMARY_CALL_IF_CRASH_NOT_OUR_BUG__(void(^callout)(void)) {
137+
callout();
138+
}
139+
#define FDPrimaryCall(...) do {__FD_TEMPLATE_LAYOUT_CELL_PRIMARY_CALL_IF_CRASH_NOT_OUR_BUG__(^{__VA_ARGS__});} while(0)
140+
141+
@implementation UITableView (FDIndexPathHeightCacheInvalidation)
142+
143+
- (void)fd_reloadDataWithoutInvalidateIndexPathHeightCache {
144+
FDPrimaryCall([self fd_reloadData];);
145+
}
146+
147+
+ (void)load {
148+
// All methods that trigger height cache's invalidation
149+
SEL selectors[] = {
150+
@selector(reloadData),
151+
@selector(insertSections:withRowAnimation:),
152+
@selector(deleteSections:withRowAnimation:),
153+
@selector(reloadSections:withRowAnimation:),
154+
@selector(moveSection:toSection:),
155+
@selector(insertRowsAtIndexPaths:withRowAnimation:),
156+
@selector(deleteRowsAtIndexPaths:withRowAnimation:),
157+
@selector(reloadRowsAtIndexPaths:withRowAnimation:),
158+
@selector(moveRowAtIndexPath:toIndexPath:)
159+
};
160+
161+
for (NSUInteger index = 0; index < sizeof(selectors) / sizeof(SEL); ++index) {
162+
SEL originalSelector = selectors[index];
163+
SEL swizzledSelector = NSSelectorFromString([@"fd_" stringByAppendingString:NSStringFromSelector(originalSelector)]);
164+
Method originalMethod = class_getInstanceMethod(self, originalSelector);
165+
Method swizzledMethod = class_getInstanceMethod(self, swizzledSelector);
166+
method_exchangeImplementations(originalMethod, swizzledMethod);
167+
}
168+
}
169+
170+
- (void)fd_reloadData {
171+
if (self.fd_indexPathHeightCache.automaticallyInvalidateEnabled) {
172+
[self.fd_indexPathHeightCache enumerateAllOrientationsUsingBlock:^(FDIndexPathHeightsBySection *heightsBySection) {
173+
[heightsBySection removeAllObjects];
174+
}];
175+
}
176+
FDPrimaryCall([self fd_reloadData];);
177+
}
178+
179+
- (void)fd_insertSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation {
180+
if (self.fd_indexPathHeightCache.automaticallyInvalidateEnabled) {
181+
[sections enumerateIndexesUsingBlock:^(NSUInteger section, BOOL *stop) {
182+
[self.fd_indexPathHeightCache buildSectionsIfNeeded:section];
183+
[self.fd_indexPathHeightCache enumerateAllOrientationsUsingBlock:^(FDIndexPathHeightsBySection *heightsBySection) {
184+
[heightsBySection insertObject:[NSMutableArray array] atIndex:section];
185+
}];
186+
}];
187+
}
188+
FDPrimaryCall([self fd_insertSections:sections withRowAnimation:animation];);
189+
}
190+
191+
- (void)fd_deleteSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation {
192+
if (self.fd_indexPathHeightCache.automaticallyInvalidateEnabled) {
193+
[sections enumerateIndexesUsingBlock:^(NSUInteger section, BOOL *stop) {
194+
[self.fd_indexPathHeightCache buildSectionsIfNeeded:section];
195+
[self.fd_indexPathHeightCache enumerateAllOrientationsUsingBlock:^(FDIndexPathHeightsBySection *heightsBySection) {
196+
[heightsBySection removeObjectAtIndex:section];
197+
}];
198+
}];
199+
}
200+
FDPrimaryCall([self fd_deleteSections:sections withRowAnimation:animation];);
201+
}
202+
203+
- (void)fd_reloadSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation {
204+
if (self.fd_indexPathHeightCache.automaticallyInvalidateEnabled) {
205+
[sections enumerateIndexesUsingBlock: ^(NSUInteger section, BOOL *stop) {
206+
[self.fd_indexPathHeightCache buildSectionsIfNeeded:section];
207+
[self.fd_indexPathHeightCache enumerateAllOrientationsUsingBlock:^(FDIndexPathHeightsBySection *heightsBySection) {
208+
[heightsBySection[section] removeAllObjects];
209+
}];
210+
211+
}];
212+
}
213+
FDPrimaryCall([self fd_reloadSections:sections withRowAnimation:animation];);
214+
}
215+
216+
- (void)fd_moveSection:(NSInteger)section toSection:(NSInteger)newSection {
217+
if (self.fd_indexPathHeightCache.automaticallyInvalidateEnabled) {
218+
[self.fd_indexPathHeightCache buildSectionsIfNeeded:section];
219+
[self.fd_indexPathHeightCache buildSectionsIfNeeded:newSection];
220+
[self.fd_indexPathHeightCache enumerateAllOrientationsUsingBlock:^(FDIndexPathHeightsBySection *heightsBySection) {
221+
[heightsBySection exchangeObjectAtIndex:section withObjectAtIndex:newSection];
222+
}];
223+
}
224+
FDPrimaryCall([self fd_moveSection:section toSection:newSection];);
225+
}
226+
227+
- (void)fd_insertRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation {
228+
if (self.fd_indexPathHeightCache.automaticallyInvalidateEnabled) {
229+
[self.fd_indexPathHeightCache buildCachesAtIndexPathsIfNeeded:indexPaths];
230+
[indexPaths enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, BOOL *stop) {
231+
[self.fd_indexPathHeightCache enumerateAllOrientationsUsingBlock:^(FDIndexPathHeightsBySection *heightsBySection) {
232+
[heightsBySection[indexPath.section] insertObject:@-1 atIndex:indexPath.row];
233+
}];
234+
}];
235+
}
236+
FDPrimaryCall([self fd_insertRowsAtIndexPaths:indexPaths withRowAnimation:animation];);
237+
}
238+
239+
- (void)fd_deleteRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation {
240+
if (self.fd_indexPathHeightCache.automaticallyInvalidateEnabled) {
241+
[self.fd_indexPathHeightCache buildCachesAtIndexPathsIfNeeded:indexPaths];
242+
243+
NSMutableDictionary<NSNumber *, NSMutableIndexSet *> *mutableIndexSetsToRemove = [NSMutableDictionary dictionary];
244+
[indexPaths enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, BOOL *stop) {
245+
NSMutableIndexSet *mutableIndexSet = mutableIndexSetsToRemove[@(indexPath.section)];
246+
if (!mutableIndexSet) {
247+
mutableIndexSet = [NSMutableIndexSet indexSet];
248+
mutableIndexSetsToRemove[@(indexPath.section)] = mutableIndexSet;
249+
}
250+
[mutableIndexSet addIndex:indexPath.row];
251+
}];
252+
253+
[mutableIndexSetsToRemove enumerateKeysAndObjectsUsingBlock:^(NSNumber *key, NSIndexSet *indexSet, BOOL *stop) {
254+
[self.fd_indexPathHeightCache enumerateAllOrientationsUsingBlock:^(FDIndexPathHeightsBySection *heightsBySection) {
255+
[heightsBySection[key.integerValue] removeObjectsAtIndexes:indexSet];
256+
}];
257+
}];
258+
}
259+
FDPrimaryCall([self fd_deleteRowsAtIndexPaths:indexPaths withRowAnimation:animation];);
260+
}
261+
262+
- (void)fd_reloadRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation {
263+
if (self.fd_indexPathHeightCache.automaticallyInvalidateEnabled) {
264+
[self.fd_indexPathHeightCache buildCachesAtIndexPathsIfNeeded:indexPaths];
265+
[indexPaths enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, BOOL *stop) {
266+
[self.fd_indexPathHeightCache enumerateAllOrientationsUsingBlock:^(FDIndexPathHeightsBySection *heightsBySection) {
267+
heightsBySection[indexPath.section][indexPath.row] = @-1;
268+
}];
269+
}];
270+
}
271+
FDPrimaryCall([self fd_reloadRowsAtIndexPaths:indexPaths withRowAnimation:animation];);
272+
}
273+
274+
- (void)fd_moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath {
275+
if (self.fd_indexPathHeightCache.automaticallyInvalidateEnabled) {
276+
[self.fd_indexPathHeightCache buildCachesAtIndexPathsIfNeeded:@[sourceIndexPath, destinationIndexPath]];
277+
[self.fd_indexPathHeightCache enumerateAllOrientationsUsingBlock:^(FDIndexPathHeightsBySection *heightsBySection) {
278+
NSMutableArray<NSNumber *> *sourceRows = heightsBySection[sourceIndexPath.section];
279+
NSMutableArray<NSNumber *> *destinationRows = heightsBySection[destinationIndexPath.section];
280+
NSNumber *sourceValue = sourceRows[sourceIndexPath.row];
281+
NSNumber *destinationValue = destinationRows[destinationIndexPath.row];
282+
sourceRows[sourceIndexPath.row] = destinationValue;
283+
destinationRows[destinationIndexPath.row] = sourceValue;
284+
}];
285+
}
286+
FDPrimaryCall([self fd_moveRowAtIndexPath:sourceIndexPath toIndexPath:destinationIndexPath];);
287+
}
288+
289+
@end
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// The MIT License (MIT)
2+
//
3+
// Copyright (c) 2015-2016 forkingdog ( https://github.com/forkingdog )
4+
//
5+
// Permission is hereby granted, free of charge, to any person obtaining a copy
6+
// of this software and associated documentation files (the "Software"), to deal
7+
// in the Software without restriction, including without limitation the rights
8+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
// copies of the Software, and to permit persons to whom the Software is
10+
// furnished to do so, subject to the following conditions:
11+
//
12+
// The above copyright notice and this permission notice shall be included in all
13+
// copies or substantial portions of the Software.
14+
//
15+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
// SOFTWARE.
22+
23+
#import <UIKit/UIKit.h>
24+
25+
@interface FDKeyedHeightCache : NSObject
26+
27+
- (BOOL)existsHeightForKey:(id<NSCopying>)key;
28+
- (void)cacheHeight:(CGFloat)height byKey:(id<NSCopying>)key;
29+
- (CGFloat)heightForKey:(id<NSCopying>)key;
30+
31+
// Invalidation
32+
- (void)invalidateHeightForKey:(id<NSCopying>)key;
33+
- (void)invalidateAllHeightCache;
34+
@end
35+
36+
@interface UITableView (FDKeyedHeightCache)
37+
38+
/// Height cache by key. Generally, you don't need to use it directly.
39+
@property (nonatomic, strong, readonly) FDKeyedHeightCache *fd_keyedHeightCache;
40+
@end

0 commit comments

Comments
 (0)