Explorar el Código

修复 UICollectionView 应用 FlowLayout 并使用performBatchUpdates时MJRefreshAutoFooter/MJRefreshBackFooter的位置问题 (#1554)

Jia Song hace 4 años
padre
commit
d81a79ac7c

+ 6 - 1
MJRefresh/Base/MJRefreshAutoFooter.m

@@ -64,8 +64,13 @@ - (void)scrollViewContentSizeDidChange:(NSDictionary *)change
 {
     [super scrollViewContentSizeDidChange:change];
     
+    CGSize size = [change[NSKeyValueChangeNewKey] CGSizeValue];
+    CGFloat contentHeight = size.height == 0 ? self.scrollView.mj_contentH : size.height;
     // 设置位置
-    self.mj_y = self.scrollView.mj_contentH + self.ignoredScrollViewContentInsetBottom;
+    CGFloat y = contentHeight + self.ignoredScrollViewContentInsetBottom;
+    if (self.mj_y != y) {
+        self.mj_y = y;
+    }
 }
 
 - (void)scrollViewContentOffsetDidChange:(NSDictionary *)change

+ 8 - 3
MJRefresh/Base/MJRefreshBackFooter.m

@@ -72,12 +72,17 @@ - (void)scrollViewContentSizeDidChange:(NSDictionary *)change
 {
     [super scrollViewContentSizeDidChange:change];
     
+    CGSize size = [change[NSKeyValueChangeNewKey] CGSizeValue];
+    CGFloat contentHeight = size.height == 0 ? self.scrollView.mj_contentH : size.height;
     // 内容的高度
-    CGFloat contentHeight = self.scrollView.mj_contentH + self.ignoredScrollViewContentInsetBottom;
+    contentHeight += self.ignoredScrollViewContentInsetBottom;
     // 表格的高度
     CGFloat scrollHeight = self.scrollView.mj_h - self.scrollViewOriginalInset.top - self.scrollViewOriginalInset.bottom + self.ignoredScrollViewContentInsetBottom;
-    // 设置位置和尺寸
-    self.mj_y = MAX(contentHeight, scrollHeight);
+    // 设置位置
+    CGFloat y = MAX(contentHeight, scrollHeight);
+    if (self.mj_y != y) {
+        self.mj_y = y;
+    }
 }
 
 - (void)setState:(MJRefreshState)state

+ 34 - 0
MJRefresh/MJRefreshConst.h

@@ -1,6 +1,7 @@
 //  代码地址: https://github.com/CoderMJLee/MJRefresh
 #import <UIKit/UIKit.h>
 #import <objc/message.h>
+#import <objc/runtime.h>
 
 // 弱引用
 #define MJWeakSelf __weak typeof(self) weakSelf = self;
@@ -79,3 +80,36 @@ dispatch_async(dispatch_get_main_queue(), ^{ \
 typeof(weakSelf) self = weakSelf; \
 {x} \
 });
+
+/// 替换方法实现
+/// @param _fromClass 源类
+/// @param _originSelector 源类的 Selector
+/// @param _toClass  目标类
+/// @param _newSelector 目标类的 Selector
+CG_INLINE BOOL MJRefreshExchangeImplementations(
+                                                Class _fromClass, SEL _originSelector,
+                                                Class _toClass, SEL _newSelector) {
+    if (!_fromClass || !_toClass) {
+        return NO;
+    }
+    
+    Method oriMethod = class_getInstanceMethod(_fromClass, _originSelector);
+    Method newMethod = class_getInstanceMethod(_toClass, _newSelector);
+    if (!newMethod) {
+        return NO;
+    }
+    
+    BOOL isAddedMethod = class_addMethod(_fromClass, _originSelector,
+                                         method_getImplementation(newMethod),
+                                         method_getTypeEncoding(newMethod));
+    if (isAddedMethod) {
+        // 如果 class_addMethod 成功了,说明之前 fromClass 里并不存在 originSelector,所以要用一个空的方法代替它,以避免 class_replaceMethod 后,后续 toClass 的这个方法被调用时可能会 crash
+        IMP emptyIMP = imp_implementationWithBlock(^(id selfObject) {});
+        IMP oriMethodIMP = method_getImplementation(oriMethod) ?: emptyIMP;
+        const char *oriMethodTypeEncoding = method_getTypeEncoding(oriMethod) ?: "v@:";
+        class_replaceMethod(_toClass, _newSelector, oriMethodIMP, oriMethodTypeEncoding);
+    } else {
+        method_exchangeImplementations(oriMethod, newMethod);
+    }
+    return YES;
+}

+ 20 - 0
MJRefresh/UICollectionViewLayout+MJRefresh.h

@@ -0,0 +1,20 @@
+//
+//  UICollectionViewLayout+MJRefresh.h
+//  
+//  该类是用来解决 Footer 在底端加载完成后, 仍停留在原处的 bug.
+//  此问题出现在 iOS 14 及以下系统上.
+//  Reference: https://github.com/CoderMJLee/MJRefresh/issues/1552
+//
+//  Created by jiasong on 2021/11/15.
+//  Copyright © 2021 小码哥. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface UICollectionViewLayout (MJRefresh)
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 44 - 0
MJRefresh/UICollectionViewLayout+MJRefresh.m

@@ -0,0 +1,44 @@
+//
+//  UICollectionViewLayout+MJRefresh.m
+//
+//  该类是用来解决 Footer 在底端加载完成后, 仍停留在原处的 bug.
+//  此问题出现在 iOS 14 及以下系统上.
+//  Reference: https://github.com/CoderMJLee/MJRefresh/issues/1552
+//
+//  Created by jiasong on 2021/11/15.
+//  Copyright © 2021 小码哥. All rights reserved.
+//
+
+#import "UICollectionViewLayout+MJRefresh.h"
+#import "MJRefreshConst.h"
+#import "MJRefreshFooter.h"
+
+@implementation UICollectionViewLayout (MJRefresh)
+
++ (void)load {
+    static dispatch_once_t onceToken;
+    dispatch_once(&onceToken, ^{
+        MJRefreshExchangeImplementations(self.class, @selector(finalizeCollectionViewUpdates),
+                                         self.class, @selector(mj_finalizeCollectionViewUpdates));
+    });
+}
+
+- (void)mj_finalizeCollectionViewUpdates {
+    [self mj_finalizeCollectionViewUpdates];
+    
+    __kindof MJRefreshFooter *footer = self.collectionView.mj_footer;
+    CGSize newSize = self.collectionViewContentSize;
+    CGSize oldSize = self.collectionView.contentSize;
+    if (footer != nil && !CGSizeEqualToSize(newSize, oldSize)) {
+        NSDictionary *changed = @{
+            NSKeyValueChangeNewKey: [NSValue valueWithCGSize:newSize],
+            NSKeyValueChangeOldKey: [NSValue valueWithCGSize:oldSize],
+        };
+        [CATransaction begin];
+        [CATransaction setDisableActions:YES];
+        [footer scrollViewContentSizeDidChange:changed];
+        [CATransaction commit];
+    }
+}
+
+@end

+ 14 - 4
MJRefreshExample.xcodeproj/project.pbxproj

@@ -101,6 +101,9 @@
 		2DB2EA281BECBE6700D58F6A /* UIScrollView+MJRefresh.m in Sources */ = {isa = PBXBuildFile; fileRef = 2DB2EA141BECBE6700D58F6A /* UIScrollView+MJRefresh.m */; };
 		2DB2EA291BECBE6700D58F6A /* UIView+MJExtension.m in Sources */ = {isa = PBXBuildFile; fileRef = 2DB2EA161BECBE6700D58F6A /* UIView+MJExtension.m */; };
 		6B8FB1A9250E610D00AF64B7 /* MJHorizontalCollectionViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 6B8FB1A8250E610D00AF64B7 /* MJHorizontalCollectionViewController.m */; };
+		7A698CD2274232A00028A4DD /* UICollectionViewLayout+MJRefresh.h in Headers */ = {isa = PBXBuildFile; fileRef = 7A698CD0274232A00028A4DD /* UICollectionViewLayout+MJRefresh.h */; };
+		7A698CD3274232A00028A4DD /* UICollectionViewLayout+MJRefresh.m in Sources */ = {isa = PBXBuildFile; fileRef = 7A698CD1274232A00028A4DD /* UICollectionViewLayout+MJRefresh.m */; };
+		7A698CD5274232A00028A4DD /* UICollectionViewLayout+MJRefresh.m in Sources */ = {isa = PBXBuildFile; fileRef = 7A698CD1274232A00028A4DD /* UICollectionViewLayout+MJRefresh.m */; };
 		84FC857E228037CE001C6728 /* MJRefreshConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = 01A2CE0B21ACE01500BEE365 /* MJRefreshConfig.m */; };
 		88E3200B24839ABA00069FA7 /* MJRefreshTrailer.m in Sources */ = {isa = PBXBuildFile; fileRef = 88E3200924839ABA00069FA7 /* MJRefreshTrailer.m */; };
 		88E3201124839B2300069FA7 /* MJRefreshStateTrailer.m in Sources */ = {isa = PBXBuildFile; fileRef = 88E3200E24839B2300069FA7 /* MJRefreshStateTrailer.m */; };
@@ -236,6 +239,8 @@
 		2DB2EA161BECBE6700D58F6A /* UIView+MJExtension.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIView+MJExtension.m"; sourceTree = "<group>"; };
 		6B8FB1A7250E610D00AF64B7 /* MJHorizontalCollectionViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MJHorizontalCollectionViewController.h; sourceTree = "<group>"; };
 		6B8FB1A8250E610D00AF64B7 /* MJHorizontalCollectionViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MJHorizontalCollectionViewController.m; sourceTree = "<group>"; };
+		7A698CD0274232A00028A4DD /* UICollectionViewLayout+MJRefresh.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UICollectionViewLayout+MJRefresh.h"; sourceTree = "<group>"; };
+		7A698CD1274232A00028A4DD /* UICollectionViewLayout+MJRefresh.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UICollectionViewLayout+MJRefresh.m"; sourceTree = "<group>"; };
 		88E3200924839ABA00069FA7 /* MJRefreshTrailer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MJRefreshTrailer.m; sourceTree = "<group>"; };
 		88E3200A24839ABA00069FA7 /* MJRefreshTrailer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MJRefreshTrailer.h; sourceTree = "<group>"; };
 		88E3200D24839B2300069FA7 /* MJRefreshNormalTrailer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MJRefreshNormalTrailer.h; sourceTree = "<group>"; };
@@ -450,18 +455,20 @@
 				2DB2E9F61BECBE6700D58F6A /* Custom */,
 				2DB2EA0D1BECBE6700D58F6A /* MJRefresh.bundle */,
 				2DB2EA0E1BECBE6700D58F6A /* MJRefresh.h */,
+				01A2CE0A21ACE01500BEE365 /* MJRefreshConfig.h */,
+				01A2CE0B21ACE01500BEE365 /* MJRefreshConfig.m */,
 				2DB2EA0F1BECBE6700D58F6A /* MJRefreshConst.h */,
 				2DB2EA101BECBE6700D58F6A /* MJRefreshConst.m */,
+				2D4698841D0EE6A400CB8025 /* NSBundle+MJRefresh.h */,
+				2D4698851D0EE6A400CB8025 /* NSBundle+MJRefresh.m */,
+				7A698CD0274232A00028A4DD /* UICollectionViewLayout+MJRefresh.h */,
+				7A698CD1274232A00028A4DD /* UICollectionViewLayout+MJRefresh.m */,
 				2DB2EA111BECBE6700D58F6A /* UIScrollView+MJExtension.h */,
 				2DB2EA121BECBE6700D58F6A /* UIScrollView+MJExtension.m */,
 				2DB2EA131BECBE6700D58F6A /* UIScrollView+MJRefresh.h */,
 				2DB2EA141BECBE6700D58F6A /* UIScrollView+MJRefresh.m */,
 				2DB2EA151BECBE6700D58F6A /* UIView+MJExtension.h */,
 				2DB2EA161BECBE6700D58F6A /* UIView+MJExtension.m */,
-				2D4698841D0EE6A400CB8025 /* NSBundle+MJRefresh.h */,
-				2D4698851D0EE6A400CB8025 /* NSBundle+MJRefresh.m */,
-				01A2CE0A21ACE01500BEE365 /* MJRefreshConfig.h */,
-				01A2CE0B21ACE01500BEE365 /* MJRefreshConfig.m */,
 			);
 			path = MJRefresh;
 			sourceTree = SOURCE_ROOT;
@@ -582,6 +589,7 @@
 				2D9DF4681F7CE2C20042D6DD /* MJRefresh.h in Headers */,
 				2D9DF4691F7CE2C20042D6DD /* MJRefreshConst.h in Headers */,
 				2D9DF46A1F7CE2C20042D6DD /* UIScrollView+MJExtension.h in Headers */,
+				7A698CD2274232A00028A4DD /* UICollectionViewLayout+MJRefresh.h in Headers */,
 				2D9DF46B1F7CE2C20042D6DD /* UIScrollView+MJRefresh.h in Headers */,
 				2D9DF46C1F7CE2C20042D6DD /* UIView+MJExtension.h in Headers */,
 				2D9DF46D1F7CE2C20042D6DD /* NSBundle+MJRefresh.h in Headers */,
@@ -738,6 +746,7 @@
 				84FC857E228037CE001C6728 /* MJRefreshConfig.m in Sources */,
 				2D9DF46F1F7CE2F90042D6DD /* MJRefreshAutoFooter.m in Sources */,
 				2D9DF4701F7CE2F90042D6DD /* MJRefreshBackFooter.m in Sources */,
+				7A698CD5274232A00028A4DD /* UICollectionViewLayout+MJRefresh.m in Sources */,
 				2D9DF4711F7CE2F90042D6DD /* MJRefreshComponent.m in Sources */,
 				2D9DF4721F7CE2F90042D6DD /* MJRefreshFooter.m in Sources */,
 				2D9DF4731F7CE2F90042D6DD /* MJRefreshHeader.m in Sources */,
@@ -768,6 +777,7 @@
 				2D9BEB0F1BB15F4A00AED473 /* MJDIYHeader.m in Sources */,
 				2D9BEB181BB15F4A00AED473 /* MJTableViewController.m in Sources */,
 				2DB2EA261BECBE6700D58F6A /* MJRefreshConst.m in Sources */,
+				7A698CD3274232A00028A4DD /* UICollectionViewLayout+MJRefresh.m in Sources */,
 				2D9BEB0A1BB15F4A00AED473 /* MJChiBaoZiFooter.m in Sources */,
 				2D9BEB121BB15F4A00AED473 /* MJExampleWindow.m in Sources */,
 				2DB2EA221BECBE6700D58F6A /* MJRefreshGifHeader.m in Sources */,

+ 12 - 3
MJRefreshExample/Classes/Second/MJCollectionViewController.m

@@ -57,14 +57,23 @@ - (void)example21
         
         // 模拟延迟加载数据,因此2秒后才调用(真实开发中,可以移除这段gcd代码)
         dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(MJDuration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
-            [weakSelf.collectionView reloadData];
+//            [weakSelf.collectionView reloadData];
+//            [weakSelf.collectionView.mj_footer endRefreshing];
+            
+            [weakSelf.collectionView performBatchUpdates:^{
+                [weakSelf.collectionView reloadSections:[NSIndexSet indexSetWithIndex:0]];
+            } completion:^(BOOL finished) {
+                // 结束刷新
+                [weakSelf.collectionView.mj_footer endRefreshing];
+            }];
             
-            // 结束刷新
-            [weakSelf.collectionView.mj_footer endRefreshing];
         });
+        
     }] setAnimationDisabled]
       autoChangeTransparency:YES]
      linkTo:self.collectionView];
+    
+    [self.collectionView.mj_header beginRefreshing];
 }
 
 #pragma mark - 数据相关