├── NECollectionViewLayout ├── Assets │ └── .gitkeep └── Classes │ ├── NECollectionViewLayout.h │ ├── NECollectionViewFlowLayout │ ├── NECollectionViewFlowLayout+subhook.h │ ├── ReuseView │ │ ├── NECollectionViewFlowLayoutSectionContentScrollCollectionReusableView.h │ │ └── NECollectionViewFlowLayoutSectionContentScrollCollectionReusableView.m │ ├── NECollectionViewFlowLayoutInvalidationContext.h │ ├── NECollectionViewFlowLayoutAttributes.h │ ├── NECollectionViewFlowLayoutInvalidationContext.m │ ├── private │ │ ├── NECollectionViewFlowLayoutCollection.h │ │ ├── NECollectionViewFlowLayoutTypes.h │ │ ├── NECollectionViewFlowLayoutNode.h │ │ ├── NECollectionViewDelegateResponds.h │ │ ├── NECollectionViewFlowLayoutInvalidation.h │ │ ├── NECollectionViewLayoutHelpers.h │ │ ├── NECollectionViewUpdates.h │ │ ├── NECollectionViewFlowLayoutLine.h │ │ ├── NECollectionViewFlowLayoutContext.h │ │ ├── NECollectionViewFlowLayoutItem.h │ │ └── NECollectionViewFlowLayoutCalculator.h │ ├── NECollectionViewFlowLayoutAnimator.m │ ├── NECollectionViewFlowLayoutAttributes.m │ ├── NECollectionViewFlowLayoutAnimator.h │ └── NECollectionViewFlowLayout.h │ └── NECollectionView │ ├── NEOptimizeCollectionView.h │ ├── NEOptimizeCollectionViewLayoutProtocol.h │ └── NEOptimizeCollectionView.m ├── _Pods.xcodeproj ├── Example ├── Tests │ ├── en.lproj │ │ └── InfoPlist.strings │ ├── Tests-Prefix.pch │ ├── Tests.m │ └── Tests-Info.plist ├── NECollectionViewLayout │ ├── en.lproj │ │ └── InfoPlist.strings │ ├── NEAppDelegate.h │ ├── NEMainTableViewController.h │ ├── main.m │ ├── NECollectionViewLayout-Prefix.pch │ ├── NEMoveCollectionViewController.h │ ├── NEPinCollectionViewController.h │ ├── NEDeleteCollectionViewController.h │ ├── NEInsertCollectionViewController.h │ ├── NEUpdateCollectionViewController.h │ ├── NEReorderCollectionViewController.h │ ├── NEAlignmentCollectionViewController.h │ ├── NEBackgroundCollectionViewController.h │ ├── NEScrollDirectionCollectionViewController.h │ ├── NETextCollectionViewCell.h │ ├── NEFooterCollectionReusableView.h │ ├── NEHeaderCollectionReusableView.h │ ├── NEFooterCollectionReusableView.m │ ├── NEHeaderCollectionReusableView.m │ ├── NETextCollectionViewCell.m │ ├── NECollectionViewLayout-Info.plist │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── Images.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── NEAppDelegate.m │ ├── NEMainTableViewController.m │ ├── NEUpdateCollectionViewController.m │ ├── NEReorderCollectionViewController.m │ ├── NEMoveCollectionViewController.m │ ├── NEScrollDirectionCollectionViewController.m │ ├── NEBackgroundCollectionViewController.m │ ├── NEInsertCollectionViewController.m │ ├── NEDeleteCollectionViewController.m │ ├── NEPinCollectionViewController.m │ └── NEAlignmentCollectionViewController.m ├── Podfile ├── NECollectionViewLayout.xcodeproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ └── NECollectionViewLayout-Example.xcscheme ├── NECollectionViewLayout.xcworkspace │ ├── xcshareddata │ │ └── IDEWorkspaceChecks.plist │ └── contents.xcworkspacedata └── Podfile.lock ├── NECollectionViewLayout.modulemap ├── .gitignore ├── README.md ├── NECollectionViewLayout.podspec └── LICENSE /NECollectionViewLayout/Assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /_Pods.xcodeproj: -------------------------------------------------------------------------------- 1 | Example/Pods/Pods.xcodeproj -------------------------------------------------------------------------------- /Example/Tests/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /Example/NECollectionViewLayout/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /NECollectionViewLayout.modulemap: -------------------------------------------------------------------------------- 1 | framework module NECollectionViewLayout { 2 | umbrella header "NECollectionViewLayout.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Example/Tests/Tests-Prefix.pch: -------------------------------------------------------------------------------- 1 | // The contents of this file are implicitly included at the beginning of every test case source file. 2 | 3 | #ifdef __OBJC__ 4 | 5 | 6 | 7 | #endif 8 | -------------------------------------------------------------------------------- /Example/Podfile: -------------------------------------------------------------------------------- 1 | 2 | target 'NECollectionViewLayout_Example' do 3 | pod 'NECollectionViewLayout', :path => '../' 4 | 5 | target 'NECollectionViewLayout_Tests' do 6 | inherit! :search_paths 7 | 8 | 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /Example/NECollectionViewLayout.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/NECollectionViewLayout.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Example/NECollectionViewLayout.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /NECollectionViewLayout/Classes/NECollectionViewLayout.h: -------------------------------------------------------------------------------- 1 | // 2 | // NECollectionViewLayout.h 3 | // Pods 4 | // 5 | // Created by Daniel on 2019/12/10. 6 | // 7 | 8 | #ifndef NECollectionViewLayout_h 9 | #define NECollectionViewLayout_h 10 | 11 | #import "NEOptimizeCollectionView.h" 12 | #import "NECollectionViewFlowLayout.h" 13 | 14 | #endif /* NECollectionViewLayout_h */ 15 | -------------------------------------------------------------------------------- /Example/NECollectionViewLayout/NEAppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // NEAppDelegate.h 3 | // NECollectionViewLayout 4 | // 5 | // Created by Daniel on 11/28/2019. 6 | // Copyright (c) 2019 Daniel. All rights reserved. 7 | // 8 | 9 | @import UIKit; 10 | 11 | @interface NEAppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Example/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - NECollectionViewLayout (0.1.0) 3 | 4 | DEPENDENCIES: 5 | - NECollectionViewLayout (from `../`) 6 | 7 | EXTERNAL SOURCES: 8 | NECollectionViewLayout: 9 | :path: "../" 10 | 11 | SPEC CHECKSUMS: 12 | NECollectionViewLayout: c2cb01f051e6197cd2ae219140fffd0e1a5dbe2c 13 | 14 | PODFILE CHECKSUM: abf0dc9a185f1407a4202a96779be45586c81f68 15 | 16 | COCOAPODS: 1.10.0 17 | -------------------------------------------------------------------------------- /Example/NECollectionViewLayout/NEMainTableViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // NEMainTableViewController.h 3 | // NECollectionViewLayout_Example 4 | // 5 | // Created by Daniel on 2019/12/2. 6 | // Copyright © 2019 Daniel. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface NEMainTableViewController : UITableViewController 14 | 15 | @end 16 | 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /Example/NECollectionViewLayout/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // NECollectionViewLayout 4 | // 5 | // Created by Daniel on 11/28/2019. 6 | // Copyright (c) 2019 Daniel. All rights reserved. 7 | // 8 | 9 | @import UIKit; 10 | #import "NEAppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) 13 | { 14 | @autoreleasepool { 15 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([NEAppDelegate class])); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Example/NECollectionViewLayout/NECollectionViewLayout-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header 3 | // 4 | // The contents of this file are implicitly included at the beginning of every source file. 5 | // 6 | 7 | #import 8 | 9 | #ifndef __IPHONE_5_0 10 | #warning "This project uses features only available in iOS SDK 5.0 and later." 11 | #endif 12 | 13 | #ifdef __OBJC__ 14 | @import UIKit; 15 | @import Foundation; 16 | #endif 17 | -------------------------------------------------------------------------------- /Example/NECollectionViewLayout/NEMoveCollectionViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // NEMoveCollectionViewController.h 3 | // NECollectionViewLayout_Example 4 | // 5 | // Created by Daniel on 2019/12/9. 6 | // Copyright © 2019 Daniel. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface NEMoveCollectionViewController : UICollectionViewController 14 | 15 | @end 16 | 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /Example/NECollectionViewLayout/NEPinCollectionViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // NEPinCollectionViewController.h 3 | // NECollectionViewLayout_Example 4 | // 5 | // Created by Daniel on 2019/12/9. 6 | // Copyright © 2019 Daniel. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface NEPinCollectionViewController : UICollectionViewController 14 | 15 | @end 16 | 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /Example/NECollectionViewLayout/NEDeleteCollectionViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // NEDeleteCollectionViewController.h 3 | // NECollectionViewLayout_Example 4 | // 5 | // Created by Daniel on 2019/12/9. 6 | // Copyright © 2019 Daniel. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface NEDeleteCollectionViewController : UICollectionViewController 14 | 15 | @end 16 | 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /Example/NECollectionViewLayout/NEInsertCollectionViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // NEInsertCollectionViewController.h 3 | // NECollectionViewLayout_Example 4 | // 5 | // Created by Daniel on 2019/12/9. 6 | // Copyright © 2019 Daniel. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface NEInsertCollectionViewController : UICollectionViewController 14 | 15 | @end 16 | 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /Example/NECollectionViewLayout/NEUpdateCollectionViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // NEUpdateCollectionViewController.h 3 | // NECollectionViewLayout_Example 4 | // 5 | // Created by Daniel on 2019/12/2. 6 | // Copyright © 2019 Daniel. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface NEUpdateCollectionViewController : UICollectionViewController 14 | 15 | @end 16 | 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /Example/NECollectionViewLayout/NEReorderCollectionViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // NEReorderCollectionViewController.h 3 | // NECollectionViewLayout_Example 4 | // 5 | // Created by Daniel on 2019/12/9. 6 | // Copyright © 2019 Daniel. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface NEReorderCollectionViewController : UICollectionViewController 14 | 15 | @end 16 | 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /Example/NECollectionViewLayout/NEAlignmentCollectionViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // NEAlignmentCollectionViewController.h 3 | // NECollectionViewLayout_Example 4 | // 5 | // Created by Daniel on 2019/12/9. 6 | // Copyright © 2019 Daniel. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface NEAlignmentCollectionViewController : UICollectionViewController 14 | 15 | @end 16 | 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /Example/NECollectionViewLayout/NEBackgroundCollectionViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // NEBackgroundCollectionViewController.h 3 | // NECollectionViewLayout_Example 4 | // 5 | // Created by Daniel on 2019/12/5. 6 | // Copyright © 2019 Daniel. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface NEBackgroundCollectionViewController : UICollectionViewController 14 | 15 | @end 16 | 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /Example/NECollectionViewLayout/NEScrollDirectionCollectionViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // NEScrollDirectionCollectionViewController.h 3 | // NECollectionViewLayout_Example 4 | // 5 | // Created by Daniel on 2019/12/5. 6 | // Copyright © 2019 Daniel. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface NEScrollDirectionCollectionViewController : UICollectionViewController 14 | 15 | @end 16 | 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /Example/NECollectionViewLayout/NETextCollectionViewCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // NETextCollectionViewCell.h 3 | // NECollectionViewLayout_Example 4 | // 5 | // Created by Daniel on 2019/12/4. 6 | // Copyright © 2019 Daniel. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface NETextCollectionViewCell : UICollectionViewCell 14 | 15 | 16 | @property (nonatomic, strong) UILabel *textLabel; 17 | 18 | @end 19 | 20 | NS_ASSUME_NONNULL_END 21 | -------------------------------------------------------------------------------- /Example/NECollectionViewLayout/NEFooterCollectionReusableView.h: -------------------------------------------------------------------------------- 1 | // 2 | // NEFooterCollectionReusableView.h 3 | // NECollectionViewLayout_Example 4 | // 5 | // Created by Daniel on 2019/12/9. 6 | // Copyright © 2019 Daniel. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface NEFooterCollectionReusableView : UICollectionReusableView 14 | 15 | @property (nonatomic, strong) UILabel *textLabel; 16 | 17 | @end 18 | 19 | NS_ASSUME_NONNULL_END 20 | -------------------------------------------------------------------------------- /Example/NECollectionViewLayout/NEHeaderCollectionReusableView.h: -------------------------------------------------------------------------------- 1 | // 2 | // NEHeaderCollectionReusableView.h 3 | // NECollectionViewLayout_Example 4 | // 5 | // Created by Daniel on 2019/12/9. 6 | // Copyright © 2019 Daniel. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface NEHeaderCollectionReusableView : UICollectionReusableView 14 | 15 | @property (nonatomic, strong) UILabel *textLabel; 16 | 17 | @end 18 | 19 | NS_ASSUME_NONNULL_END 20 | -------------------------------------------------------------------------------- /NECollectionViewLayout/Classes/NECollectionViewFlowLayout/NECollectionViewFlowLayout+subhook.h: -------------------------------------------------------------------------------- 1 | // 2 | // NECollectionViewFlowLayout+subhook.h 3 | // CHTCollectionViewWaterfallLayout 4 | // 5 | // Created by Daniel on 2019/12/9. 6 | // 7 | 8 | #import "NECollectionViewFlowLayout.h" 9 | #import "NECollectionViewFlowLayoutCalculator.h" 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface NECollectionViewFlowLayout (subhook) 14 | 15 | - (NE::CollectionViewFlowLayout::Calculator&)layout; 16 | - (CGRect)visibleRect; 17 | 18 | @end 19 | 20 | NS_ASSUME_NONNULL_END 21 | -------------------------------------------------------------------------------- /NECollectionViewLayout/Classes/NECollectionView/NEOptimizeCollectionView.h: -------------------------------------------------------------------------------- 1 | // 2 | // NEOptimizeCollectionView.h 3 | // CHTCollectionViewWaterfallLayout 4 | // 5 | // Created by Daniel on 2019/12/4. 6 | // 7 | 8 | #import 9 | #import "NEOptimizeCollectionViewLayoutProtocol.h" 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | /// A collection view with optimization feature. To support this feature, the layout MUST 14 | /// conform to NEOptimizeCollectionViewLayoutProtocol, and relayout the changed area. 15 | @interface NEOptimizeCollectionView : UICollectionView 16 | 17 | @end 18 | 19 | NS_ASSUME_NONNULL_END 20 | -------------------------------------------------------------------------------- /NECollectionViewLayout/Classes/NECollectionViewFlowLayout/ReuseView/NECollectionViewFlowLayoutSectionContentScrollCollectionReusableView.h: -------------------------------------------------------------------------------- 1 | // 2 | // NECollectionViewFlowLayoutSectionContentScrollCollectionReusableView.h 3 | // CHTCollectionViewWaterfallLayout 4 | // 5 | // Created by Daniel on 2019/12/6. 6 | // 7 | 8 | #import 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | @interface NECollectionViewFlowLayoutSectionContentScrollCollectionReusableView : UICollectionReusableView 13 | 14 | @property (nonatomic, strong) UIScrollView *scrollView; 15 | 16 | @end 17 | 18 | NS_ASSUME_NONNULL_END 19 | -------------------------------------------------------------------------------- /Example/Tests/Tests.m: -------------------------------------------------------------------------------- 1 | // 2 | // NECollectionViewLayoutTests.m 3 | // NECollectionViewLayoutTests 4 | // 5 | // Created by Daniel on 11/28/2019. 6 | // Copyright (c) 2019 Daniel. All rights reserved. 7 | // 8 | 9 | @import XCTest; 10 | 11 | @interface Tests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation Tests 16 | 17 | - (void)setUp 18 | { 19 | [super setUp]; 20 | // Put setup code here. This method is called before the invocation of each test method in the class. 21 | } 22 | 23 | - (void)tearDown 24 | { 25 | // Put teardown code here. This method is called after the invocation of each test method in the class. 26 | [super tearDown]; 27 | } 28 | 29 | @end 30 | 31 | -------------------------------------------------------------------------------- /Example/Tests/Tests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Example/NECollectionViewLayout/NEFooterCollectionReusableView.m: -------------------------------------------------------------------------------- 1 | // 2 | // NEFooterCollectionReusableView.m 3 | // NECollectionViewLayout_Example 4 | // 5 | // Created by Daniel on 2019/12/9. 6 | // Copyright © 2019 Daniel. All rights reserved. 7 | // 8 | 9 | #import "NEFooterCollectionReusableView.h" 10 | 11 | @implementation NEFooterCollectionReusableView 12 | 13 | - (instancetype)initWithFrame:(CGRect)frame 14 | { 15 | self = [super initWithFrame:frame]; 16 | if (self) { 17 | _textLabel = [UILabel new]; 18 | _textLabel.textAlignment = NSTextAlignmentCenter; 19 | [self addSubview:_textLabel]; 20 | } 21 | return self; 22 | } 23 | 24 | - (void)layoutSubviews { 25 | [super layoutSubviews]; 26 | _textLabel.frame = self.bounds; 27 | } 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /Example/NECollectionViewLayout/NEHeaderCollectionReusableView.m: -------------------------------------------------------------------------------- 1 | // 2 | // NEHeaderCollectionReusableView.m 3 | // NECollectionViewLayout_Example 4 | // 5 | // Created by Daniel on 2019/12/9. 6 | // Copyright © 2019 Daniel. All rights reserved. 7 | // 8 | 9 | #import "NEHeaderCollectionReusableView.h" 10 | 11 | @implementation NEHeaderCollectionReusableView 12 | 13 | - (instancetype)initWithFrame:(CGRect)frame 14 | { 15 | self = [super initWithFrame:frame]; 16 | if (self) { 17 | _textLabel = [UILabel new]; 18 | _textLabel.textAlignment = NSTextAlignmentCenter; 19 | [self addSubview:_textLabel]; 20 | } 21 | return self; 22 | } 23 | 24 | - (void)layoutSubviews { 25 | [super layoutSubviews]; 26 | _textLabel.frame = self.bounds; 27 | } 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OS X 2 | .DS_Store 3 | 4 | # Xcode 5 | build/ 6 | *.pbxuser 7 | !default.pbxuser 8 | *.mode1v3 9 | !default.mode1v3 10 | *.mode2v3 11 | !default.mode2v3 12 | *.perspectivev3 13 | !default.perspectivev3 14 | xcuserdata/ 15 | *.xccheckout 16 | profile 17 | *.moved-aside 18 | DerivedData 19 | *.hmap 20 | *.ipa 21 | 22 | # Bundler 23 | .bundle 24 | 25 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 26 | # Carthage/Checkouts 27 | 28 | Carthage/Build 29 | 30 | # We recommend against adding the Pods directory to your .gitignore. However 31 | # you should judge for yourself, the pros and cons are mentioned at: 32 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 33 | # 34 | # Note: if you ignore the Pods directory, make sure to uncomment 35 | # `pod install` in .travis.yml 36 | # 37 | Pods/ 38 | -------------------------------------------------------------------------------- /NECollectionViewLayout/Classes/NECollectionViewFlowLayout/NECollectionViewFlowLayoutInvalidationContext.h: -------------------------------------------------------------------------------- 1 | // 2 | // NECollectionViewFlowLayoutInvalidationContext.h 3 | // CHTCollectionViewWaterfallLayout 4 | // 5 | // Created by Daniel on 2019/12/3. 6 | // 7 | 8 | #import 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | @interface NECollectionViewFlowLayoutInvalidationContext : UICollectionViewFlowLayoutInvalidationContext 13 | 14 | #pragma mark - Section scroll support 15 | 16 | /// Invalidate the contentOffset of the section 17 | /// @param offset contentOffset of the section 18 | /// @param index the index of section 19 | - (void)invalidateScrollOffset:(CGPoint)offset forSectionAtIndex:(NSInteger)index; 20 | 21 | /// All invalidated contentOffset section infos. 22 | @property (nonatomic, readonly) NSDictionary *invalidatedSectionScrollOffsets; 23 | 24 | @end 25 | 26 | NS_ASSUME_NONNULL_END 27 | -------------------------------------------------------------------------------- /NECollectionViewLayout/Classes/NECollectionView/NEOptimizeCollectionViewLayoutProtocol.h: -------------------------------------------------------------------------------- 1 | // 2 | // NEOptimizeCollectionViewLayoutProtocol.h 3 | // CHTCollectionViewWaterfallLayout 4 | // 5 | // Created by Daniel on 2019/12/4. 6 | // 7 | 8 | #import 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | @protocol NEOptimizeCollectionViewLayoutProtocol 13 | 14 | @required 15 | - (void)insertSections:(NSIndexSet *)sections; 16 | - (void)deleteSections:(NSIndexSet *)sections; 17 | - (void)reloadSections:(NSIndexSet *)sections; 18 | - (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection; 19 | 20 | - (void)insertItemsAtIndexPaths:(NSArray *)indexPaths; 21 | - (void)deleteItemsAtIndexPaths:(NSArray *)indexPaths; 22 | - (void)reloadItemsAtIndexPaths:(NSArray *)indexPaths; 23 | - (void)moveItemAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath; 24 | 25 | @end 26 | 27 | NS_ASSUME_NONNULL_END 28 | -------------------------------------------------------------------------------- /NECollectionViewLayout/Classes/NECollectionViewFlowLayout/NECollectionViewFlowLayoutAttributes.h: -------------------------------------------------------------------------------- 1 | // 2 | // NECollectionViewFlowLayoutAttributes.h 3 | // CHTCollectionViewWaterfallLayout 4 | // 5 | // Created by Daniel on 2019/12/6. 6 | // 7 | 8 | #import 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | @protocol NECollectionViewFlowLayoutAttributesDelegate 13 | 14 | - (void)collectionViewFlowLayoutAttributesSectionDidScrollWithContentOffset:(CGPoint)offset atIndexPath:(NSIndexPath *)indexPath; 15 | 16 | @end 17 | 18 | @interface NECollectionViewFlowLayoutAttributes : UICollectionViewLayoutAttributes 19 | 20 | @property (nonatomic, weak) id delegate; 21 | @property (nonatomic, assign) BOOL pageEnable; 22 | @property (nonatomic, assign) CGSize pageSize; 23 | @property (nonatomic, assign) CGSize contentSize; 24 | @property (nonatomic, assign) CGPoint contentOffset; 25 | 26 | @property (nonatomic, assign) BOOL pinned; 27 | 28 | @end 29 | 30 | NS_ASSUME_NONNULL_END 31 | -------------------------------------------------------------------------------- /Example/NECollectionViewLayout/NETextCollectionViewCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // NETextCollectionViewCell.m 3 | // NECollectionViewLayout_Example 4 | // 5 | // Created by Daniel on 2019/12/4. 6 | // Copyright © 2019 Daniel. All rights reserved. 7 | // 8 | 9 | #import "NETextCollectionViewCell.h" 10 | 11 | @implementation NETextCollectionViewCell 12 | 13 | - (instancetype)initWithFrame:(CGRect)frame 14 | { 15 | self = [super initWithFrame:frame]; 16 | if (self) { 17 | _textLabel = [UILabel new]; 18 | _textLabel.font = [UIFont systemFontOfSize:17]; 19 | _textLabel.textAlignment = NSTextAlignmentCenter; 20 | [self.contentView addSubview:_textLabel]; 21 | } 22 | return self; 23 | } 24 | 25 | - (void)layoutSubviews { 26 | [super layoutSubviews]; 27 | 28 | _textLabel.frame = self.bounds; 29 | } 30 | 31 | - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { 32 | [super touchesBegan:touches withEvent:event]; 33 | NSLog(@"%s", sel_getName(_cmd)); 34 | } 35 | 36 | @end 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NECollectionViewFlowLayout 2 | 3 | 几乎等价于UICollectionViewFlowLayout,但对其增加了一些额外的能力。 4 | 5 | ## 对齐 6 | 7 | 对齐方式可以根据section来配置了。 8 | 9 | 目前支持的对齐有:(水平方向 / 竖直方向) 10 | 11 | - 居左 / 居顶 12 | - 居右 / 居底 13 | - 居中 / 居中 14 | - 两端对齐 15 | 16 | ## 背景 17 | 18 | section可以单独设置背景view了 19 | 20 | ![](./background.png) 21 | 22 | ## Pin 23 | 24 | 目前支持更丰富的Pin能力。并且可以对单独section设置 25 | 26 | - inside section 和系统行为一致,在section内部pin 27 | - before section 在小于等于section的位置永远展示 28 | - after section 在大于等于section的位置永远展示 29 | - always 无论何时,永远展示,类似于某些列表头部的悬停区域 30 | 31 | ## 横向滚动 32 | 33 | 类似于app store的结构。如果以前要做列表内的横向列表,需要在cell上添加列表这样的双层结构才能实现,现改为一个CollectionView来实现该能力。这样: 34 | 35 | - 减少了层次结构,减少复杂度,更符合结构上的分层 36 | - 减少了因多列表产生的offset、性能等问题 37 | - 可以完美接入cell的display事件 38 | 39 | #### Page 40 | 41 | 横向滚动可以启用page功能,会变成分页效果,可以自定义分页大小 pageSize 42 | 43 | ## 性能优化 44 | 45 | 增量更新,系统layout会全量拉取size并计算,这里优化了这种情况。 46 | 47 | 如果要启用增量更新的特性,需要将`UICollectionView`替换为`NEOptimizeCollectionView`。 48 | 49 | 并且使用支持更新协议`NEOptimizeCollectionViewLayoutProtocol`的layout。 50 | 51 | 否则增量更新特性会失效,并回到全量更新策略。 52 | 53 | ## Author 54 | 55 | Daniel, djs66256@163.com 56 | 57 | ## License 58 | 59 | See the LICENSE file for more info. 60 | -------------------------------------------------------------------------------- /NECollectionViewLayout/Classes/NECollectionViewFlowLayout/NECollectionViewFlowLayoutInvalidationContext.m: -------------------------------------------------------------------------------- 1 | // 2 | // NECollectionViewFlowLayoutInvalidationContext.m 3 | // CHTCollectionViewWaterfallLayout 4 | // 5 | // Created by Daniel on 2019/12/3. 6 | // 7 | 8 | #import "NECollectionViewFlowLayoutInvalidationContext.h" 9 | 10 | @implementation NECollectionViewFlowLayoutInvalidationContext { 11 | NSMutableDictionary *_invalidatedSectionScrollOffsets; 12 | } 13 | 14 | //- (instancetype)init 15 | //{ 16 | // self = [super init]; 17 | // if (self) { 18 | // self.invalidateFlowLayoutDelegateMetrics = NO; 19 | // self.invalidateFlowLayoutAttributes = NO; 20 | // } 21 | // return self; 22 | //} 23 | 24 | - (void)invalidateScrollOffset:(CGPoint)offset forSectionAtIndex:(NSInteger)index { 25 | if (_invalidatedSectionScrollOffsets == nil) { 26 | _invalidatedSectionScrollOffsets = [NSMutableDictionary new]; 27 | } 28 | _invalidatedSectionScrollOffsets[@(index)] = @(offset); 29 | } 30 | 31 | - (NSDictionary *)invalidatedSectionScrollOffsets { 32 | return _invalidatedSectionScrollOffsets; 33 | } 34 | 35 | @end 36 | -------------------------------------------------------------------------------- /NECollectionViewLayout/Classes/NECollectionViewFlowLayout/private/NECollectionViewFlowLayoutCollection.h: -------------------------------------------------------------------------------- 1 | // 2 | // NECollectionViewFlowLayoutCollection.h 3 | // Pods 4 | // 5 | // Created by Daniel on 2019/12/4. 6 | // 7 | 8 | #ifndef NECollectionViewFlowLayoutCollection_h 9 | #define NECollectionViewFlowLayoutCollection_h 10 | 11 | #import 12 | 13 | namespace NE::CollectionViewFlowLayout { 14 | 15 | class LayoutCollection { 16 | public: 17 | LayoutCollection() { 18 | attributes_ = [NSMutableArray arrayWithCapacity:0]; 19 | } 20 | 21 | void addItem(UICollectionViewLayoutAttributes *attr) { 22 | [attributes_ addObject:attr]; 23 | } 24 | 25 | void addSupplementary(UICollectionViewLayoutAttributes *attr) { 26 | [attributes_ addObject:attr]; 27 | } 28 | 29 | void addDecoration(UICollectionViewLayoutAttributes *attr) { 30 | [attributes_ addObject:attr]; 31 | } 32 | 33 | NSMutableArray *attributes() { 34 | return attributes_; 35 | } 36 | 37 | private: 38 | __strong NSMutableArray *attributes_ = nil; 39 | }; 40 | 41 | } 42 | 43 | #endif /* NECollectionViewFlowLayoutCollection_h */ 44 | -------------------------------------------------------------------------------- /NECollectionViewLayout.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # Be sure to run `pod lib lint NECollectionViewLayout.podspec' to ensure this is a 3 | # valid spec before submitting. 4 | # 5 | # Any lines starting with a # are optional, but their use is encouraged 6 | # To learn more about a Podspec see https://guides.cocoapods.org/syntax/podspec.html 7 | # 8 | 9 | Pod::Spec.new do |s| 10 | s.name = 'NECollectionViewLayout' 11 | s.version = '0.1.0' 12 | s.summary = 'NECollectionViewLayout.' 13 | s.homepage = 'https://github.com/djs66256/NECollectionViewLayout' 14 | s.license = { :text => 'Apache License 2.0' } 15 | s.author = { 'Daniel' => 'djs66256@163.com' } 16 | s.source = { :git => 'https://github.com/djs66256/NECollectionViewLayout', :tag => s.version.to_s } 17 | 18 | # s.resource_bundles = { 19 | # 'NECollectionViewLayout' => ['NECollectionViewLayout/Assets/**/*'] 20 | # } 21 | 22 | s.ios.deployment_target = '9.0' 23 | s.source_files = 'NECollectionViewLayout/Classes/**/*' 24 | # s.private_header_files = 'NECollectionViewLayout/Classes/**/*.h' 25 | s.module_map = 'NECollectionViewLayout.modulemap' 26 | 27 | s.pod_target_xcconfig = { 28 | 'CLANG_CXX_LANGUAGE_STANDARD' => 'c++17', 29 | } 30 | s.library = 'c++' 31 | s.frameworks = 'UIKit', 'CoreGraphics' 32 | end 33 | -------------------------------------------------------------------------------- /NECollectionViewLayout/Classes/NECollectionViewFlowLayout/NECollectionViewFlowLayoutAnimator.m: -------------------------------------------------------------------------------- 1 | // 2 | // NECollectionViewFlowLayoutAnimator.m 3 | // CHTCollectionViewWaterfallLayout 4 | // 5 | // Created by Daniel on 2019/12/30. 6 | // 7 | 8 | #import "NECollectionViewFlowLayoutAnimator.h" 9 | 10 | @implementation NECollectionViewFlowLayoutScaleAnimator 11 | 12 | - (UICollectionViewLayoutAttributes *)layout:(UICollectionViewLayout *)layout 13 | initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)itemIndexPath 14 | previousAttributes:(nullable UICollectionViewLayoutAttributes *)prevAttributes { 15 | UICollectionViewLayoutAttributes *attributes = prevAttributes.copy; 16 | attributes.zIndex --; 17 | attributes.alpha = 0.3; 18 | attributes.transform = CGAffineTransformScale(CGAffineTransformIdentity, 0.1, 0.1); 19 | return attributes; 20 | } 21 | 22 | - (UICollectionViewLayoutAttributes *)layout:(UICollectionViewLayout *)layout 23 | finalLayoutAttributesForDisappearingItemAtIndexPath:(NSIndexPath *)itemIndexPath 24 | previousAttributes:(UICollectionViewLayoutAttributes *)prevAttributes { 25 | UICollectionViewLayoutAttributes *attributes = prevAttributes.copy; 26 | attributes.zIndex --; 27 | attributes.alpha = 0.3; 28 | attributes.transform = CGAffineTransformScale(CGAffineTransformIdentity, 0.1, 0.1); 29 | return attributes; 30 | } 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /NECollectionViewLayout/Classes/NECollectionViewFlowLayout/NECollectionViewFlowLayoutAttributes.m: -------------------------------------------------------------------------------- 1 | // 2 | // NECollectionViewFlowLayoutAttributes.m 3 | // CHTCollectionViewWaterfallLayout 4 | // 5 | // Created by Daniel on 2019/12/6. 6 | // 7 | 8 | #import "NECollectionViewFlowLayoutAttributes.h" 9 | 10 | @implementation NECollectionViewFlowLayoutAttributes 11 | 12 | - (BOOL)isEqual:(id)object { 13 | BOOL b = [super isEqual:object]; 14 | if (b && [object isKindOfClass:NECollectionViewFlowLayoutAttributes.class]) { 15 | NECollectionViewFlowLayoutAttributes *attributes = (NECollectionViewFlowLayoutAttributes *)object; 16 | BOOL ret = attributes.delegate == self.delegate 17 | && attributes.pageEnable == self.pageEnable 18 | && CGSizeEqualToSize(attributes.pageSize, self.pageSize) 19 | && CGSizeEqualToSize(attributes.contentSize, self.contentSize) 20 | && CGPointEqualToPoint(attributes.contentOffset, self.contentOffset) 21 | && attributes.pinned == self.pinned; 22 | 23 | return ret; 24 | } 25 | return NO; 26 | } 27 | 28 | - (id)copyWithZone:(NSZone *)zone { 29 | NECollectionViewFlowLayoutAttributes *attributes = [super copyWithZone:zone]; 30 | attributes.delegate = self.delegate; 31 | attributes.pageEnable = self.pageEnable; 32 | attributes.pageSize = self.pageSize; 33 | attributes.contentSize = self.contentSize; 34 | attributes.contentOffset = self.contentOffset; 35 | attributes.pinned = self.pinned; 36 | return attributes; 37 | } 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /NECollectionViewLayout/Classes/NECollectionViewFlowLayout/private/NECollectionViewFlowLayoutTypes.h: -------------------------------------------------------------------------------- 1 | // 2 | // NECollectionViewFlowLayoutTypes.h 3 | // Pods 4 | // 5 | // Created by Daniel on 2019/12/3. 6 | // 7 | 8 | #ifndef NECollectionViewFlowLayoutTypes_h 9 | #define NECollectionViewFlowLayoutTypes_h 10 | 11 | namespace NE { 12 | namespace CollectionViewFlowLayout { 13 | enum class Alignment { 14 | Leading = NECollectionViewFlowLayoutAlignLeading, 15 | Trailing = NECollectionViewFlowLayoutAlignTrailing, 16 | Center = NECollectionViewFlowLayoutAlignCenter, 17 | SpacingBetween = NECollectionViewFlowLayoutAlignSpacingBetween 18 | }; 19 | 20 | inline CGFloat CalculatePositionWithAlignment(Alignment align, CGFloat container, CGFloat value) { 21 | switch (align) { 22 | case Alignment::Trailing: 23 | return container - value; 24 | break; 25 | case Alignment::Center: 26 | return (container - value) / 2; 27 | break; 28 | default: 29 | return 0; 30 | break; 31 | } 32 | } 33 | 34 | enum class PinToVisibleBounds { 35 | None = NECollectionViewFlowLayoutPinToVisibleBoundsNone, 36 | InsideSection = NECollectionViewFlowLayoutPinToVisibleBoundsInsideSection, 37 | AfterSection = NECollectionViewFlowLayoutPinToVisibleBoundsAfterSection, 38 | BeforeSection = NECollectionViewFlowLayoutPinToVisibleBoundsBeforeSection, 39 | Always = NECollectionViewFlowLayoutPinToVisibleBoundsAlways, 40 | }; 41 | 42 | enum class ScrollDirection { 43 | Horizontal = UICollectionViewScrollDirectionHorizontal, 44 | Vertical = UICollectionViewScrollDirectionVertical, 45 | }; 46 | } 47 | } 48 | 49 | #endif /* NECollectionViewFlowLayoutTypes_h */ 50 | -------------------------------------------------------------------------------- /Example/NECollectionViewLayout/NECollectionViewLayout-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1.0 25 | LSRequiresIPhoneOS 26 | 27 | UILaunchStoryboardName 28 | LaunchScreen 29 | UIMainStoryboardFile 30 | Main 31 | UIRequiredDeviceCapabilities 32 | 33 | armv7 34 | 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationLandscapeLeft 39 | UIInterfaceOrientationLandscapeRight 40 | 41 | UISupportedInterfaceOrientations~ipad 42 | 43 | UIInterfaceOrientationPortrait 44 | UIInterfaceOrientationPortraitUpsideDown 45 | UIInterfaceOrientationLandscapeLeft 46 | UIInterfaceOrientationLandscapeRight 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /Example/NECollectionViewLayout/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /NECollectionViewLayout/Classes/NECollectionViewFlowLayout/NECollectionViewFlowLayoutAnimator.h: -------------------------------------------------------------------------------- 1 | // 2 | // NECollectionViewFlowLayoutAnimator.h 3 | // CHTCollectionViewWaterfallLayout 4 | // 5 | // Created by Daniel on 2019/12/30. 6 | // 7 | 8 | #import 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | @protocol NECollectionViewFlowLayoutAnimator 13 | 14 | @optional 15 | 16 | - (nullable UICollectionViewLayoutAttributes *)layout:(UICollectionViewLayout *)layout 17 | initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)itemIndexPath 18 | previousAttributes:(nullable UICollectionViewLayoutAttributes *)prevAttributes; 19 | 20 | - (nullable UICollectionViewLayoutAttributes *)layout:(UICollectionViewLayout *)layout 21 | finalLayoutAttributesForDisappearingItemAtIndexPath:(NSIndexPath *)itemIndexPath 22 | previousAttributes:(nullable UICollectionViewLayoutAttributes *)prevAttributes; 23 | @end 24 | 25 | @protocol NECollectionViewFlowLayoutElementAnimator 26 | 27 | @optional 28 | 29 | - (nullable UICollectionViewLayoutAttributes *)layout:(UICollectionViewLayout *)layout 30 | initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)itemIndexPath 31 | elementKind:(NSString *)elementKind 32 | previousAttributes:(nullable UICollectionViewLayoutAttributes *)prevAttributes; 33 | 34 | - (nullable UICollectionViewLayoutAttributes *)layout:(UICollectionViewLayout *)layout 35 | finalLayoutAttributesForDisappearingItemAtIndexPath:(NSIndexPath *)itemIndexPath 36 | elementKind:(NSString *)elementKind 37 | previousAttributes:(nullable UICollectionViewLayoutAttributes *)prevAttributes; 38 | 39 | @end 40 | 41 | @interface NECollectionViewFlowLayoutScaleAnimator : NSObject 42 | 43 | @end 44 | 45 | NS_ASSUME_NONNULL_END 46 | -------------------------------------------------------------------------------- /Example/NECollectionViewLayout/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /NECollectionViewLayout/Classes/NECollectionViewFlowLayout/private/NECollectionViewFlowLayoutNode.h: -------------------------------------------------------------------------------- 1 | // 2 | // NECollectionViewFlowLayoutNode.h 3 | // Pods 4 | // 5 | // Created by Daniel on 2019/12/3. 6 | // 7 | 8 | #ifndef NECollectionViewFlowLayoutNode_h 9 | #define NECollectionViewFlowLayoutNode_h 10 | 11 | #import 12 | 13 | namespace NE::CollectionViewFlowLayout { 14 | class Node { 15 | public: 16 | Node() = default; 17 | Node(const CGPoint& origin) : frame_({origin, CGSizeZero}) {} 18 | Node(const Node&) = default; 19 | Node& operator=(const Node&) = default; 20 | virtual ~Node() = default; 21 | 22 | void setOrigin(const CGPoint& origin) { frame_.origin = origin; } 23 | const CGPoint& origin() const { return frame_.origin; } 24 | 25 | void setSize(const CGSize& size) { frame_.size = size; } 26 | const CGSize& size() const { return frame_.size; } 27 | 28 | void setFrame(const CGRect& frame) { frame_ = frame; } 29 | const CGRect& frame() const { return frame_; } 30 | 31 | protected: 32 | CGRect frame_{0}; 33 | }; 34 | 35 | class Container : public Node { 36 | public: 37 | using Node::Node; 38 | Container(CGPoint origin, CGSize fitSize) : Node(origin), fitSize_(fitSize) {} 39 | Container(const Container&) = default; 40 | Container& operator=(const Container&) = default; 41 | 42 | void setFitSize(CGSize fitSize) { fitSize_ = fitSize; } 43 | const CGSize& fitSize() const { return fitSize_; } 44 | 45 | protected: 46 | CGSize fitSize_{0}; 47 | 48 | }; 49 | 50 | class Content : public Node { 51 | public: 52 | using Node::Node; 53 | Content(const Content&) = default; 54 | Content& operator=(const Content&) = default; 55 | 56 | void setContentSize(CGSize size) { contentSize_ = size; } 57 | const CGSize& contentSize() const { return contentSize_; } 58 | 59 | protected: 60 | CGSize contentSize_{0}; 61 | }; 62 | } 63 | 64 | #endif /* NECollectionViewFlowLayoutNode_h */ 65 | -------------------------------------------------------------------------------- /Example/NECollectionViewLayout/NEAppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // NEAppDelegate.m 3 | // NECollectionViewLayout 4 | // 5 | // Created by Daniel on 11/28/2019. 6 | // Copyright (c) 2019 Daniel. All rights reserved. 7 | // 8 | 9 | #import "NEAppDelegate.h" 10 | 11 | @implementation NEAppDelegate 12 | 13 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 14 | { 15 | // Override point for customization after application launch. 16 | return YES; 17 | } 18 | 19 | - (void)applicationWillResignActive:(UIApplication *)application 20 | { 21 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 22 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 23 | } 24 | 25 | - (void)applicationDidEnterBackground:(UIApplication *)application 26 | { 27 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 28 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 29 | } 30 | 31 | - (void)applicationWillEnterForeground:(UIApplication *)application 32 | { 33 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | - (void)applicationDidBecomeActive:(UIApplication *)application 37 | { 38 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 39 | } 40 | 41 | - (void)applicationWillTerminate:(UIApplication *)application 42 | { 43 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 44 | } 45 | 46 | @end 47 | -------------------------------------------------------------------------------- /NECollectionViewLayout/Classes/NECollectionView/NEOptimizeCollectionView.m: -------------------------------------------------------------------------------- 1 | // 2 | // NEOptimizeCollectionView.m 3 | // CHTCollectionViewWaterfallLayout 4 | // 5 | // Created by Daniel on 2019/12/4. 6 | // 7 | 8 | #import "NEOptimizeCollectionView.h" 9 | 10 | #import "NEOptimizeCollectionViewLayoutProtocol.h" 11 | 12 | @implementation NEOptimizeCollectionView 13 | 14 | - (id)optimizeCollectionViewLayout { 15 | if ([self.collectionViewLayout conformsToProtocol:@protocol(NEOptimizeCollectionViewLayoutProtocol)]) { 16 | return (id)self.collectionViewLayout; 17 | } 18 | else { 19 | return nil; 20 | } 21 | } 22 | 23 | - (void)insertSections:(NSIndexSet *)sections { 24 | [self.optimizeCollectionViewLayout insertSections:sections]; 25 | [super insertSections:sections]; 26 | } 27 | 28 | - (void)deleteSections:(NSIndexSet *)sections { 29 | [self.optimizeCollectionViewLayout deleteSections:sections]; 30 | [super deleteSections:sections]; 31 | } 32 | 33 | - (void)reloadSections:(NSIndexSet *)sections { 34 | [self.optimizeCollectionViewLayout reloadSections:sections]; 35 | [super reloadSections:sections]; 36 | } 37 | 38 | - (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection { 39 | [self.optimizeCollectionViewLayout moveSection:section toSection:newSection]; 40 | [super moveSection:section toSection:newSection]; 41 | } 42 | 43 | - (void)insertItemsAtIndexPaths:(NSArray *)indexPaths { 44 | [self.optimizeCollectionViewLayout insertItemsAtIndexPaths:indexPaths]; 45 | [super insertItemsAtIndexPaths:indexPaths]; 46 | } 47 | 48 | - (void)deleteItemsAtIndexPaths:(NSArray *)indexPaths { 49 | [self.optimizeCollectionViewLayout deleteItemsAtIndexPaths:indexPaths]; 50 | [super deleteItemsAtIndexPaths:indexPaths]; 51 | } 52 | 53 | - (void)reloadItemsAtIndexPaths:(NSArray *)indexPaths { 54 | [self.optimizeCollectionViewLayout reloadItemsAtIndexPaths:indexPaths]; 55 | [super reloadItemsAtIndexPaths:indexPaths]; 56 | } 57 | 58 | - (void)moveItemAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath { 59 | [self.optimizeCollectionViewLayout moveItemAtIndexPath:indexPath toIndexPath:newIndexPath]; 60 | [super moveItemAtIndexPath:indexPath toIndexPath:newIndexPath]; 61 | } 62 | 63 | @end 64 | -------------------------------------------------------------------------------- /Example/NECollectionViewLayout/NEMainTableViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // NEMainTableViewController.m 3 | // NECollectionViewLayout_Example 4 | // 5 | // Created by Daniel on 2019/12/2. 6 | // Copyright © 2019 Daniel. All rights reserved. 7 | // 8 | 9 | #import "NEMainTableViewController.h" 10 | 11 | @interface NEMainTableViewController () 12 | 13 | @property (nonatomic, strong) NSArray *dataSource; 14 | 15 | @end 16 | 17 | @implementation NEMainTableViewController 18 | 19 | - (void)viewDidLoad { 20 | [super viewDidLoad]; 21 | 22 | [self.tableView registerClass:UITableViewCell.class forCellReuseIdentifier:@"cell"]; 23 | self.dataSource = @[ 24 | @{ 25 | @"title": @"update", 26 | @"class": @"NEUpdateCollectionViewController" 27 | }, 28 | @{ 29 | @"title": @"background", 30 | @"class": @"NEBackgroundCollectionViewController" 31 | }, 32 | @{ 33 | @"title": @"scroll horizontal", 34 | @"class": @"NEScrollDirectionCollectionViewController" 35 | },@{ 36 | @"title": @"Alignment", 37 | @"class": @"NEAlignmentCollectionViewController" 38 | }, 39 | @{ 40 | @"title": @"pin", 41 | @"class": @"NEPinCollectionViewController" 42 | }, 43 | @{ 44 | @"title": @"insert", 45 | @"class": @"NEInsertCollectionViewController" 46 | }, 47 | @{ 48 | @"title": @"delete", 49 | @"class": @"NEDeleteCollectionViewController" 50 | }, 51 | // TODO: 52 | // @{ 53 | // @"title": @"reorder", 54 | // @"class": @"NEReorderCollectionViewController" 55 | // }, 56 | @{ 57 | @"title": @"Move", 58 | @"class": @"NEMoveCollectionViewController" 59 | }, 60 | ]; 61 | } 62 | 63 | #pragma mark - Table view data source 64 | 65 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { 66 | return 1; 67 | } 68 | 69 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 70 | return self.dataSource.count; 71 | } 72 | 73 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 74 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell" forIndexPath:indexPath]; 75 | cell.textLabel.text = self.dataSource[indexPath.row][@"title"]; 76 | 77 | return cell; 78 | } 79 | 80 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { 81 | Class cls = NSClassFromString(self.dataSource[indexPath.row][@"class"]); 82 | UIViewController *vc = [[cls alloc] init]; 83 | [self.navigationController pushViewController:vc animated:YES]; 84 | } 85 | 86 | @end 87 | -------------------------------------------------------------------------------- /NECollectionViewLayout/Classes/NECollectionViewFlowLayout/private/NECollectionViewDelegateResponds.h: -------------------------------------------------------------------------------- 1 | // 2 | // NECollectionViewDelegateResponds.h 3 | // Pods 4 | // 5 | // Created by Daniel on 2019/11/28. 6 | // 7 | 8 | #ifndef NECollectionViewDelegateResponds_h 9 | #define NECollectionViewDelegateResponds_h 10 | 11 | #include 12 | #include "NECollectionViewFlowLayoutTypes.h" 13 | #include 14 | 15 | namespace NE { 16 | namespace CollectionViewFlowLayout { 17 | 18 | /// Cache of delegate respondsToSelector 19 | struct UICollectionViewFlowLayoutResponds { 20 | BOOL sizeForItemAtIndexPath = NO; 21 | BOOL insetForSectionAtIndex = NO; 22 | BOOL minimumLineSpacingForSectionAtIndex = NO; 23 | BOOL minimumInteritemSpacingForSectionAtIndex = NO; 24 | BOOL referenceSizeForHeaderInSection = NO; 25 | BOOL referenceSizeForFooterInSection = NO; 26 | 27 | UICollectionViewFlowLayoutResponds(id delegate) : 28 | sizeForItemAtIndexPath([delegate respondsToSelector:@selector(collectionView:layout:sizeForItemAtIndexPath:)]), 29 | insetForSectionAtIndex([delegate respondsToSelector:@selector(collectionView:layout:insetForSectionAtIndex:)]), 30 | minimumLineSpacingForSectionAtIndex([delegate respondsToSelector:@selector(collectionView:layout:minimumLineSpacingForSectionAtIndex:)]), 31 | minimumInteritemSpacingForSectionAtIndex([delegate respondsToSelector:@selector(collectionView:layout:minimumInteritemSpacingForSectionAtIndex:)]), 32 | referenceSizeForHeaderInSection([delegate respondsToSelector:@selector(collectionView:layout:referenceSizeForHeaderInSection:)]), 33 | referenceSizeForFooterInSection([delegate respondsToSelector:@selector(collectionView:layout:referenceSizeForFooterInSection:)]) 34 | { } 35 | }; 36 | 37 | struct NECollectionViewFlowLayoutResponds : UICollectionViewFlowLayoutResponds { 38 | public: 39 | NECollectionViewFlowLayoutResponds(id delegate): 40 | UICollectionViewFlowLayoutResponds(delegate), 41 | delegate_(delegate) 42 | {} 43 | 44 | #define RespondsList(L) \ 45 | L(additionZIndexForSectionAtIndex) \ 46 | \ 47 | L(headerPinToVisibleBoundsForSectionAtIndex) \ 48 | L(footerPinToVisibleBoundsForSectionAtIndex) \ 49 | \ 50 | L(backgroundVisibleForSectionAtIndex) \ 51 | L(backgroundIncludeSupplementarysForSectionAtIndex) \ 52 | L(backgroundInsetsForSectionAtIndex) \ 53 | \ 54 | L(alignHorizontalForSectionAtIndex) \ 55 | L(alignVerticalForSectionAtIndex) \ 56 | \ 57 | L(scrollDirectionForSectionAtIndex) \ 58 | L(pageEnableForSectionAtIndex) \ 59 | L(pageSizeForSectionAtIndex) \ 60 | L(heightForScrollHorizontalSectionAtIndex) 61 | 62 | #define RespondsGetter(method_name) \ 63 | bool method_name() { \ 64 | if (method_name ## _) { \ 65 | return *method_name ## _; \ 66 | } \ 67 | else { \ 68 | bool r = [delegate_ respondsToSelector:@selector(collectionView:layout:method_name:)]; \ 69 | method_name ## _ = r; \ 70 | return r; \ 71 | } \ 72 | } \ 73 | 74 | RespondsList(RespondsGetter) 75 | 76 | #undef RespondsGetter 77 | 78 | private: 79 | __weak id delegate_; 80 | 81 | #define RespondsOptional(method_name) std::optional method_name ## _ = std::nullopt; 82 | RespondsList(RespondsOptional) 83 | #undef RespondsOptional 84 | 85 | #undef RespondsList 86 | }; 87 | } 88 | } 89 | 90 | #endif /* NECollectionViewDelegateResponds_h */ 91 | -------------------------------------------------------------------------------- /NECollectionViewLayout/Classes/NECollectionViewFlowLayout/private/NECollectionViewFlowLayoutInvalidation.h: -------------------------------------------------------------------------------- 1 | // 2 | // NECollectionViewFlowLayoutInvalidation.h 3 | // Pods 4 | // 5 | // Created by Daniel on 2019/12/3. 6 | // 7 | 8 | #ifndef NECollectionViewFlowLayoutInvalidation_h 9 | #define NECollectionViewFlowLayoutInvalidation_h 10 | 11 | #include 12 | #include 13 | #include 14 | #import 15 | #include "NECollectionViewLayoutHelpers.h" 16 | 17 | namespace NE::CollectionViewFlowLayout { 18 | 19 | class Invalidation { 20 | public: 21 | 22 | void invalidateEverything() { invalidateEverything_ = true; } 23 | bool isInvalidateEverything() { return invalidateEverything_; } 24 | void invalidateDataSourceCounts() { invalidateDataSourceCounts_ = true; } 25 | bool isInvalidateDataSourceCounts() { return invalidateDataSourceCounts_; } 26 | void invalidateFlowLayoutDelegateMetrics() { invalidateFlowLayoutDelegateMetrics_ = true; } 27 | bool isInvalidateFlowLayoutDelegateMetrics() { return invalidateFlowLayoutDelegateMetrics_; } 28 | void invalidateFlowLayoutAttributes() { invalidateFlowLayoutAttributes_ = true; } 29 | bool isInvalidateFlowLayoutAttributes() { return invalidateFlowLayoutAttributes_; } 30 | 31 | void invalidateItem(const IndexPath indexPath) { 32 | minSection_ = std::min(minSection_, indexPath.section()); 33 | invalidateItems_.insert(indexPath); 34 | } 35 | 36 | const auto& invalidateItems() { return invalidateItems_; } 37 | 38 | void invalidateSumplementary(NSString *kind, const IndexPath indexPath) { 39 | minSection_ = std::min(minSection_, indexPath.section()); 40 | auto& idxes = invalidateSumplementaries_[kind]; 41 | idxes.push_back(indexPath); 42 | } 43 | 44 | const auto& invalidateSumplementaries() const { return invalidateSumplementaries_; } 45 | 46 | void invalidateDecoration(NSString *kind, const IndexPath indexPath) { 47 | minSection_ = std::min(minSection_, indexPath.section()); 48 | auto& idxes = invalidateDecorations_[kind]; 49 | idxes.push_back(indexPath); 50 | } 51 | 52 | const auto& invalidateDecorations() const { return invalidateDecorations_; } 53 | 54 | void invalidateSectionContentOffset(const NSUInteger index, CGPoint contentOffset) { 55 | minSection_ = std::min(minSection_, index); 56 | invalidateScrollOffsets_[index] = contentOffset; 57 | } 58 | const auto& invalidatedSectionContentOffsets() const { return invalidateScrollOffsets_; } 59 | 60 | NSUInteger minSection() { 61 | return minSection_; 62 | } 63 | 64 | bool hasInvalidateAttributes() { 65 | return minSection_ != NSNotFound; 66 | } 67 | 68 | void reset() { 69 | invalidateEverything_ = false; 70 | invalidateDataSourceCounts_ = false; 71 | invalidateFlowLayoutDelegateMetrics_ = false; 72 | invalidateFlowLayoutAttributes_ = false; 73 | invalidateItems_.clear(); 74 | invalidateSumplementaries_.clear(); 75 | invalidateDecorations_.clear(); 76 | invalidateScrollOffsets_.clear(); 77 | minSection_ = NSNotFound; 78 | } 79 | private: 80 | bool invalidateEverything_{false}; 81 | bool invalidateDataSourceCounts_{false}; 82 | bool invalidateFlowLayoutDelegateMetrics_{false}; 83 | bool invalidateFlowLayoutAttributes_{false}; 84 | NSUInteger minSection_{NSNotFound}; 85 | 86 | std::set invalidateItems_; 87 | std::unordered_map, std::vector> invalidateSumplementaries_; 88 | std::unordered_map, std::vector> invalidateDecorations_; 89 | std::unordered_map invalidateScrollOffsets_; 90 | }; 91 | 92 | } 93 | 94 | #endif /* NECollectionViewFlowLayoutInvalidation_h */ 95 | -------------------------------------------------------------------------------- /Example/NECollectionViewLayout.xcodeproj/xcshareddata/xcschemes/NECollectionViewLayout-Example.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 42 | 48 | 49 | 50 | 51 | 52 | 62 | 64 | 70 | 71 | 72 | 73 | 79 | 81 | 87 | 88 | 89 | 90 | 92 | 93 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /Example/NECollectionViewLayout/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /NECollectionViewLayout/Classes/NECollectionViewFlowLayout/ReuseView/NECollectionViewFlowLayoutSectionContentScrollCollectionReusableView.m: -------------------------------------------------------------------------------- 1 | // 2 | // NECollectionViewFlowLayoutSectionContentScrollCollectionReusableView.m 3 | // CHTCollectionViewWaterfallLayout 4 | // 5 | // Created by Daniel on 2019/12/6. 6 | // 7 | 8 | #import "NECollectionViewFlowLayoutSectionContentScrollCollectionReusableView.h" 9 | #import "NECollectionViewFlowLayoutAttributes.h" 10 | 11 | @interface NECollectionViewFlowLayoutSectionContentScrollCollectionReusableViewScrollView : UIScrollView 12 | 13 | @end 14 | 15 | @implementation NECollectionViewFlowLayoutSectionContentScrollCollectionReusableViewScrollView 16 | 17 | - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer { 18 | CGPoint point = [gestureRecognizer locationInView:self]; 19 | BOOL contain = CGRectContainsPoint(self.bounds, point); 20 | return !contain; 21 | } 22 | 23 | @end 24 | 25 | @implementation NECollectionViewFlowLayoutSectionContentScrollCollectionReusableView { 26 | CGSize _pageSize; 27 | BOOL _pageEnable; 28 | NSIndexPath *_indexPath; 29 | id _delegate; 30 | } 31 | 32 | - (void)dealloc { 33 | if (self.superview) { 34 | [self.superview removeGestureRecognizer:_scrollView.panGestureRecognizer]; 35 | } 36 | } 37 | 38 | - (instancetype)initWithFrame:(CGRect)frame 39 | { 40 | self = [super initWithFrame:frame]; 41 | if (self) { 42 | self.userInteractionEnabled = NO; 43 | 44 | _scrollView = [[NECollectionViewFlowLayoutSectionContentScrollCollectionReusableViewScrollView alloc] initWithFrame:(CGRect){ CGPointZero, frame.size }]; 45 | _scrollView.scrollsToTop = NO; 46 | _scrollView.backgroundColor = [UIColor clearColor]; 47 | _scrollView.alwaysBounceHorizontal = YES; 48 | _scrollView.showsVerticalScrollIndicator = NO; 49 | _scrollView.showsHorizontalScrollIndicator = NO; 50 | _scrollView.directionalLockEnabled = YES; 51 | _scrollView.delegate = self; 52 | _scrollView.decelerationRate = UIScrollViewDecelerationRateFast; 53 | if (@available(iOS 11.0, *)) { 54 | _scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; 55 | } 56 | if (@available(iOS 13.0, *)) { 57 | _scrollView.automaticallyAdjustsScrollIndicatorInsets = NO; 58 | } 59 | [self addSubview:_scrollView]; 60 | } 61 | return self; 62 | } 63 | 64 | - (void)layoutSubviews { 65 | [super layoutSubviews]; 66 | _scrollView.frame = self.bounds; 67 | } 68 | 69 | - (void)didMoveToSuperview { 70 | UIView *superview = self.superview; 71 | if (superview) { 72 | [superview addGestureRecognizer:_scrollView.panGestureRecognizer]; 73 | } 74 | else { 75 | [superview removeGestureRecognizer:_scrollView.panGestureRecognizer]; 76 | } 77 | } 78 | 79 | - (void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes { 80 | NSParameterAssert([layoutAttributes isKindOfClass:NECollectionViewFlowLayoutAttributes.class]); 81 | [super applyLayoutAttributes:layoutAttributes]; 82 | _delegate = nil; 83 | NECollectionViewFlowLayoutAttributes *attr = (NECollectionViewFlowLayoutAttributes *)layoutAttributes; 84 | if (fabs(_scrollView.contentSize.width - attr.contentSize.width) > 0.1 ) { 85 | _scrollView.contentSize = attr.contentSize; 86 | } 87 | 88 | if (fabs(_scrollView.contentOffset.x - attr.contentOffset.x) > 0.1 ) { 89 | _scrollView.contentOffset = attr.contentOffset; 90 | } 91 | 92 | _pageEnable = attr.pageEnable; 93 | _pageSize = attr.pageSize; 94 | _indexPath = attr.indexPath; 95 | _delegate = attr.delegate; 96 | } 97 | 98 | - (void)scrollViewDidScroll:(UIScrollView *)scrollView { 99 | [_delegate collectionViewFlowLayoutAttributesSectionDidScrollWithContentOffset:scrollView.contentOffset atIndexPath:_indexPath]; 100 | } 101 | 102 | - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset { 103 | if (_pageEnable) { 104 | CGSize pageSize = _pageSize.width < 1 ? scrollView.frame.size : _pageSize; 105 | if (velocity.x > 0.1) { 106 | CGFloat pageNum = ceil(scrollView.contentOffset.x / pageSize.width); 107 | targetContentOffset->x = pageSize.width * pageNum; 108 | } 109 | else if (velocity.x < -0.1) { 110 | CGFloat pageNum = floor(scrollView.contentOffset.x / pageSize.width); 111 | targetContentOffset->x = pageSize.width * pageNum; 112 | } 113 | else { 114 | targetContentOffset->x = pageSize.width * round(scrollView.contentOffset.x / pageSize.width); 115 | } 116 | } 117 | } 118 | 119 | @end 120 | -------------------------------------------------------------------------------- /NECollectionViewLayout/Classes/NECollectionViewFlowLayout/private/NECollectionViewLayoutHelpers.h: -------------------------------------------------------------------------------- 1 | // 2 | // NECollectionViewLayoutHelpers.h 3 | // Pods 4 | // 5 | // Created by Daniel on 2019/12/3. 6 | // 7 | 8 | #ifndef NECollectionViewLayoutHelpers_h 9 | #define NECollectionViewLayoutHelpers_h 10 | 11 | #include 12 | #include 13 | 14 | namespace NE { 15 | 16 | inline CGPoint operator+ (const CGPoint& left, const UIEdgeInsets& insets) { 17 | return CGPoint({ 18 | .x = left.x + insets.left, 19 | .y = left.y + insets.top, 20 | }); 21 | } 22 | inline CGPoint operator- (const CGPoint& left, const UIEdgeInsets& insets) { 23 | return CGPoint({ 24 | .x = left.x - insets.left, 25 | .y = left.y - insets.top, 26 | }); 27 | } 28 | inline CGSize operator+ (const CGSize& left, const UIEdgeInsets& insets) { 29 | return CGSize({ 30 | .width = left.width - insets.left - insets.right, 31 | .height = left.height - insets.top - insets.bottom 32 | }); 33 | } 34 | inline CGSize operator- (const CGSize& left, const UIEdgeInsets& insets) { 35 | return CGSize({ 36 | .width = left.width + insets.left + insets.right, 37 | .height = left.height + insets.top + insets.bottom 38 | }); 39 | } 40 | 41 | inline CGRect operator+ (const CGRect& left, const UIEdgeInsets& insets) { 42 | return { 43 | .origin = left.origin + insets, 44 | .size = left.size + insets 45 | }; 46 | } 47 | inline CGRect operator- (const CGRect& left, const UIEdgeInsets& insets) { 48 | return { 49 | .origin = left.origin - insets, 50 | .size = left.size - insets 51 | }; 52 | } 53 | 54 | inline bool operator== (const CGRect& left, const CGRect& right) { 55 | return CGRectEqualToRect(left, right); 56 | } 57 | inline bool operator!= (const CGRect& left, const CGRect& right) { 58 | return !CGRectEqualToRect(left, right); 59 | } 60 | 61 | inline bool operator== (const CGSize& left, const CGSize& right) { 62 | return CGSizeEqualToSize(left, right); 63 | } 64 | inline bool operator!= (const CGSize& left, const CGSize& right) { 65 | return !CGSizeEqualToSize(left, right); 66 | } 67 | 68 | inline bool operator== (const CGPoint& left, const CGPoint& right) { 69 | return CGPointEqualToPoint(left, right); 70 | } 71 | inline bool operator!= (const CGPoint& left, const CGPoint& right) { 72 | return !CGPointEqualToPoint(left, right); 73 | } 74 | 75 | 76 | inline bool operator == (const UIEdgeInsets& left, const UIEdgeInsets& right) { 77 | return UIEdgeInsetsEqualToEdgeInsets(left, right); 78 | } 79 | inline bool operator != (const UIEdgeInsets& left, const UIEdgeInsets& right) { 80 | return !UIEdgeInsetsEqualToEdgeInsets(left, right); 81 | } 82 | 83 | 84 | class IndexPath : std::array { 85 | private: 86 | using Super = std::array; 87 | public: 88 | IndexPath(NSIndexPath *indexPath) : Super({static_cast([indexPath section]), static_cast([indexPath item])}) {} 89 | IndexPath(NSUInteger section, NSUInteger item) : Super({section, item}) {} 90 | NSUInteger section() const { return at(0); } 91 | void setSection(NSUInteger section) { at(0) = section; } 92 | NSUInteger item() const { return at(1); } 93 | void setItem(NSUInteger item) { at(1) = item; } 94 | operator NSIndexPath *() const { 95 | return [NSIndexPath indexPathForItem:at(1) inSection:at(0)]; 96 | } 97 | }; 98 | 99 | inline bool operator> (const IndexPath& left, const IndexPath& right) { 100 | if (left.section() > right.section()) return true; 101 | else if (left.section() == right.section()) return left.item() > right.item(); 102 | else return false; 103 | } 104 | 105 | inline bool operator>= (const IndexPath& left, const IndexPath& right) { 106 | if (left.section() > right.section()) return true; 107 | else if (left.section() == right.section()) return left.item() >= right.item(); 108 | else return false; 109 | } 110 | 111 | inline bool operator< (const IndexPath& left, const IndexPath& right) { 112 | return !operator>=(left, right); 113 | } 114 | 115 | inline bool operator<= (const IndexPath& left, const IndexPath& right) { 116 | return !operator>(left, right); 117 | } 118 | 119 | inline bool operator== (const IndexPath& left, const IndexPath& right) { 120 | return left.section() == right.section() && left.item() == right.item(); 121 | } 122 | 123 | inline bool operator!= (const IndexPath& left, const IndexPath& right) { 124 | return !operator==(left, right); 125 | } 126 | 127 | template 128 | struct ObjcRef { 129 | __strong T *value; 130 | ObjcRef(T *val) : value(val) {} 131 | T *operator*() const noexcept { return value; } 132 | operator T*() const noexcept { return value; } 133 | }; 134 | 135 | } 136 | 137 | namespace std { 138 | 139 | template 140 | struct hash> { 141 | size_t operator()(const NE::ObjcRef& obj) const noexcept { 142 | return [*obj hash]; 143 | } 144 | }; 145 | 146 | template 147 | bool operator==(const NE::ObjcRef& left, const NE::ObjcRef& right) noexcept { 148 | return [*left isEqual:*right]; 149 | } 150 | 151 | template 152 | bool operator!=(const NE::ObjcRef& left, const NE::ObjcRef& right) noexcept { 153 | return ![*left isEqual:*right]; 154 | } 155 | 156 | } 157 | 158 | #endif /* NECollectionViewLayoutHelpers_h */ 159 | -------------------------------------------------------------------------------- /NECollectionViewLayout/Classes/NECollectionViewFlowLayout/private/NECollectionViewUpdates.h: -------------------------------------------------------------------------------- 1 | // 2 | // NECollectionViewUpdaters.h 3 | // Pods 4 | // 5 | // Created by Daniel on 2019/12/4. 6 | // 7 | 8 | #ifndef NECollectionViewUpdates_h 9 | #define NECollectionViewUpdates_h 10 | 11 | #import 12 | #include 13 | #include 14 | #include 15 | #include "NECollectionViewLayoutHelpers.h" 16 | 17 | namespace NE::CollectionView { 18 | 19 | enum class UpdateAction { 20 | Insert, 21 | Delete, 22 | Reload, 23 | Move, 24 | }; 25 | 26 | class UpdateItem { 27 | public: 28 | explicit UpdateItem(UpdateAction action, std::set&& section) : 29 | action_(action), isSection_(true), sections_(std::move(section)) 30 | {} 31 | 32 | explicit UpdateItem(UpdateAction action, std::set&& section) : 33 | action_(action), isSection_(false), indexPaths_(std::move(section)) 34 | {} 35 | 36 | explicit UpdateItem(UpdateAction action, NSUInteger from, NSUInteger to) : 37 | action_(action), isSection_(true), moveSections_({from, to}) 38 | {} 39 | 40 | explicit UpdateItem(UpdateAction action, IndexPath from, IndexPath to) : 41 | action_(action), isSection_(false), moveIndexPaths_({from, to}) 42 | {} 43 | 44 | UpdateAction action() const { return action_; } 45 | bool isSection() const { return isSection_; } 46 | const auto& sections() const { return sections_; } 47 | const auto& indexPaths() const { return indexPaths_; } 48 | const auto& moveSections() const { return moveSections_; } 49 | const auto& moveIndexPaths() const { return moveIndexPaths_; } 50 | 51 | private: 52 | UpdateAction action_; 53 | bool isSection_; 54 | std::set sections_; 55 | std::set indexPaths_; 56 | std::pair moveSections_{0, 0}; 57 | std::pair moveIndexPaths_{0, 0}; 58 | }; 59 | 60 | class Updates { 61 | private: 62 | struct SectionCompare { 63 | bool operator()(const IndexPath& i1, const IndexPath& i2) { 64 | return i1.section() < i2.section(); 65 | } 66 | }; 67 | public: 68 | void insertSections(std::set&& section) { 69 | minSection_ = std::min(minSection_, *std::min_element(section.cbegin(), section.cend())); 70 | updates_.emplace_back(UpdateAction::Insert, std::move(section)); 71 | } 72 | void deleteSections(std::set&& section) { 73 | minSection_ = std::min(minSection_, *std::min_element(section.cbegin(), section.cend())); 74 | updates_.emplace_back(UpdateAction::Delete, std::move(section)); 75 | } 76 | 77 | void reloadSections(std::set&& section) { 78 | minSection_ = std::min(minSection_, *std::min_element(section.cbegin(), section.cend())); 79 | updates_.emplace_back(UpdateAction::Reload, std::move(section)); 80 | } 81 | 82 | void moveSection(NSUInteger from, NSUInteger to) { 83 | minSection_ = std::min(minSection_, std::min(from, to)); 84 | updates_.emplace_back(UpdateAction::Move, from, to); 85 | } 86 | 87 | void insertItems(std::set&& indexPaths) { 88 | auto minIndexPath = indexPaths.begin();// std::min_element(indexPaths.cbegin(), indexPaths.cend(), SectionCompare()); 89 | minSection_ = std::min(minSection_, minIndexPath->section()); 90 | updates_.emplace_back(UpdateAction::Insert, std::move(indexPaths)); 91 | } 92 | 93 | void deleteItems(std::set&& indexPaths) { 94 | auto minIndexPath = indexPaths.begin();//std::min_element(indexPaths.cbegin(), indexPaths.cend(), SectionCompare()); 95 | minSection_ = std::min(minSection_, minIndexPath->section()); 96 | updates_.emplace_back(UpdateAction::Delete, std::move(indexPaths)); 97 | } 98 | 99 | void reloadItems(std::set&& indexPaths) { 100 | auto minIndexPath = indexPaths.begin();// std::min_element(indexPaths.cbegin(), indexPaths.cend(), SectionCompare()); 101 | minSection_ = std::min(minSection_, minIndexPath->section()); 102 | updates_.emplace_back(UpdateAction::Reload, std::move(indexPaths)); 103 | } 104 | 105 | void moveItem(IndexPath from, IndexPath to) { 106 | minSection_ = std::min(minSection_, std::min(from.section(), to.section())); 107 | updates_.emplace_back(UpdateAction::Move, from, to); 108 | } 109 | 110 | NSUInteger minSection() const { 111 | return minSection_; 112 | } 113 | 114 | bool hasModified() const { 115 | return minSection_ != NSNotFound; 116 | } 117 | 118 | void reset() { 119 | minSection_ = NSNotFound; 120 | updates_.clear(); 121 | } 122 | 123 | const std::vector& updateItems() const { 124 | return updates_; 125 | } 126 | 127 | private: 128 | std::vector updates_; 129 | NSUInteger minSection_{NSNotFound}; 130 | }; 131 | 132 | } 133 | 134 | #endif /* NECollectionViewUpdates_h */ 135 | -------------------------------------------------------------------------------- /Example/NECollectionViewLayout/NEUpdateCollectionViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // NEUpdateCollectionViewController.m 3 | // NECollectionViewLayout_Example 4 | // 5 | // Created by Daniel on 2019/12/2. 6 | // Copyright © 2019 Daniel. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "NEUpdateCollectionViewController.h" 12 | #import "NETextCollectionViewCell.h" 13 | 14 | @interface NEUpdateCollectionViewController () 15 | 16 | @property (nonatomic, assign) BOOL expanded; 17 | 18 | @end 19 | 20 | @implementation NEUpdateCollectionViewController 21 | 22 | static NSString * const reuseIdentifier = @"Cell"; 23 | 24 | - (instancetype)init 25 | { 26 | NECollectionViewFlowLayout *layout = [NECollectionViewFlowLayout new]; 27 | // UICollectionViewFlowLayout *layout = [UICollectionViewFlowLayout new]; 28 | layout.minimumLineSpacing = 10; 29 | layout.minimumInteritemSpacing = 10; 30 | // layout.invalidateFlowLayoutDelegateMetricsWhenUpdates = YES; 31 | // UICollectionViewLayout *layout = [NECollectionViewFlowLayout new]; 32 | self = [self initWithCollectionViewLayout:layout]; 33 | if (self) { 34 | 35 | } 36 | return self; 37 | } 38 | 39 | - (void)loadView { 40 | self.collectionView = [[NEOptimizeCollectionView alloc] initWithFrame:UIScreen.mainScreen.bounds 41 | collectionViewLayout:self.collectionViewLayout]; 42 | self.collectionView.backgroundColor = UIColor.blackColor; 43 | 44 | // self.collectionView = [[UICollectionView alloc] initWithFrame:UIScreen.mainScreen.bounds 45 | // collectionViewLayout:self.collectionViewLayout]; 46 | } 47 | 48 | - (void)viewDidLoad { 49 | [super viewDidLoad]; 50 | 51 | // Register cell classes 52 | [self.collectionView registerClass:[NETextCollectionViewCell class] forCellWithReuseIdentifier:NSStringFromClass(NETextCollectionViewCell.class)]; 53 | [self.collectionView registerClass:[UICollectionReusableView class] 54 | forSupplementaryViewOfKind:UICollectionElementKindSectionHeader 55 | withReuseIdentifier:NSStringFromClass(UICollectionReusableView.class)]; 56 | [self.collectionView registerClass:[UICollectionReusableView class] 57 | forSupplementaryViewOfKind:UICollectionElementKindSectionFooter 58 | withReuseIdentifier:NSStringFromClass(UICollectionReusableView.class)]; 59 | 60 | } 61 | 62 | #pragma mark 63 | 64 | - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { 65 | return 4; 66 | } 67 | 68 | 69 | - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { 70 | return 100; 71 | } 72 | 73 | - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { 74 | NETextCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:NSStringFromClass(NETextCollectionViewCell.class) forIndexPath:indexPath]; 75 | cell.backgroundColor = [UIColor grayColor]; 76 | cell.textLabel.text = @(indexPath.item).stringValue; 77 | return cell; 78 | } 79 | 80 | - (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath { 81 | UICollectionReusableView *view = [collectionView dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:NSStringFromClass(UICollectionReusableView.class) forIndexPath:indexPath]; 82 | if ([kind isEqualToString:UICollectionElementKindSectionHeader]) { 83 | view.backgroundColor = [UIColor yellowColor]; 84 | } 85 | else if ([kind isEqualToString:UICollectionElementKindSectionFooter]) { 86 | view.backgroundColor = [UIColor blueColor]; 87 | } 88 | else { 89 | view.backgroundColor = [UIColor redColor]; 90 | } 91 | return view; 92 | } 93 | 94 | - (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section { 95 | return UIEdgeInsetsMake(10, 20, 10, 20); 96 | } 97 | 98 | - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section { 99 | return CGSizeMake(collectionView.frame.size.width, 60); 100 | } 101 | 102 | - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section { 103 | return CGSizeMake(collectionView.frame.size.width, 30); 104 | } 105 | 106 | - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath { 107 | return self.expanded ? CGSizeMake(collectionView.frame.size.width / 5, indexPath.item % 3 ? 44 : 88) : CGSizeMake(collectionView.frame.size.width / 3, indexPath.item % 3 ? 44 : 88); 108 | } 109 | 110 | - (NECollectionViewFlowLayoutAlignment)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)layout alignVerticalForSectionAtIndex:(NSInteger)section { 111 | return section % 4; 112 | } 113 | 114 | - (NECollectionViewFlowLayoutAlignment)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)layout alignHorizontalForSectionAtIndex:(NSInteger)section { 115 | return section % 4; 116 | } 117 | 118 | - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath { 119 | self.expanded = !self.expanded; 120 | 121 | // [collectionView reloadItemsAtIndexPaths:@[indexPath]]; 122 | // [collectionView reloadData]; 123 | [CATransaction begin]; 124 | [CATransaction setDisableActions:YES]; 125 | CFTimeInterval t1 = CACurrentMediaTime(); 126 | [collectionView performBatchUpdates:^{ 127 | // __auto_type ctx = [UICollectionViewFlowLayoutInvalidationContext new]; 128 | // [ctx invalidateItemsAtIndexPaths:@[indexPath]]; 129 | // [collectionView.collectionViewLayout invalidateLayoutWithContext:ctx]; 130 | [collectionView reloadItemsAtIndexPaths:@[indexPath]]; 131 | } completion:^(BOOL finished) { 132 | CFTimeInterval t2 = CACurrentMediaTime(); 133 | NSLog(@"duration = %f", t2 - t1); 134 | }]; 135 | [CATransaction commit]; 136 | } 137 | 138 | 139 | @end 140 | -------------------------------------------------------------------------------- /Example/NECollectionViewLayout/NEReorderCollectionViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // NEReorderCollectionViewController.m 3 | // NECollectionViewLayout_Example 4 | // 5 | // Created by Daniel on 2019/12/9. 6 | // Copyright © 2019 Daniel. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "NETextCollectionViewCell.h" 12 | #import "NEHeaderCollectionReusableView.h" 13 | #import "NEFooterCollectionReusableView.h" 14 | #import "NEReorderCollectionViewController.h" 15 | 16 | @interface NEReorderCollectionViewController () 17 | 18 | @property (nonatomic, strong) NSMutableArray *> *dataSourece; 19 | 20 | @end 21 | 22 | @implementation NEReorderCollectionViewController 23 | 24 | - (instancetype)init 25 | { 26 | NECollectionViewFlowLayout *layout = [NECollectionViewFlowLayout new]; 27 | // UICollectionViewFlowLayout *layout = [UICollectionViewFlowLayout new]; 28 | layout.minimumLineSpacing = 10; 29 | layout.minimumInteritemSpacing = 10; 30 | // UICollectionViewLayout *layout = [NECollectionViewFlowLayout new]; 31 | self = [self initWithCollectionViewLayout:layout]; 32 | if (self) { 33 | 34 | } 35 | return self; 36 | } 37 | 38 | - (void)loadView { 39 | self.collectionView = [[NEOptimizeCollectionView alloc] initWithFrame:UIScreen.mainScreen.bounds 40 | collectionViewLayout:self.collectionViewLayout]; 41 | 42 | // self.collectionView = [[UICollectionView alloc] initWithFrame:UIScreen.mainScreen.bounds 43 | // collectionViewLayout:self.collectionViewLayout]; 44 | } 45 | 46 | - (void)viewDidLoad { 47 | [super viewDidLoad]; 48 | 49 | // Register cell classes 50 | [self.collectionView registerClass:[NETextCollectionViewCell class] forCellWithReuseIdentifier:NSStringFromClass(NETextCollectionViewCell.class)]; 51 | [self.collectionView registerClass:[NEHeaderCollectionReusableView class] 52 | forSupplementaryViewOfKind:UICollectionElementKindSectionHeader 53 | withReuseIdentifier:NSStringFromClass(NEHeaderCollectionReusableView.class)]; 54 | [self.collectionView registerClass:[NEFooterCollectionReusableView class] 55 | forSupplementaryViewOfKind:UICollectionElementKindSectionFooter 56 | withReuseIdentifier:NSStringFromClass(NEFooterCollectionReusableView.class)]; 57 | [self.collectionView registerClass:[UICollectionReusableView class] 58 | forSupplementaryViewOfKind:NECollectionElementKindSectionBackground 59 | withReuseIdentifier:NSStringFromClass(UICollectionReusableView.class)]; 60 | 61 | self.dataSourece = [NSMutableArray new]; 62 | 63 | for (int i = 0; i < 1; i++) { 64 | NSMutableArray *section = [NSMutableArray new]; 65 | for (int r = 0; r < 20; r++) { 66 | [section addObject:@{ 67 | @"title" : [NSString stringWithFormat:@"%d-%d", i, r] 68 | }]; 69 | } 70 | [self.dataSourece addObject:section]; 71 | } 72 | 73 | } 74 | 75 | #pragma mark 76 | 77 | - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { 78 | return self.dataSourece.count; 79 | } 80 | 81 | 82 | - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { 83 | return [self.dataSourece[section] count]; 84 | } 85 | 86 | - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { 87 | NETextCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:NSStringFromClass(NETextCollectionViewCell.class) forIndexPath:indexPath]; 88 | cell.backgroundColor = [UIColor grayColor]; 89 | cell.textLabel.text = self.dataSourece[indexPath.section][indexPath.item][@"title"]; 90 | return cell; 91 | } 92 | 93 | - (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath { 94 | if ([kind isEqualToString:UICollectionElementKindSectionHeader]) { 95 | NEHeaderCollectionReusableView *view = [collectionView dequeueReusableSupplementaryViewOfKind:kind 96 | withReuseIdentifier:NSStringFromClass(NEHeaderCollectionReusableView.class) 97 | forIndexPath:indexPath]; 98 | view.backgroundColor = [UIColor yellowColor]; 99 | view.textLabel.text = [NSString stringWithFormat:@"Header - %@", @(indexPath.section).stringValue]; 100 | return view; 101 | } 102 | else if ([kind isEqualToString:UICollectionElementKindSectionFooter]) { 103 | NEFooterCollectionReusableView *view = [collectionView dequeueReusableSupplementaryViewOfKind:kind 104 | withReuseIdentifier:NSStringFromClass(NEFooterCollectionReusableView.class) 105 | forIndexPath:indexPath]; 106 | view.backgroundColor = [UIColor blueColor]; 107 | view.textLabel.text = [NSString stringWithFormat:@"Footer - %@", @(indexPath.section).stringValue]; 108 | return view; 109 | } 110 | else { 111 | UICollectionReusableView *view = [collectionView dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:NSStringFromClass(UICollectionReusableView.class) forIndexPath:indexPath]; 112 | view.backgroundColor = [UIColor colorWithWhite:1 alpha:0.3]; 113 | view.clipsToBounds = YES; 114 | view.layer.cornerRadius = 10; 115 | return view; 116 | } 117 | return nil; 118 | } 119 | 120 | - (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section { 121 | return UIEdgeInsetsMake(20, 20, 20, 20); 122 | } 123 | 124 | - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section { 125 | return CGSizeMake(collectionView.frame.size.width, 60); 126 | } 127 | 128 | - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section { 129 | return CGSizeMake(collectionView.frame.size.width, 30); 130 | } 131 | 132 | - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath { 133 | return CGSizeMake(collectionView.frame.size.width / 6, indexPath.item % 3 ? 88 : 44); 134 | } 135 | 136 | - (NECollectionViewFlowLayoutAlignment)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)layout alignVerticalForSectionAtIndex:(NSInteger)section { 137 | return section % 3; 138 | } 139 | 140 | - (NECollectionViewFlowLayoutAlignment)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)layout alignHorizontalForSectionAtIndex:(NSInteger)section { 141 | return section % 4; 142 | } 143 | 144 | - (void)collectionView:(UICollectionView *)collectionView moveItemAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath { 145 | [self.dataSourece.firstObject exchangeObjectAtIndex:sourceIndexPath.item withObjectAtIndex:destinationIndexPath.item]; 146 | } 147 | 148 | - (BOOL)collectionView:(UICollectionView *)collectionView canMoveItemAtIndexPath:(NSIndexPath *)indexPath { 149 | return YES; 150 | } 151 | 152 | @end 153 | -------------------------------------------------------------------------------- /NECollectionViewLayout/Classes/NECollectionViewFlowLayout/private/NECollectionViewFlowLayoutLine.h: -------------------------------------------------------------------------------- 1 | // 2 | // NECollectionViewFlowLayoutLine.h 3 | // Pods 4 | // 5 | // Created by Daniel on 2019/12/3. 6 | // 7 | 8 | #ifndef NECollectionViewFlowLayoutLine_h 9 | #define NECollectionViewFlowLayoutLine_h 10 | 11 | #include 12 | #include "NECollectionViewFlowLayoutItem.h" 13 | 14 | namespace NE::CollectionViewFlowLayout { 15 | 16 | class Line : public Container { 17 | public: 18 | Line(CGRect bounds, CGFloat spacing, Alignment alignHorizontal, Alignment alignVertical) 19 | : Container(bounds.origin, bounds.size), spacing_(spacing), alignHorizontal_(alignHorizontal), alignVertical_(alignVertical) {} 20 | 21 | 22 | bool pushItem(Item* item) { 23 | if (items_.size() == 0) { 24 | lineWidth_ = item->contentSize().width; 25 | lineHeight_ = item->contentSize().height; 26 | items_.push_back(item); 27 | return true; 28 | } 29 | else { 30 | auto newLineWidth = lineWidth_ + item->contentSize().width + spacing_; 31 | if (newLineWidth > fitSize().width) return false; 32 | 33 | lineWidth_ = newLineWidth; 34 | lineHeight_ = std::max(lineHeight_, item->contentSize().height); 35 | items_.push_back(item); 36 | return true; 37 | } 38 | } 39 | 40 | void calculateLayout() { 41 | if (items_.size() == 0) return ; 42 | if (items_.size() == 1) { 43 | // 44 | auto width = std::min(lineWidth_, fitSize().width); 45 | auto x = origin().x + CalculatePositionWithAlignment(alignHorizontal_, fitSize().width, width); 46 | auto item = items_[0]; 47 | item->setFrame({ 48 | .origin = { 49 | .x = x, 50 | .y = origin().y, 51 | }, 52 | .size = { 53 | .width = width, 54 | .height = item->contentSize().height 55 | } 56 | }); 57 | } 58 | else { 59 | auto origin = this->origin(); 60 | origin.x += CalculatePositionWithAlignment(alignHorizontal_, fitSize().width, lineWidth_); 61 | auto spacing = spacing_; 62 | if (alignHorizontal_ == Alignment::SpacingBetween) { 63 | auto itemsWidth = 0.; 64 | for (auto item : items_) { 65 | itemsWidth += item->contentSize().width; 66 | } 67 | spacing = (fitSize().width - itemsWidth) / (items_.size() - 1); 68 | } 69 | for (auto item : items_) { 70 | item->setFrame({ 71 | .origin = { 72 | .x = origin.x, 73 | .y = origin.y + CalculatePositionWithAlignment(alignVertical_, lineHeight_, item->contentSize().height), 74 | }, 75 | .size = item->contentSize() 76 | }); 77 | origin.x += item->contentSize().width + spacing; 78 | } 79 | } 80 | } 81 | 82 | void newLine(CGFloat lineSpacing) { 83 | setOrigin({ origin().x, lineSpacing + origin().y + lineHeight_ }); 84 | 85 | items_.clear(); 86 | lineWidth_ = lineHeight_ = 0; 87 | } 88 | 89 | CGFloat lineWidth() { return lineWidth_; } 90 | CGFloat lineHeight() { return lineHeight_; } 91 | 92 | ~Line() override {} 93 | private: 94 | CGFloat spacing_{0}; 95 | Alignment alignHorizontal_ = Alignment::Center; 96 | Alignment alignVertical_ = Alignment::Center; 97 | std::vector items_; 98 | 99 | CGFloat lineWidth_{0}; 100 | CGFloat lineHeight_{0}; 101 | 102 | Line(Line&) = delete; 103 | Line& operator=(Line&) = delete; 104 | }; // END Line 105 | 106 | class Column : public Container { 107 | public: 108 | Column(CGRect bounds, CGFloat spacing, Alignment alignHorizontal, Alignment alignVertical) 109 | : Container(bounds.origin, bounds.size), spacing_(spacing), alignHorizontal_(alignHorizontal), alignVertical_(alignVertical) {} 110 | 111 | 112 | bool pushItem(Item* item) { 113 | if (items_.size() == 0) { 114 | columnWidth_ = item->contentSize().width; 115 | columnHeight_ = item->contentSize().height; 116 | items_.push_back(item); 117 | return true; 118 | } 119 | else { 120 | auto newColumnHeight = columnHeight_ + item->contentSize().height + spacing_; 121 | if (newColumnHeight > fitSize().height) return false; 122 | 123 | columnHeight_ = newColumnHeight; 124 | columnWidth_ = std::max(columnWidth_, item->contentSize().width); 125 | items_.push_back(item); 126 | return true; 127 | } 128 | } 129 | 130 | void calculateLayout() { 131 | if (items_.size() == 0) return ; 132 | if (items_.size() == 1) { 133 | // 134 | auto y = origin().y + CalculatePositionWithAlignment(alignVertical_, fitSize().height, columnHeight_); 135 | auto item = items_[0]; 136 | item->setFrame({ 137 | .origin = { 138 | .x = origin().x, 139 | .y = y, 140 | }, 141 | .size = item->contentSize() 142 | }); 143 | } 144 | else { 145 | auto origin = this->origin(); 146 | origin.y += CalculatePositionWithAlignment(alignVertical_, fitSize().height, columnHeight_); 147 | auto spacing = spacing_; 148 | if (alignVertical_ == Alignment::SpacingBetween) { 149 | auto itemsHeight = 0.; 150 | for (auto item : items_) { 151 | itemsHeight += item->contentSize().height; 152 | } 153 | spacing = (fitSize().height - itemsHeight) / (items_.size() - 1); 154 | } 155 | for (auto item : items_) { 156 | item->setFrame({ 157 | .origin = { 158 | .x = origin.x + CalculatePositionWithAlignment(alignHorizontal_, columnWidth_, item->contentSize().width), 159 | .y = origin.y, 160 | }, 161 | .size = item->contentSize() 162 | }); 163 | origin.y += item->contentSize().height + spacing; 164 | } 165 | } 166 | } 167 | 168 | void newColumn(CGFloat lineSpacing) { 169 | setOrigin({ origin().x + lineSpacing + columnWidth_, origin().y}); 170 | 171 | items_.clear(); 172 | columnWidth_ = columnHeight_ = 0; 173 | } 174 | 175 | CGFloat columnWidth() { return columnWidth_; } 176 | CGFloat columnHeight() { return columnHeight_; } 177 | 178 | ~Column() override {} 179 | private: 180 | CGFloat spacing_{0}; 181 | Alignment alignHorizontal_ = Alignment::Center; 182 | Alignment alignVertical_ = Alignment::Center; 183 | std::vector items_; 184 | 185 | CGFloat columnWidth_{0}; 186 | CGFloat columnHeight_{0}; 187 | 188 | Column(Column&) = delete; 189 | Column& operator=(Column&) = delete; 190 | }; 191 | 192 | } 193 | 194 | #endif /* NECollectionViewFlowLayoutLine_h */ 195 | -------------------------------------------------------------------------------- /NECollectionViewLayout/Classes/NECollectionViewFlowLayout/private/NECollectionViewFlowLayoutContext.h: -------------------------------------------------------------------------------- 1 | // 2 | // NECollectionViewFlowLayoutContext.h 3 | // Pods 4 | // 5 | // Created by Daniel on 2019/12/3. 6 | // 7 | 8 | #ifndef NECollectionViewFlowLayoutContext_h 9 | #define NECollectionViewFlowLayoutContext_h 10 | 11 | #include "NECollectionViewDelegateResponds.h" 12 | #import "NECollectionViewFlowLayout.h" 13 | #import "NECollectionViewFlowLayoutAttributes.h" 14 | 15 | namespace NE { 16 | namespace CollectionViewFlowLayout { 17 | class Context { 18 | public: 19 | Context(){} 20 | Context(NECollectionViewFlowLayout *layout) : 21 | layout_(layout), 22 | attributesClass_([[layout class] layoutAttributesClass]), 23 | delegate_(static_cast>(layout.collectionView.delegate)), 24 | collectionView_(layout.collectionView), 25 | responds_(layout.collectionView.delegate) {} 26 | 27 | NSInteger numberOfSections() { 28 | return collectionView_.numberOfSections; 29 | } 30 | 31 | NSInteger numberOfItemsInSection(NSInteger section) { 32 | return [collectionView_ numberOfItemsInSection:section]; 33 | } 34 | 35 | CGSize sizeForItemAtIndexPath(NSIndexPath *indexPath) { 36 | if (responds_.sizeForItemAtIndexPath) { 37 | return [delegate_ collectionView:collectionView_ layout:layout_ sizeForItemAtIndexPath:indexPath]; 38 | } 39 | else { 40 | return layout_.itemSize; 41 | } 42 | } 43 | 44 | UIEdgeInsets insetForSectionAtIndex(NSInteger section) { 45 | if (responds_.insetForSectionAtIndex) { 46 | return [delegate_ collectionView:collectionView_ layout:layout_ insetForSectionAtIndex:section]; 47 | } 48 | else { 49 | return layout_.sectionInset; 50 | } 51 | } 52 | 53 | CGFloat minimumLineSpacingForSectionAtIndex(NSInteger section) { 54 | if (responds_.minimumLineSpacingForSectionAtIndex) { 55 | return [delegate_ collectionView:collectionView_ layout:layout_ minimumLineSpacingForSectionAtIndex:section]; 56 | } 57 | else { 58 | return layout_.minimumLineSpacing; 59 | } 60 | } 61 | 62 | CGFloat minimumInteritemSpacingForSectionAtIndex(NSInteger section) { 63 | if (responds_.minimumInteritemSpacingForSectionAtIndex) { 64 | return [delegate_ collectionView:collectionView_ layout:layout_ minimumInteritemSpacingForSectionAtIndex:section]; 65 | } 66 | else { 67 | return layout_.minimumInteritemSpacing; 68 | } 69 | } 70 | 71 | CGSize referenceSizeForHeaderInSection(NSInteger section) { 72 | if (responds_.referenceSizeForHeaderInSection) { 73 | return [delegate_ collectionView:collectionView_ layout:layout_ referenceSizeForHeaderInSection:section]; 74 | } 75 | else { 76 | return layout_.headerReferenceSize; 77 | } 78 | } 79 | 80 | CGSize referenceSizeForFooterInSection(NSInteger section) { 81 | if (responds_.referenceSizeForFooterInSection) { 82 | return [delegate_ collectionView:collectionView_ layout:layout_ referenceSizeForFooterInSection:section]; 83 | } 84 | else { 85 | return layout_.footerReferenceSize; 86 | } 87 | } 88 | 89 | UICollectionViewScrollDirection sectionScrollDirection() { 90 | return layout_.sectionScrollDirection; 91 | } 92 | 93 | bool isCustomSectionWidth() { 94 | return layout_.sectionWidth > 1; 95 | } 96 | 97 | CGFloat sectionWidth() { 98 | return layout_.sectionWidth; 99 | } 100 | 101 | CGFloat sectionSpacing() { 102 | return layout_.sectionSpacing; 103 | } 104 | 105 | #define PropertyList(L) \ 106 | L(NSInteger, additionZIndexForSectionAtIndex, 0) \ 107 | \ 108 | L(bool, backgroundVisibleForSectionAtIndex, false) \ 109 | L(bool, backgroundIncludeSupplementarysForSectionAtIndex, false) \ 110 | L(UIEdgeInsets, backgroundInsetsForSectionAtIndex, UIEdgeInsetsZero) \ 111 | \ 112 | L(Alignment, alignHorizontalForSectionAtIndex, static_cast(layout_.alignHorizontal)) \ 113 | L(Alignment, alignVerticalForSectionAtIndex, static_cast(layout_.alignVertical)) \ 114 | \ 115 | L(ScrollDirection, scrollDirectionForSectionAtIndex, ScrollDirection::Vertical) \ 116 | L(bool, pageEnableForSectionAtIndex, false) \ 117 | L(CGSize, pageSizeForSectionAtIndex, CGSizeZero) \ 118 | L(CGFloat, heightForScrollHorizontalSectionAtIndex, 0.) 119 | 120 | #define PropertyGetter(Type, method_name, default_value) \ 121 | Type method_name(NSUInteger section) {\ 122 | if (responds_.method_name()) {\ 123 | return static_cast([delegate_ collectionView:collectionView_ layout:layout_ method_name:section]);\ 124 | }\ 125 | return default_value;\ 126 | }\ 127 | 128 | PropertyList(PropertyGetter) 129 | 130 | #undef PropertyGetter 131 | #undef PropertyList 132 | 133 | PinToVisibleBounds headerPinToVisibleBoundsForSectionAtIndex(NSUInteger section) { 134 | if (pinToVisibleBoundsEnable() && responds_.headerPinToVisibleBoundsForSectionAtIndex()) { 135 | return static_cast([delegate_ collectionView:collectionView_ 136 | layout:layout_ 137 | headerPinToVisibleBoundsForSectionAtIndex:section]); 138 | } 139 | return PinToVisibleBounds::None; 140 | } 141 | 142 | PinToVisibleBounds footerPinToVisibleBoundsForSectionAtIndex(NSUInteger section) { 143 | if (pinToVisibleBoundsEnable() && responds_.footerPinToVisibleBoundsForSectionAtIndex()) { 144 | return static_cast([delegate_ collectionView:collectionView_ 145 | layout:layout_ 146 | footerPinToVisibleBoundsForSectionAtIndex:section]); 147 | } 148 | return PinToVisibleBounds::None; 149 | } 150 | 151 | NECollectionViewFlowLayoutAttributes *cellAttributes(NSIndexPath *indexPath) { 152 | NECollectionViewFlowLayoutAttributes* attr = [[[layout_ class] layoutAttributesClass] layoutAttributesForCellWithIndexPath:indexPath]; 153 | attr.delegate = (id)layout_; 154 | return attr; 155 | } 156 | NECollectionViewFlowLayoutAttributes *supplementaryAttributes(NSString *kind, NSIndexPath *indexPath) { 157 | NECollectionViewFlowLayoutAttributes* attr = [[[layout_ class] layoutAttributesClass] layoutAttributesForSupplementaryViewOfKind:kind withIndexPath:indexPath]; 158 | attr.delegate = (id)layout_; 159 | return attr; 160 | } 161 | NECollectionViewFlowLayoutAttributes *decorationAttributes(NSString *kind, NSIndexPath *indexPath) { 162 | NECollectionViewFlowLayoutAttributes* attr = [[[layout_ class] layoutAttributesClass] layoutAttributesForDecorationViewOfKind:kind withIndexPath:indexPath]; 163 | attr.delegate = (id)layout_; 164 | return attr; 165 | } 166 | 167 | 168 | private: 169 | __weak NECollectionViewFlowLayout *layout_ = nil; 170 | __weak id delegate_ = nil; 171 | __weak UICollectionView *collectionView_ = nil; 172 | NECollectionViewFlowLayoutResponds responds_ = nil; 173 | Class attributesClass_ = Nil; 174 | 175 | bool pinToVisibleBoundsEnable() { return layout_.pinToVisibleBoundsEnable; } 176 | }; 177 | } 178 | } 179 | 180 | #endif /* NECollectionViewFlowLayoutContext_h */ 181 | -------------------------------------------------------------------------------- /Example/NECollectionViewLayout/NEMoveCollectionViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // NEMoveCollectionViewController.m 3 | // NECollectionViewLayout_Example 4 | // 5 | // Created by Daniel on 2019/12/9. 6 | // Copyright © 2019 Daniel. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "NETextCollectionViewCell.h" 12 | #import "NEHeaderCollectionReusableView.h" 13 | #import "NEFooterCollectionReusableView.h" 14 | #import "NEMoveCollectionViewController.h" 15 | 16 | @interface NEMoveCollectionViewController () 17 | 18 | @property (nonatomic, strong) NSMutableArray *> *dataSourece; 19 | 20 | @end 21 | 22 | @implementation NEMoveCollectionViewController 23 | 24 | - (instancetype)init 25 | { 26 | NECollectionViewFlowLayout *layout = [NECollectionViewFlowLayout new]; 27 | layout.pinToVisibleBoundsEnable = YES; 28 | // UICollectionViewFlowLayout *layout = [UICollectionViewFlowLayout new]; 29 | layout.minimumLineSpacing = 10; 30 | layout.minimumInteritemSpacing = 10; 31 | // UICollectionViewLayout *layout = [NECollectionViewFlowLayout new]; 32 | self = [self initWithCollectionViewLayout:layout]; 33 | if (self) { 34 | 35 | } 36 | return self; 37 | } 38 | 39 | - (void)loadView { 40 | self.collectionView = [[NEOptimizeCollectionView alloc] initWithFrame:UIScreen.mainScreen.bounds 41 | collectionViewLayout:self.collectionViewLayout]; 42 | self.collectionView.backgroundColor = UIColor.blackColor; 43 | 44 | // self.collectionView = [[UICollectionView alloc] initWithFrame:UIScreen.mainScreen.bounds 45 | // collectionViewLayout:self.collectionViewLayout]; 46 | } 47 | 48 | - (void)viewDidLoad { 49 | [super viewDidLoad]; 50 | 51 | __auto_type moveItem = [[UIBarButtonItem alloc] initWithTitle:@"item" style:UIBarButtonItemStylePlain target:self action:@selector(moveItem)]; 52 | __auto_type moveSection = [[UIBarButtonItem alloc] initWithTitle:@"section" style:UIBarButtonItemStylePlain target:self action:@selector(moveSection)]; 53 | self.navigationItem.rightBarButtonItems = @[moveSection, moveItem]; 54 | 55 | // Register cell classes 56 | [self.collectionView registerClass:[NETextCollectionViewCell class] forCellWithReuseIdentifier:NSStringFromClass(NETextCollectionViewCell.class)]; 57 | [self.collectionView registerClass:[NEHeaderCollectionReusableView class] 58 | forSupplementaryViewOfKind:UICollectionElementKindSectionHeader 59 | withReuseIdentifier:NSStringFromClass(NEHeaderCollectionReusableView.class)]; 60 | [self.collectionView registerClass:[NEFooterCollectionReusableView class] 61 | forSupplementaryViewOfKind:UICollectionElementKindSectionFooter 62 | withReuseIdentifier:NSStringFromClass(NEFooterCollectionReusableView.class)]; 63 | [self.collectionView registerClass:[UICollectionReusableView class] 64 | forSupplementaryViewOfKind:NECollectionElementKindSectionBackground 65 | withReuseIdentifier:NSStringFromClass(UICollectionReusableView.class)]; 66 | 67 | self.dataSourece = [NSMutableArray new]; 68 | 69 | for (int i = 0; i < 3; i++) { 70 | NSMutableArray *section = [NSMutableArray new]; 71 | for (int r = 0; r < 10; r++) { 72 | [section addObject:@{ 73 | @"title" : [NSString stringWithFormat:@"%d-%d", i, r] 74 | }]; 75 | } 76 | [self.dataSourece addObject:section]; 77 | } 78 | 79 | } 80 | 81 | #pragma mark 82 | 83 | - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { 84 | return self.dataSourece.count; 85 | } 86 | 87 | 88 | - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { 89 | return [self.dataSourece[section] count]; 90 | } 91 | 92 | - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { 93 | NETextCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:NSStringFromClass(NETextCollectionViewCell.class) forIndexPath:indexPath]; 94 | cell.backgroundColor = [UIColor grayColor]; 95 | cell.textLabel.text = self.dataSourece[indexPath.section][indexPath.item][@"title"]; 96 | return cell; 97 | } 98 | 99 | - (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath { 100 | if ([kind isEqualToString:UICollectionElementKindSectionHeader]) { 101 | NEHeaderCollectionReusableView *view = [collectionView dequeueReusableSupplementaryViewOfKind:kind 102 | withReuseIdentifier:NSStringFromClass(NEHeaderCollectionReusableView.class) 103 | forIndexPath:indexPath]; 104 | view.backgroundColor = [UIColor yellowColor]; 105 | view.textLabel.text = [NSString stringWithFormat:@"Header - %@", @(indexPath.section).stringValue]; 106 | return view; 107 | } 108 | else if ([kind isEqualToString:UICollectionElementKindSectionFooter]) { 109 | NEFooterCollectionReusableView *view = [collectionView dequeueReusableSupplementaryViewOfKind:kind 110 | withReuseIdentifier:NSStringFromClass(NEFooterCollectionReusableView.class) 111 | forIndexPath:indexPath]; 112 | view.backgroundColor = [UIColor blueColor]; 113 | view.textLabel.text = [NSString stringWithFormat:@"Footer - %@", @(indexPath.section).stringValue]; 114 | return view; 115 | } 116 | else { 117 | UICollectionReusableView *view = [collectionView dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:NSStringFromClass(UICollectionReusableView.class) forIndexPath:indexPath]; 118 | view.backgroundColor = [UIColor colorWithWhite:1 alpha:0.3]; 119 | view.clipsToBounds = YES; 120 | view.layer.cornerRadius = 10; 121 | return view; 122 | } 123 | return nil; 124 | } 125 | 126 | - (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section { 127 | return UIEdgeInsetsMake(20, 20, 20, 20); 128 | } 129 | 130 | - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section { 131 | return CGSizeMake(collectionView.frame.size.width, 60); 132 | } 133 | 134 | - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section { 135 | return CGSizeMake(collectionView.frame.size.width, 30); 136 | } 137 | 138 | - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath { 139 | return CGSizeMake(collectionView.frame.size.width / 6, 88); 140 | } 141 | 142 | - (NECollectionViewFlowLayoutAlignment)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)layout alignVerticalForSectionAtIndex:(NSInteger)section { 143 | return section % 3; 144 | } 145 | 146 | - (NECollectionViewFlowLayoutAlignment)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)layout alignHorizontalForSectionAtIndex:(NSInteger)section { 147 | return section % 4; 148 | } 149 | 150 | - (void)moveItem { 151 | __auto_type section = self.dataSourece.firstObject; 152 | __auto_type obj = [section objectAtIndex:0]; 153 | [section removeObjectAtIndex:0]; 154 | [section addObject:obj]; 155 | [self.collectionView moveItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0] 156 | toIndexPath:[NSIndexPath indexPathForItem:section.count - 1 inSection:0]]; 157 | } 158 | 159 | - (void)moveSection { 160 | __auto_type obj = self.dataSourece[0]; 161 | [self.dataSourece removeObjectAtIndex:0]; 162 | [self.dataSourece addObject:obj]; 163 | [self.collectionView moveSection:0 toSection:self.dataSourece.count - 1]; 164 | } 165 | 166 | @end 167 | -------------------------------------------------------------------------------- /Example/NECollectionViewLayout/NEScrollDirectionCollectionViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // NEScrollDirectionCollectionViewController.m 3 | // NECollectionViewLayout_Example 4 | // 5 | // Created by Daniel on 2019/12/5. 6 | // Copyright © 2019 Daniel. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "NETextCollectionViewCell.h" 12 | #import "NEHeaderCollectionReusableView.h" 13 | #import "NEFooterCollectionReusableView.h" 14 | #import "NEScrollDirectionCollectionViewController.h" 15 | 16 | @interface NEScrollDirectionCollectionViewController () 17 | 18 | @property (nonatomic, assign) NSInteger sections; 19 | 20 | @end 21 | 22 | @implementation NEScrollDirectionCollectionViewController 23 | 24 | - (instancetype)init 25 | { 26 | NECollectionViewFlowLayout *layout = [NECollectionViewFlowLayout new]; 27 | // UICollectionViewFlowLayout *layout = [UICollectionViewFlowLayout new]; 28 | layout.minimumLineSpacing = 10; 29 | layout.minimumInteritemSpacing = 10; 30 | // UICollectionViewLayout *layout = [NECollectionViewFlowLayout new]; 31 | self = [self initWithCollectionViewLayout:layout]; 32 | if (self) { 33 | _sections = 4; 34 | } 35 | return self; 36 | } 37 | 38 | - (void)deleteSection { 39 | self.sections --; 40 | [self.collectionView deleteSections:[NSIndexSet indexSetWithIndex:self.sections]]; 41 | } 42 | 43 | - (void)loadView { 44 | self.collectionView = [[NEOptimizeCollectionView alloc] initWithFrame:UIScreen.mainScreen.bounds 45 | collectionViewLayout:self.collectionViewLayout]; 46 | self.collectionView.backgroundColor = UIColor.blackColor; 47 | 48 | // self.collectionView = [[UICollectionView alloc] initWithFrame:UIScreen.mainScreen.bounds 49 | // collectionViewLayout:self.collectionViewLayout]; 50 | } 51 | 52 | - (void)viewDidLoad { 53 | [super viewDidLoad]; 54 | 55 | self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"delete" style:UIBarButtonItemStylePlain target:self action:@selector(deleteSection)]; 56 | 57 | // Register cell classes 58 | [self.collectionView registerClass:[NETextCollectionViewCell class] forCellWithReuseIdentifier:NSStringFromClass(NETextCollectionViewCell.class)]; 59 | [self.collectionView registerClass:[NEHeaderCollectionReusableView class] 60 | forSupplementaryViewOfKind:UICollectionElementKindSectionHeader 61 | withReuseIdentifier:NSStringFromClass(NEHeaderCollectionReusableView.class)]; 62 | [self.collectionView registerClass:[NEFooterCollectionReusableView class] 63 | forSupplementaryViewOfKind:UICollectionElementKindSectionFooter 64 | withReuseIdentifier:NSStringFromClass(NEFooterCollectionReusableView.class)]; 65 | 66 | } 67 | 68 | #pragma mark 69 | 70 | - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { 71 | return self.sections; 72 | } 73 | 74 | 75 | - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { 76 | return 20; 77 | } 78 | 79 | - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { 80 | NETextCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:NSStringFromClass(NETextCollectionViewCell.class) forIndexPath:indexPath]; 81 | cell.backgroundColor = [UIColor grayColor]; 82 | cell.textLabel.text = [NSString stringWithFormat:@"%@-%@", @(indexPath.section).stringValue, @(indexPath.item).stringValue]; 83 | return cell; 84 | } 85 | 86 | - (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath { 87 | if ([kind isEqualToString:UICollectionElementKindSectionHeader]) { 88 | NEHeaderCollectionReusableView *view = [collectionView dequeueReusableSupplementaryViewOfKind:kind 89 | withReuseIdentifier:NSStringFromClass(NEHeaderCollectionReusableView.class) 90 | forIndexPath:indexPath]; 91 | view.backgroundColor = [UIColor yellowColor]; 92 | view.textLabel.text = [NSString stringWithFormat:@"Header - %@", @(indexPath.section).stringValue]; 93 | return view; 94 | } 95 | else if ([kind isEqualToString:UICollectionElementKindSectionFooter]) { 96 | NEFooterCollectionReusableView *view = [collectionView dequeueReusableSupplementaryViewOfKind:kind 97 | withReuseIdentifier:NSStringFromClass(NEFooterCollectionReusableView.class) 98 | forIndexPath:indexPath]; 99 | view.backgroundColor = [UIColor blueColor]; 100 | view.textLabel.text = [NSString stringWithFormat:@"Footer - %@", @(indexPath.section).stringValue]; 101 | return view; 102 | } 103 | return nil; 104 | } 105 | 106 | - (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section { 107 | return UIEdgeInsetsMake(10, 15, 10, 15); 108 | } 109 | 110 | - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section { 111 | return CGSizeMake(collectionView.frame.size.width, 60); 112 | } 113 | 114 | - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section { 115 | return CGSizeMake(collectionView.frame.size.width, 30); 116 | } 117 | 118 | - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath { 119 | return CGSizeMake(collectionView.frame.size.width / 5, indexPath.item % 3 ? 44 : 88); 120 | } 121 | 122 | - (NECollectionViewFlowLayoutAlignment)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)layout alignVerticalForSectionAtIndex:(NSInteger)section { 123 | return section % 4; 124 | } 125 | 126 | - (NECollectionViewFlowLayoutAlignment)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)layout alignHorizontalForSectionAtIndex:(NSInteger)section { 127 | return section % 4; 128 | } 129 | 130 | #pragma mark - Scroll direction delegate & page enable & page size 131 | 132 | - (UICollectionViewScrollDirection)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)layout scrollDirectionForSectionAtIndex:(NSInteger)section { 133 | return UICollectionViewScrollDirectionHorizontal; 134 | } 135 | 136 | - (BOOL)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)layout pageEnableForSectionAtIndex:(NSInteger)section { 137 | return YES; 138 | } 139 | 140 | - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)layout pageSizeForSectionAtIndex:(NSInteger)section { 141 | return CGSizeMake(collectionView.frame.size.width * 0.8, 1); 142 | } 143 | 144 | - (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)layout heightForScrollHorizontalSectionAtIndex:(NSInteger)section { 145 | return 44 * 3 + 10 + 10; 146 | } 147 | 148 | - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath { 149 | 150 | 151 | // [collectionView reloadItemsAtIndexPaths:@[indexPath]]; 152 | // [collectionView reloadData]; 153 | [CATransaction begin]; 154 | [CATransaction setDisableActions:YES]; 155 | CFTimeInterval t1 = CACurrentMediaTime(); 156 | [collectionView performBatchUpdates:^{ 157 | // __auto_type ctx = [UICollectionViewFlowLayoutInvalidationContext new]; 158 | // [ctx invalidateItemsAtIndexPaths:@[indexPath]]; 159 | // [collectionView.collectionViewLayout invalidateLayoutWithContext:ctx]; 160 | [collectionView reloadItemsAtIndexPaths:@[indexPath]]; 161 | } completion:^(BOOL finished) { 162 | CFTimeInterval t2 = CACurrentMediaTime(); 163 | NSLog(@"duration = %f", t2 - t1); 164 | }]; 165 | [CATransaction commit]; 166 | } 167 | 168 | 169 | @end 170 | -------------------------------------------------------------------------------- /Example/NECollectionViewLayout/NEBackgroundCollectionViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // NEBackgroundCollectionViewController.m 3 | // NECollectionViewLayout_Example 4 | // 5 | // Created by Daniel on 2019/12/5. 6 | // Copyright © 2019 Daniel. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "NETextCollectionViewCell.h" 12 | #import "NEHeaderCollectionReusableView.h" 13 | #import "NEFooterCollectionReusableView.h" 14 | #import "NEBackgroundCollectionViewController.h" 15 | 16 | @interface NEBackgroundCollectionViewController () 17 | 18 | @end 19 | 20 | @implementation NEBackgroundCollectionViewController 21 | 22 | - (instancetype)init 23 | { 24 | NECollectionViewFlowLayout *layout = [NECollectionViewFlowLayout new]; 25 | // UICollectionViewFlowLayout *layout = [UICollectionViewFlowLayout new]; 26 | layout.minimumLineSpacing = 10; 27 | layout.minimumInteritemSpacing = 10; 28 | // UICollectionViewLayout *layout = [NECollectionViewFlowLayout new]; 29 | // layout.sectionScrollDirection = UICollectionViewScrollDirectionHorizontal; 30 | // layout.sectionWidth = 300; 31 | layout.sectionSpacing = 30; 32 | self = [self initWithCollectionViewLayout:layout]; 33 | if (self) { 34 | 35 | } 36 | return self; 37 | } 38 | 39 | - (void)loadView { 40 | self.collectionView = [[UICollectionView alloc] initWithFrame:UIScreen.mainScreen.bounds 41 | collectionViewLayout:self.collectionViewLayout]; 42 | self.collectionView.backgroundColor = UIColor.blackColor; 43 | // self.collectionView = [[UICollectionView alloc] initWithFrame:UIScreen.mainScreen.bounds 44 | // collectionViewLayout:self.collectionViewLayout]; 45 | } 46 | 47 | - (void)viewDidLoad { 48 | [super viewDidLoad]; 49 | 50 | // Register cell classes 51 | [self.collectionView registerClass:[NETextCollectionViewCell class] forCellWithReuseIdentifier:NSStringFromClass(NETextCollectionViewCell.class)]; 52 | [self.collectionView registerClass:[NEHeaderCollectionReusableView class] 53 | forSupplementaryViewOfKind:UICollectionElementKindSectionHeader 54 | withReuseIdentifier:NSStringFromClass(NEHeaderCollectionReusableView.class)]; 55 | [self.collectionView registerClass:[NEFooterCollectionReusableView class] 56 | forSupplementaryViewOfKind:UICollectionElementKindSectionFooter 57 | withReuseIdentifier:NSStringFromClass(NEFooterCollectionReusableView.class)]; 58 | [self.collectionView registerClass:[UICollectionReusableView class] 59 | forSupplementaryViewOfKind:NECollectionElementKindSectionBackground 60 | withReuseIdentifier:NSStringFromClass(UICollectionReusableView.class)]; 61 | 62 | } 63 | 64 | #pragma mark 65 | 66 | - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { 67 | return 4; 68 | } 69 | 70 | 71 | - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { 72 | return 20; 73 | } 74 | 75 | - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { 76 | NETextCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:NSStringFromClass(NETextCollectionViewCell.class) forIndexPath:indexPath]; 77 | cell.backgroundColor = [UIColor grayColor]; 78 | cell.textLabel.text = [NSString stringWithFormat:@"%@-%@", @(indexPath.section).stringValue, @(indexPath.item).stringValue]; 79 | return cell; 80 | } 81 | 82 | - (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath { 83 | if ([kind isEqualToString:UICollectionElementKindSectionHeader]) { 84 | NEHeaderCollectionReusableView *view = [collectionView dequeueReusableSupplementaryViewOfKind:kind 85 | withReuseIdentifier:NSStringFromClass(NEHeaderCollectionReusableView.class) 86 | forIndexPath:indexPath]; 87 | view.backgroundColor = [UIColor yellowColor]; 88 | view.textLabel.text = [NSString stringWithFormat:@"Header - %@", @(indexPath.section).stringValue]; 89 | return view; 90 | } 91 | else if ([kind isEqualToString:UICollectionElementKindSectionFooter]) { 92 | NEFooterCollectionReusableView *view = [collectionView dequeueReusableSupplementaryViewOfKind:kind 93 | withReuseIdentifier:NSStringFromClass(NEFooterCollectionReusableView.class) 94 | forIndexPath:indexPath]; 95 | view.backgroundColor = [UIColor blueColor]; 96 | view.textLabel.text = [NSString stringWithFormat:@"Footer - %@", @(indexPath.section).stringValue]; 97 | return view; 98 | } 99 | else { 100 | UICollectionReusableView *view = [collectionView dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:NSStringFromClass(UICollectionReusableView.class) forIndexPath:indexPath]; 101 | view.backgroundColor = [UIColor colorWithWhite:1 alpha:0.3]; 102 | view.clipsToBounds = YES; 103 | view.layer.cornerRadius = 10; 104 | return view; 105 | } 106 | return nil; 107 | } 108 | 109 | - (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section { 110 | return UIEdgeInsetsMake(20, 20, 20, 20); 111 | } 112 | 113 | - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section { 114 | return CGSizeMake(collectionView.frame.size.width, 60); 115 | } 116 | 117 | - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section { 118 | return CGSizeMake(collectionView.frame.size.width, 30); 119 | } 120 | 121 | - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath { 122 | return CGSizeMake(collectionView.frame.size.width / 6, indexPath.item % 3 ? 44 : 88); 123 | } 124 | 125 | - (NECollectionViewFlowLayoutAlignment)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)layout alignVerticalForSectionAtIndex:(NSInteger)section { 126 | return section % 3; 127 | } 128 | 129 | - (NECollectionViewFlowLayoutAlignment)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)layout alignHorizontalForSectionAtIndex:(NSInteger)section { 130 | return section % 4; 131 | } 132 | 133 | 134 | - (BOOL)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)layout backgroundVisibleForSectionAtIndex:(NSInteger)section { 135 | return YES; 136 | } 137 | 138 | - (BOOL)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)layout backgroundIncludeSupplementarysForSectionAtIndex:(NSInteger)section { 139 | return NO; 140 | } 141 | 142 | - (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)layout backgroundInsetsForSectionAtIndex:(NSInteger)section { 143 | return UIEdgeInsetsMake(-10, -10, -10, -10); 144 | } 145 | 146 | - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath { 147 | 148 | 149 | // [collectionView reloadItemsAtIndexPaths:@[indexPath]]; 150 | // [collectionView reloadData]; 151 | [CATransaction begin]; 152 | [CATransaction setDisableActions:YES]; 153 | CFTimeInterval t1 = CACurrentMediaTime(); 154 | [collectionView performBatchUpdates:^{ 155 | // __auto_type ctx = [UICollectionViewFlowLayoutInvalidationContext new]; 156 | // [ctx invalidateItemsAtIndexPaths:@[indexPath]]; 157 | // [collectionView.collectionViewLayout invalidateLayoutWithContext:ctx]; 158 | [collectionView reloadItemsAtIndexPaths:@[indexPath]]; 159 | } completion:^(BOOL finished) { 160 | CFTimeInterval t2 = CACurrentMediaTime(); 161 | NSLog(@"duration = %f", t2 - t1); 162 | }]; 163 | [CATransaction commit]; 164 | } 165 | 166 | 167 | @end 168 | -------------------------------------------------------------------------------- /NECollectionViewLayout/Classes/NECollectionViewFlowLayout/private/NECollectionViewFlowLayoutItem.h: -------------------------------------------------------------------------------- 1 | // 2 | // NECollectionViewFlowLayoutItem.h 3 | // Pods 4 | // 5 | // Created by Daniel on 2019/12/3. 6 | // 7 | 8 | #ifndef NECollectionViewFlowLayoutItem_h 9 | #define NECollectionViewFlowLayoutItem_h 10 | 11 | #import 12 | #include "NECollectionViewFlowLayoutNode.h" 13 | #include "NECollectionViewLayoutHelpers.h" 14 | #include "NECollectionViewFlowLayoutContext.h" 15 | 16 | namespace NE::CollectionViewFlowLayout { 17 | 18 | constexpr NSInteger HeaderZIndex = 10; 19 | constexpr NSInteger FooterZIndex = 10; 20 | constexpr NSInteger ItemZIndex = 0; 21 | constexpr NSInteger BackgroundZIndex = -10; 22 | constexpr NSInteger ScrollContentZIndex = -10; 23 | 24 | struct ItemAttributesTraits { 25 | constexpr static NSInteger zIndex = ItemZIndex; 26 | static auto attributes(Context& ctx, NSIndexPath* indexPath) { 27 | return ctx.cellAttributes(indexPath); 28 | } 29 | static auto contentSize(Context& ctx, NSIndexPath* indexPath) { 30 | return ctx.sizeForItemAtIndexPath(indexPath); 31 | } 32 | }; 33 | 34 | struct HeaderAttributesTraits { 35 | constexpr static NSInteger zIndex = HeaderZIndex; 36 | static auto attributes(Context& ctx, NSIndexPath* indexPath) { 37 | return ctx.supplementaryAttributes(UICollectionElementKindSectionHeader, indexPath); 38 | } 39 | static auto contentSize(Context& ctx, NSIndexPath* indexPath) { 40 | return ctx.referenceSizeForHeaderInSection(indexPath.section); 41 | } 42 | }; 43 | 44 | struct FooterAttributesTraits { 45 | constexpr static NSInteger zIndex = FooterZIndex; 46 | static auto attributes(Context& ctx, NSIndexPath* indexPath) { 47 | return ctx.supplementaryAttributes(UICollectionElementKindSectionFooter, indexPath); 48 | } 49 | static auto contentSize(Context& ctx, NSIndexPath* indexPath) { 50 | return ctx.referenceSizeForFooterInSection(indexPath.section); 51 | } 52 | }; 53 | 54 | extern "C" NSString * const NECollectionElementKindSectionBackground; 55 | struct BackgroundAttributesTraits { 56 | constexpr static NSInteger zIndex = BackgroundZIndex; 57 | static auto attributes(Context& ctx, NSIndexPath* indexPath) { 58 | return ctx.supplementaryAttributes(NECollectionElementKindSectionBackground, indexPath); 59 | } 60 | static auto contentSize(Context& ctx, NSIndexPath* indexPath) { 61 | return CGSizeZero; 62 | } 63 | }; 64 | 65 | extern "C" NSString * const NECollectionElementKindSectionScrollContent; 66 | struct ScrollContentAttributesTraits { 67 | constexpr static NSInteger zIndex = ScrollContentZIndex; 68 | static auto attributes(Context& ctx, NSIndexPath* indexPath) { 69 | return ctx.decorationAttributes(NECollectionElementKindSectionScrollContent, indexPath); 70 | } 71 | static auto contentSize(Context& ctx, NSIndexPath* indexPath) { 72 | return CGSizeZero; 73 | } 74 | }; 75 | 76 | template 77 | class AttributesContent : public Content { 78 | public: 79 | AttributesContent() : Content(), zIndex_(Traits::zIndex) {} 80 | AttributesContent(const CGPoint& origin) : Content(origin), zIndex_(Traits::zIndex) {} 81 | AttributesContent(const AttributesContent& other) : Content(other), 82 | zIndex_(other.zIndex_), 83 | dataSourceDirty_(other.dataSourceDirty_), 84 | indexPath_(other.indexPath_) { }; 85 | AttributesContent& operator=(const AttributesContent& other) { 86 | if (this == &other) return *this; 87 | Content::operator=(other); 88 | zIndex_ = other.zIndex_; 89 | dataSourceDirty_ = other.dataSourceDirty_; 90 | indexPath_ = other.indexPath_; 91 | return *this; 92 | }; 93 | 94 | void setIndexPath(IndexPath indexPath) { 95 | indexPath_ = indexPath; 96 | attributes_ = nil; // When indexPath changed, attributes MUST create a new one. 97 | } 98 | IndexPath indexPath() const { return { static_cast(indexPath_.section), static_cast(indexPath_.item) }; } 99 | 100 | void setZIndex(NSInteger zIndex) { 101 | zIndex_ = zIndex + Traits::zIndex; 102 | } 103 | NSInteger zIndex() const { return zIndex_; } 104 | 105 | template 106 | void refreshSizeFromDelegate(Context& ctx) { 107 | if (force || dataSourceDirty()) { 108 | setContentSize(Traits::contentSize(ctx, indexPath_)); 109 | } 110 | clearDataSourceDirty(); 111 | } 112 | 113 | virtual NECollectionViewFlowLayoutAttributes* attributes(Context& ctx) { 114 | if (attributes_ == nil) { 115 | attributes_ = Traits::attributes(ctx, indexPath_); 116 | } 117 | attributes_.frame = frame(); 118 | attributes_.zIndex = zIndex_; 119 | return attributes_; 120 | } 121 | 122 | void markDataSourceDirty() { dataSourceDirty_ = true; } 123 | void clearDataSourceDirty() { dataSourceDirty_ = false; } 124 | bool dataSourceDirty() { return dataSourceDirty_; } 125 | 126 | private: 127 | NSInteger zIndex_ = 0; 128 | bool dataSourceDirty_ = true; 129 | __strong NSIndexPath *indexPath_; 130 | __strong NECollectionViewFlowLayoutAttributes *attributes_; 131 | }; // END Item 132 | 133 | using Item = AttributesContent; 134 | using Header = AttributesContent; 135 | using Footer = AttributesContent; 136 | using Background = AttributesContent; 137 | 138 | class ScrollContent : public AttributesContent { 139 | using Super = AttributesContent; 140 | public: 141 | using AttributesContent::AttributesContent; 142 | ScrollContent(const ScrollContent&) = default; 143 | ScrollContent& operator=(const ScrollContent&) = default; 144 | 145 | void setContentOffset(const CGPoint& contentOffset) { 146 | contentOffset_ = contentOffset; 147 | } 148 | const CGPoint& contentOffset() const { 149 | return contentOffset_; 150 | } 151 | 152 | void setContentSize(const CGSize& contentSize) { 153 | contentSize_ = contentSize; 154 | } 155 | const CGSize& contentSize() const { 156 | return contentSize_; 157 | } 158 | 159 | std::pair adjustContentOffsetToVisible() { 160 | if (pageEnable()) { 161 | auto pageWidth = pageSize_.width < 1 ? frame().size.width : pageSize_.width; 162 | auto index = static_cast(round(contentOffset_.x / pageWidth)); 163 | auto x = MAX(0, MIN(pageWidth * index, contentSize_.width - frame().size.width)); 164 | return { contentOffset_.x != x, { x, contentOffset_.y } }; 165 | } 166 | else { 167 | auto x = MAX(0, MIN(contentOffset_.x, contentSize_.width - frame().size.width)); 168 | return { x != contentOffset_.x, { x, contentOffset_.y } }; 169 | } 170 | } 171 | 172 | void setPageEnable(bool enable) { pageEnable_ = enable; } 173 | bool pageEnable() const { return pageEnable_; } 174 | 175 | void setPageSize(const CGSize size) { pageSize_ = size; } 176 | const CGSize& pageSize() const { return pageSize_; } 177 | 178 | NECollectionViewFlowLayoutAttributes* attributes(Context& ctx) override { 179 | auto attributes = Super::attributes(ctx); 180 | attributes.contentOffset = contentOffset(); 181 | attributes.contentSize = contentSize(); 182 | attributes.pageEnable = pageEnable_; 183 | attributes.pageSize = pageSize_; 184 | return attributes; 185 | } 186 | 187 | private: 188 | CGPoint contentOffset_ {0}; 189 | CGSize contentSize_ {0}; 190 | bool pageEnable_ = false; 191 | CGSize pageSize_ {0}; 192 | }; 193 | } 194 | 195 | #endif /* NECollectionViewFlowLayoutItem_h */ 196 | -------------------------------------------------------------------------------- /Example/NECollectionViewLayout/NEInsertCollectionViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // NEInsertCollectionViewController.m 3 | // NECollectionViewLayout_Example 4 | // 5 | // Created by Daniel on 2019/12/9. 6 | // Copyright © 2019 Daniel. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "NETextCollectionViewCell.h" 12 | #import "NEHeaderCollectionReusableView.h" 13 | #import "NEFooterCollectionReusableView.h" 14 | #import "NEInsertCollectionViewController.h" 15 | 16 | @interface NEInsertCollectionViewController () 17 | 18 | @property (nonatomic, strong) NSMutableArray *> *dataSourece; 19 | 20 | @end 21 | 22 | @implementation NEInsertCollectionViewController 23 | 24 | - (instancetype)init 25 | { 26 | NECollectionViewFlowLayout *layout = [NECollectionViewFlowLayout new]; 27 | layout.pinToVisibleBoundsEnable = YES; 28 | layout.appearenceAnimator = [NECollectionViewFlowLayoutScaleAnimator new]; 29 | // UICollectionViewFlowLayout *layout = [UICollectionViewFlowLayout new]; 30 | layout.minimumLineSpacing = 10; 31 | layout.minimumInteritemSpacing = 10; 32 | // UICollectionViewLayout *layout = [NECollectionViewFlowLayout new]; 33 | self = [self initWithCollectionViewLayout:layout]; 34 | if (self) { 35 | 36 | } 37 | return self; 38 | } 39 | 40 | - (void)loadView { 41 | self.collectionView = [[UICollectionView alloc] initWithFrame:UIScreen.mainScreen.bounds 42 | collectionViewLayout:self.collectionViewLayout]; 43 | self.collectionView.backgroundColor = UIColor.blackColor; 44 | // self.collectionView = [[UICollectionView alloc] initWithFrame:UIScreen.mainScreen.bounds 45 | // collectionViewLayout:self.collectionViewLayout]; 46 | } 47 | 48 | - (void)viewDidLoad { 49 | [super viewDidLoad]; 50 | 51 | __auto_type insertItem = [[UIBarButtonItem alloc] initWithTitle:@"item" style:UIBarButtonItemStylePlain target:self action:@selector(insertItem)]; 52 | __auto_type insertSection = [[UIBarButtonItem alloc] initWithTitle:@"section" style:UIBarButtonItemStylePlain target:self action:@selector(insertSection)]; 53 | __auto_type reload = [[UIBarButtonItem alloc] initWithTitle:@"reload" style:UIBarButtonItemStylePlain target:self.collectionView action:@selector(reloadData)]; 54 | self.navigationItem.rightBarButtonItems = @[insertSection, insertItem, reload]; 55 | 56 | // Register cell classes 57 | [self.collectionView registerClass:[NETextCollectionViewCell class] forCellWithReuseIdentifier:NSStringFromClass(NETextCollectionViewCell.class)]; 58 | [self.collectionView registerClass:[NEHeaderCollectionReusableView class] 59 | forSupplementaryViewOfKind:UICollectionElementKindSectionHeader 60 | withReuseIdentifier:NSStringFromClass(NEHeaderCollectionReusableView.class)]; 61 | [self.collectionView registerClass:[NEFooterCollectionReusableView class] 62 | forSupplementaryViewOfKind:UICollectionElementKindSectionFooter 63 | withReuseIdentifier:NSStringFromClass(NEFooterCollectionReusableView.class)]; 64 | [self.collectionView registerClass:[UICollectionReusableView class] 65 | forSupplementaryViewOfKind:NECollectionElementKindSectionBackground 66 | withReuseIdentifier:NSStringFromClass(UICollectionReusableView.class)]; 67 | 68 | self.dataSourece = [NSMutableArray new]; 69 | 70 | for (int i = 0; i < 2; i++) { 71 | NSMutableArray *section = [NSMutableArray new]; 72 | for (int r = 0; r < 2; r++) { 73 | [section addObject:@{ 74 | @"title" : [NSString stringWithFormat:@"%d-%d", i, r] 75 | }]; 76 | } 77 | [self.dataSourece addObject:section]; 78 | } 79 | 80 | } 81 | 82 | #pragma mark 83 | 84 | - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { 85 | return self.dataSourece.count; 86 | } 87 | 88 | 89 | - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { 90 | return [self.dataSourece[section] count]; 91 | } 92 | 93 | - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { 94 | NETextCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:NSStringFromClass(NETextCollectionViewCell.class) forIndexPath:indexPath]; 95 | cell.backgroundColor = [UIColor grayColor]; 96 | cell.textLabel.text = self.dataSourece[indexPath.section][indexPath.item][@"title"]; 97 | return cell; 98 | } 99 | 100 | - (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath { 101 | if ([kind isEqualToString:UICollectionElementKindSectionHeader]) { 102 | NEHeaderCollectionReusableView *view = [collectionView dequeueReusableSupplementaryViewOfKind:kind 103 | withReuseIdentifier:NSStringFromClass(NEHeaderCollectionReusableView.class) 104 | forIndexPath:indexPath]; 105 | view.backgroundColor = [UIColor yellowColor]; 106 | view.textLabel.text = [NSString stringWithFormat:@"Header - %@", @(indexPath.section).stringValue]; 107 | return view; 108 | } 109 | else if ([kind isEqualToString:UICollectionElementKindSectionFooter]) { 110 | NEFooterCollectionReusableView *view = [collectionView dequeueReusableSupplementaryViewOfKind:kind 111 | withReuseIdentifier:NSStringFromClass(NEFooterCollectionReusableView.class) 112 | forIndexPath:indexPath]; 113 | view.backgroundColor = [UIColor blueColor]; 114 | view.textLabel.text = [NSString stringWithFormat:@"Footer - %@", @(indexPath.section).stringValue]; 115 | return view; 116 | } 117 | else { 118 | UICollectionReusableView *view = [collectionView dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:NSStringFromClass(UICollectionReusableView.class) forIndexPath:indexPath]; 119 | view.backgroundColor = [UIColor colorWithWhite:1 alpha:0.3]; 120 | view.clipsToBounds = YES; 121 | view.layer.cornerRadius = 10; 122 | return view; 123 | } 124 | return nil; 125 | } 126 | 127 | - (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section { 128 | return UIEdgeInsetsMake(20, 20, 20, 20); 129 | } 130 | 131 | - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section { 132 | return CGSizeMake(collectionView.frame.size.width, 60); 133 | } 134 | 135 | - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section { 136 | return CGSizeMake(collectionView.frame.size.width, 30); 137 | } 138 | 139 | - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath { 140 | return CGSizeMake(collectionView.frame.size.width / 6, 88); 141 | } 142 | 143 | - (BOOL)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)layout backgroundVisibleForSectionAtIndex:(NSInteger)section { 144 | return YES; 145 | } 146 | 147 | - (NECollectionViewFlowLayoutAlignment)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)layout alignVerticalForSectionAtIndex:(NSInteger)section { 148 | return section % 3; 149 | } 150 | 151 | - (NECollectionViewFlowLayoutAlignment)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)layout alignHorizontalForSectionAtIndex:(NSInteger)section { 152 | return section % 4; 153 | } 154 | 155 | - (void)insertItem { 156 | [self.dataSourece.firstObject insertObject:@{ 157 | @"title" : @"inserted" 158 | } atIndex:0]; 159 | [self.collectionView insertItemsAtIndexPaths:@[[NSIndexPath indexPathForItem:0 inSection:0]]]; 160 | } 161 | 162 | - (void)insertSection { 163 | NSMutableArray *section = [NSMutableArray new]; 164 | for (int r = 0; r < 10; r++) { 165 | [section addObject:@{ 166 | @"title" : [NSString stringWithFormat:@"inserted-%d", r] 167 | }]; 168 | } 169 | [self.dataSourece insertObject:section atIndex:0]; 170 | [self.collectionView insertSections:[NSIndexSet indexSetWithIndex:0]]; 171 | } 172 | 173 | @end 174 | -------------------------------------------------------------------------------- /NECollectionViewLayout/Classes/NECollectionViewFlowLayout/NECollectionViewFlowLayout.h: -------------------------------------------------------------------------------- 1 | // 2 | // NECollectionViewFlowLayout.h 3 | // Pods-NECollectionViewLayout_Example 4 | // 5 | // Created by Daniel on 2019/11/28. 6 | // 7 | 8 | #import 9 | #import "NEOptimizeCollectionViewLayoutProtocol.h" 10 | #import "NECollectionViewFlowLayoutAnimator.h" 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | typedef NS_ENUM(NSInteger, NECollectionViewFlowLayoutAlignment) { 15 | NECollectionViewFlowLayoutAlignLeading, // left or top 16 | NECollectionViewFlowLayoutAlignTrailing, // right or bottom 17 | NECollectionViewFlowLayoutAlignCenter, // in the center of container 18 | NECollectionViewFlowLayoutAlignSpacingBetween, // leading & trailing with same spacing between items 19 | }; 20 | 21 | static inline NSString *NECollectionViewFlowLayoutAlignmentToReadable(NECollectionViewFlowLayoutAlignment alignment) { 22 | switch (alignment) { 23 | case NECollectionViewFlowLayoutAlignLeading: return @"leading"; 24 | case NECollectionViewFlowLayoutAlignTrailing: return @"trailing"; 25 | case NECollectionViewFlowLayoutAlignCenter: return @"center"; 26 | case NECollectionViewFlowLayoutAlignSpacingBetween: return @"spacing between"; 27 | default: return @"unknow"; 28 | } 29 | } 30 | 31 | typedef NS_ENUM(NSInteger, NECollectionViewFlowLayoutPinToVisibleBounds) { 32 | NECollectionViewFlowLayoutPinToVisibleBoundsNone, // No pinning 33 | NECollectionViewFlowLayoutPinToVisibleBoundsInsideSection, // Pin inside section, the same as UICollectionViewFlowLayout 34 | NECollectionViewFlowLayoutPinToVisibleBoundsAfterSection, // Pin at its section and after its sections, use by header normally. 35 | NECollectionViewFlowLayoutPinToVisibleBoundsBeforeSection, // Pin at its section and before its sections, use by footer normally. 36 | NECollectionViewFlowLayoutPinToVisibleBoundsAlways, // Always pin at collection view. MUST be only one, or its behavior is undefined. 37 | }; 38 | 39 | static inline NSString *NECollectionViewFlowLayoutPinToVisibleBoundsToReadable(NECollectionViewFlowLayoutPinToVisibleBounds type) { 40 | switch (type) { 41 | case NECollectionViewFlowLayoutPinToVisibleBoundsNone: return @"none"; 42 | case NECollectionViewFlowLayoutPinToVisibleBoundsInsideSection: return @"inside section"; 43 | case NECollectionViewFlowLayoutPinToVisibleBoundsAfterSection: return @"after section"; 44 | case NECollectionViewFlowLayoutPinToVisibleBoundsBeforeSection: return @"before section"; 45 | case NECollectionViewFlowLayoutPinToVisibleBoundsAlways: return @"always"; 46 | default: return @"unknow"; 47 | } 48 | } 49 | 50 | #ifdef __cplusplus 51 | extern "C" { 52 | #endif 53 | 54 | UIKIT_EXTERN NSString *const NECollectionElementKindSectionBackground; 55 | 56 | #ifdef __cplusplus 57 | } 58 | #endif 59 | 60 | @protocol NECollectionViewDelegateFlowLayout 61 | @optional 62 | 63 | - (void)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)layout scrollViewDidScrollToContentOffset:(CGPoint)contentOffset forSectionAtIndex:(NSInteger)section; 64 | 65 | #pragma mark - Zindex 66 | /// Addtion z index for all item in the section. For example, default 0 will be ( 0 + addition ) zIndex. 67 | /// May used for more complex custom layout. 68 | - (NSInteger)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)layout additionZIndexForSectionAtIndex:(NSInteger)section; 69 | 70 | #pragma mark - Pin to visible bounds 71 | /// The section header pin to visible bounds config. 72 | - (NECollectionViewFlowLayoutPinToVisibleBounds)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)layout headerPinToVisibleBoundsForSectionAtIndex:(NSInteger)section; 73 | /// The section footer pin to visible bounds config. 74 | - (NECollectionViewFlowLayoutPinToVisibleBounds)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)layout footerPinToVisibleBoundsForSectionAtIndex:(NSInteger)section; 75 | 76 | #pragma mark - Section background support 77 | /// Show a background view in the section below the items. 78 | /// Once return YES, you MUST return a supplementary view of NECollectionElementKindSectionBackground. 79 | - (BOOL)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)layout backgroundVisibleForSectionAtIndex:(NSInteger)section; 80 | 81 | /// Define the background size. If return YES, the size will contains the header & footer. 82 | /// Otherwise it is just the items size, without SectionInsets. 83 | - (BOOL)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)layout backgroundIncludeSupplementarysForSectionAtIndex:(NSInteger)section; 84 | 85 | /// This method gives you a change to modify the background size that calculate by above configs. 86 | - (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)layout backgroundInsetsForSectionAtIndex:(NSInteger)section; 87 | 88 | #pragma mark - Section layout alignment 89 | 90 | /// Declare the horizontal aligment of a line in the section. 91 | - (NECollectionViewFlowLayoutAlignment)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)layout alignHorizontalForSectionAtIndex:(NSInteger)section; 92 | 93 | /// Declare the vertical aligment of a line in the section. 94 | - (NECollectionViewFlowLayoutAlignment)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)layout alignVerticalForSectionAtIndex:(NSInteger)section; 95 | 96 | #pragma mark - Section scroll direction and page 97 | /// Declare the scroll direction of the section. Default Vertical. 98 | - (UICollectionViewScrollDirection)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)layout scrollDirectionForSectionAtIndex:(NSInteger)section; 99 | 100 | /// Declare the content height of the section, when scroll direction Horizontal. Default 0. 101 | /// Return 0 means use items max height, and location in one line. 102 | /// When is not 0, layout will effect by vertical alignment and horizontal alignment. 103 | - (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)layout heightForScrollHorizontalSectionAtIndex:(NSInteger)section; 104 | 105 | /// Enable page scroll in the section. 106 | - (BOOL)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)layout pageEnableForSectionAtIndex:(NSInteger)section; 107 | 108 | /// Decide the page width of the scroll view. Default CGSizeZero. 109 | /// Zero means the page size is equal to the frame. 110 | /// Height is no meaning now, for preversed usage. 111 | - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)layout pageSizeForSectionAtIndex:(NSInteger)section; 112 | 113 | @end 114 | 115 | @interface NECollectionViewFlowLayout : UICollectionViewLayout 116 | 117 | @property (nonatomic) UICollectionViewScrollDirection sectionScrollDirection; // Define the section layout direction. 118 | @property (nonatomic) CGFloat sectionWidth; // Default 0, means auto, will use container's width. 119 | @property (nonatomic) CGFloat sectionSpacing; // Default 0, the spacing between sections, not include section insets. 120 | 121 | @property (nonatomic) NECollectionViewFlowLayoutAlignment alignHorizontal; // Default leading(left). 122 | @property (nonatomic) NECollectionViewFlowLayoutAlignment alignVertical; // Default center. 123 | 124 | @property (nonatomic) CGFloat minimumLineSpacing; 125 | @property (nonatomic) CGFloat minimumInteritemSpacing; 126 | @property (nonatomic) CGSize itemSize; 127 | 128 | @property (nonatomic) CGSize headerReferenceSize; 129 | @property (nonatomic) CGSize footerReferenceSize; 130 | @property (nonatomic) UIEdgeInsets sectionInset; 131 | 132 | @property (nonatomic, assign) BOOL pinToVisibleBoundsEnable; // Enable for pinning feature. Default NO. 133 | 134 | @property (nonatomic, strong) id appearenceAnimator; // Animator for items 135 | @property (nonatomic, strong) id supplementaryAppearenceAnimator; // Animator for supplementaries 136 | @property (nonatomic, strong) id decorationAppearenceAnimator; // Animator for decoration views 137 | 138 | /// It will close the optimization feature when setting YES. 139 | @property (nonatomic) BOOL invalidateFlowLayoutDelegateMetricsWhenUpdates; // Default NO. 140 | 141 | /// Get the current contentOffset of the section. If the section is not scroll horizontal, the value is meaningless. 142 | /// It MUST call after [collectionView layoutIfNeeded] 143 | - (CGPoint)contentOffsetForSectionAtIndex:(NSInteger)section; 144 | /// Set the current contentOffset of the section. If the section is not scroll horizontal, do nothing. 145 | /// It MUST call after [collectionView layoutIfNeeded] 146 | - (void)setContentOffset:(CGPoint)contentOffset forSectionAtIndex:(NSInteger)section; 147 | 148 | /// Get the frame of the section. 149 | - (CGRect)frameForSectionAtIndex:(NSInteger)section; 150 | 151 | @end 152 | 153 | NS_ASSUME_NONNULL_END 154 | -------------------------------------------------------------------------------- /Example/NECollectionViewLayout/NEDeleteCollectionViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // NEDeleteCollectionViewController.m 3 | // NECollectionViewLayout_Example 4 | // 5 | // Created by Daniel on 2019/12/9. 6 | // Copyright © 2019 Daniel. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "NETextCollectionViewCell.h" 12 | #import "NEHeaderCollectionReusableView.h" 13 | #import "NEFooterCollectionReusableView.h" 14 | #import "NEDeleteCollectionViewController.h" 15 | 16 | @interface NEDeleteCollectionViewController () 17 | 18 | @property (nonatomic, strong) NSMutableArray *> *dataSourece; 19 | 20 | @end 21 | 22 | @implementation NEDeleteCollectionViewController 23 | 24 | - (instancetype)init 25 | { 26 | NECollectionViewFlowLayout *layout = [NECollectionViewFlowLayout new]; 27 | layout.pinToVisibleBoundsEnable = YES; 28 | layout.appearenceAnimator = [NECollectionViewFlowLayoutScaleAnimator new]; 29 | // UICollectionViewFlowLayout *layout = [UICollectionViewFlowLayout new]; 30 | layout.minimumLineSpacing = 10; 31 | layout.minimumInteritemSpacing = 10; 32 | // UICollectionViewLayout *layout = [NECollectionViewFlowLayout new]; 33 | self = [self initWithCollectionViewLayout:layout]; 34 | if (self) { 35 | 36 | } 37 | return self; 38 | } 39 | 40 | - (void)loadView { 41 | self.collectionView = [[NEOptimizeCollectionView alloc] initWithFrame:UIScreen.mainScreen.bounds 42 | collectionViewLayout:self.collectionViewLayout]; 43 | self.collectionView.backgroundColor = UIColor.blackColor; 44 | 45 | // self.collectionView = [[UICollectionView alloc] initWithFrame:UIScreen.mainScreen.bounds 46 | // collectionViewLayout:self.collectionViewLayout]; 47 | } 48 | 49 | - (void)viewDidLoad { 50 | [super viewDidLoad]; 51 | 52 | __auto_type deleteItem = [[UIBarButtonItem alloc] initWithTitle:@"item" style:UIBarButtonItemStylePlain target:self action:@selector(deleteItem)]; 53 | __auto_type deletSection = [[UIBarButtonItem alloc] initWithTitle:@"section" style:UIBarButtonItemStylePlain target:self action:@selector(deletSection)]; 54 | self.navigationItem.rightBarButtonItems = @[deletSection, deleteItem]; 55 | 56 | // Register cell classes 57 | [self.collectionView registerClass:[NETextCollectionViewCell class] forCellWithReuseIdentifier:NSStringFromClass(NETextCollectionViewCell.class)]; 58 | [self.collectionView registerClass:[NEHeaderCollectionReusableView class] 59 | forSupplementaryViewOfKind:UICollectionElementKindSectionHeader 60 | withReuseIdentifier:NSStringFromClass(NEHeaderCollectionReusableView.class)]; 61 | [self.collectionView registerClass:[NEFooterCollectionReusableView class] 62 | forSupplementaryViewOfKind:UICollectionElementKindSectionFooter 63 | withReuseIdentifier:NSStringFromClass(NEFooterCollectionReusableView.class)]; 64 | [self.collectionView registerClass:[UICollectionReusableView class] 65 | forSupplementaryViewOfKind:NECollectionElementKindSectionBackground 66 | withReuseIdentifier:NSStringFromClass(UICollectionReusableView.class)]; 67 | 68 | self.dataSourece = [NSMutableArray new]; 69 | 70 | for (int i = 0; i < 10; i++) { 71 | NSMutableArray *section = [NSMutableArray new]; 72 | for (int r = 0; r < 20; r++) { 73 | [section addObject:@{ 74 | @"title" : [NSString stringWithFormat:@"%d-%d", i, r] 75 | }]; 76 | } 77 | [self.dataSourece addObject:section]; 78 | } 79 | 80 | } 81 | 82 | #pragma mark 83 | 84 | - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { 85 | return self.dataSourece.count; 86 | } 87 | 88 | 89 | - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { 90 | return [self.dataSourece[section] count]; 91 | } 92 | 93 | - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { 94 | NETextCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:NSStringFromClass(NETextCollectionViewCell.class) forIndexPath:indexPath]; 95 | cell.backgroundColor = [UIColor grayColor]; 96 | cell.textLabel.text = self.dataSourece[indexPath.section][indexPath.item][@"title"]; 97 | return cell; 98 | } 99 | 100 | - (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath { 101 | if ([kind isEqualToString:UICollectionElementKindSectionHeader]) { 102 | NEHeaderCollectionReusableView *view = [collectionView dequeueReusableSupplementaryViewOfKind:kind 103 | withReuseIdentifier:NSStringFromClass(NEHeaderCollectionReusableView.class) 104 | forIndexPath:indexPath]; 105 | view.backgroundColor = [UIColor yellowColor]; 106 | view.textLabel.text = [NSString stringWithFormat:@"Header - %@", @(indexPath.section).stringValue]; 107 | return view; 108 | } 109 | else if ([kind isEqualToString:UICollectionElementKindSectionFooter]) { 110 | NEFooterCollectionReusableView *view = [collectionView dequeueReusableSupplementaryViewOfKind:kind 111 | withReuseIdentifier:NSStringFromClass(NEFooterCollectionReusableView.class) 112 | forIndexPath:indexPath]; 113 | view.backgroundColor = [UIColor blueColor]; 114 | view.textLabel.text = [NSString stringWithFormat:@"Footer - %@", @(indexPath.section).stringValue]; 115 | return view; 116 | } 117 | else { 118 | UICollectionReusableView *view = [collectionView dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:NSStringFromClass(UICollectionReusableView.class) forIndexPath:indexPath]; 119 | view.backgroundColor = [UIColor colorWithWhite:1 alpha:0.3]; 120 | view.clipsToBounds = YES; 121 | view.layer.cornerRadius = 10; 122 | return view; 123 | } 124 | return nil; 125 | } 126 | 127 | - (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath { 128 | 129 | } 130 | 131 | - (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath { 132 | 133 | } 134 | 135 | - (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section { 136 | return UIEdgeInsetsMake(20, 20, 20, 20); 137 | } 138 | 139 | - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section { 140 | return CGSizeMake(collectionView.frame.size.width, 60); 141 | } 142 | 143 | - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section { 144 | return CGSizeMake(collectionView.frame.size.width, 30); 145 | } 146 | 147 | - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath { 148 | return CGSizeMake(collectionView.frame.size.width / 6, 88); 149 | } 150 | 151 | - (BOOL)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)layout backgroundVisibleForSectionAtIndex:(NSInteger)section { 152 | return YES; 153 | } 154 | 155 | - (NECollectionViewFlowLayoutAlignment)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)layout alignVerticalForSectionAtIndex:(NSInteger)section { 156 | return section % 3; 157 | } 158 | 159 | - (NECollectionViewFlowLayoutAlignment)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)layout alignHorizontalForSectionAtIndex:(NSInteger)section { 160 | return section % 4; 161 | } 162 | 163 | - (void)deleteItem { 164 | if (self.dataSourece.firstObject.count) { 165 | NSInteger index = self.dataSourece.firstObject.count - 1; 166 | [self.dataSourece.firstObject removeObjectAtIndex:index]; 167 | [self.collectionView deleteItemsAtIndexPaths:@[[NSIndexPath indexPathForItem:index inSection:0]]]; 168 | } 169 | } 170 | 171 | - (void)deletSection { 172 | if (self.dataSourece.count) { 173 | NSInteger i = 0; //self.dataSourece.count - 1; 174 | [self.dataSourece removeObjectAtIndex:i]; 175 | [self.collectionView performBatchUpdates:^{ 176 | [self.collectionView deleteSections:[NSIndexSet indexSetWithIndex:i]]; 177 | } completion:^(BOOL finished) { 178 | 179 | }]; 180 | } 181 | } 182 | 183 | @end 184 | -------------------------------------------------------------------------------- /Example/NECollectionViewLayout/NEPinCollectionViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // NEPinCollectionViewController.m 3 | // NECollectionViewLayout_Example 4 | // 5 | // Created by Daniel on 2019/12/9. 6 | // Copyright © 2019 Daniel. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "NETextCollectionViewCell.h" 12 | #import "NEHeaderCollectionReusableView.h" 13 | #import "NEFooterCollectionReusableView.h" 14 | #import "NEPinCollectionViewController.h" 15 | 16 | @interface NEPinCollectionViewController () 17 | @property (nonatomic, assign) NECollectionViewFlowLayoutPinToVisibleBounds style; 18 | @end 19 | 20 | @implementation NEPinCollectionViewController 21 | 22 | - (instancetype)init 23 | { 24 | NECollectionViewFlowLayout *layout = [NECollectionViewFlowLayout new]; 25 | layout.pinToVisibleBoundsEnable = YES; 26 | // UICollectionViewFlowLayout *layout = [UICollectionViewFlowLayout new]; 27 | layout.minimumLineSpacing = 10; 28 | layout.minimumInteritemSpacing = 10; 29 | // UICollectionViewLayout *layout = [NECollectionViewFlowLayout new]; 30 | self = [self initWithCollectionViewLayout:layout]; 31 | if (self) { 32 | 33 | } 34 | return self; 35 | } 36 | 37 | - (void)loadView { 38 | self.collectionView = [[NEOptimizeCollectionView alloc] initWithFrame:UIScreen.mainScreen.bounds 39 | collectionViewLayout:self.collectionViewLayout]; 40 | self.collectionView.backgroundColor = UIColor.blackColor; 41 | 42 | // self.collectionView = [[UICollectionView alloc] initWithFrame:UIScreen.mainScreen.bounds 43 | // collectionViewLayout:self.collectionViewLayout]; 44 | } 45 | 46 | - (void)viewDidLoad { 47 | [super viewDidLoad]; 48 | 49 | __auto_type pin = [[UIBarButtonItem alloc] initWithTitle:@"pin" style:UIBarButtonItemStylePlain target:self action:@selector(pin)]; 50 | __auto_type style = [[UIBarButtonItem alloc] initWithTitle:@"style" style:UIBarButtonItemStylePlain target:self action:@selector(pinStyle)]; 51 | self.navigationItem.rightBarButtonItems = @[pin, style]; 52 | 53 | // Register cell classes 54 | [self.collectionView registerClass:[NETextCollectionViewCell class] forCellWithReuseIdentifier:NSStringFromClass(NETextCollectionViewCell.class)]; 55 | [self.collectionView registerClass:[NEHeaderCollectionReusableView class] 56 | forSupplementaryViewOfKind:UICollectionElementKindSectionHeader 57 | withReuseIdentifier:NSStringFromClass(NEHeaderCollectionReusableView.class)]; 58 | [self.collectionView registerClass:[NEFooterCollectionReusableView class] 59 | forSupplementaryViewOfKind:UICollectionElementKindSectionFooter 60 | withReuseIdentifier:NSStringFromClass(NEFooterCollectionReusableView.class)]; 61 | [self.collectionView registerClass:[UICollectionReusableView class] 62 | forSupplementaryViewOfKind:NECollectionElementKindSectionBackground 63 | withReuseIdentifier:NSStringFromClass(UICollectionReusableView.class)]; 64 | 65 | } 66 | 67 | #pragma mark 68 | 69 | - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { 70 | return 4; 71 | } 72 | 73 | 74 | - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { 75 | return 40; 76 | } 77 | 78 | - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { 79 | NETextCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:NSStringFromClass(NETextCollectionViewCell.class) forIndexPath:indexPath]; 80 | cell.backgroundColor = [UIColor grayColor]; 81 | cell.textLabel.text = [NSString stringWithFormat:@"%@-%@", @(indexPath.section).stringValue, @(indexPath.item).stringValue]; 82 | return cell; 83 | } 84 | 85 | - (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath { 86 | if ([kind isEqualToString:UICollectionElementKindSectionHeader]) { 87 | NEHeaderCollectionReusableView *view = [collectionView dequeueReusableSupplementaryViewOfKind:kind 88 | withReuseIdentifier:NSStringFromClass(NEHeaderCollectionReusableView.class) 89 | forIndexPath:indexPath]; 90 | view.backgroundColor = [UIColor yellowColor]; 91 | view.textLabel.text = [NSString stringWithFormat:@"Header - %@", @(indexPath.section).stringValue]; 92 | return view; 93 | } 94 | else if ([kind isEqualToString:UICollectionElementKindSectionFooter]) { 95 | NEFooterCollectionReusableView *view = [collectionView dequeueReusableSupplementaryViewOfKind:kind 96 | withReuseIdentifier:NSStringFromClass(NEFooterCollectionReusableView.class) 97 | forIndexPath:indexPath]; 98 | view.backgroundColor = [UIColor blueColor]; 99 | view.textLabel.text = [NSString stringWithFormat:@"Footer - %@", @(indexPath.section).stringValue]; 100 | return view; 101 | } 102 | else { 103 | UICollectionReusableView *view = [collectionView dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:NSStringFromClass(UICollectionReusableView.class) forIndexPath:indexPath]; 104 | view.backgroundColor = [UIColor colorWithWhite:1 alpha:0.3]; 105 | view.clipsToBounds = YES; 106 | view.layer.cornerRadius = 10; 107 | return view; 108 | } 109 | return nil; 110 | } 111 | 112 | - (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section { 113 | return UIEdgeInsetsMake(20, 20, 20, 20); 114 | } 115 | 116 | - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section { 117 | return CGSizeMake(collectionView.frame.size.width, 60); 118 | } 119 | 120 | - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section { 121 | return CGSizeMake(collectionView.frame.size.width, 30); 122 | } 123 | 124 | - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath { 125 | return CGSizeMake(collectionView.frame.size.width / 6, indexPath.item % 3 ? 44 : 88); 126 | } 127 | 128 | - (NECollectionViewFlowLayoutAlignment)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)layout alignVerticalForSectionAtIndex:(NSInteger)section { 129 | return section % 3; 130 | } 131 | 132 | - (NECollectionViewFlowLayoutAlignment)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)layout alignHorizontalForSectionAtIndex:(NSInteger)section { 133 | return section % 4; 134 | } 135 | 136 | - (NECollectionViewFlowLayoutPinToVisibleBounds)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)layout headerPinToVisibleBoundsForSectionAtIndex:(NSInteger)section { 137 | if (section == 1) { 138 | return self.style; 139 | } 140 | return NECollectionViewFlowLayoutPinToVisibleBoundsNone; 141 | } 142 | 143 | - (NECollectionViewFlowLayoutPinToVisibleBounds)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)layout footerPinToVisibleBoundsForSectionAtIndex:(NSInteger)section { 144 | if (section == 1) { 145 | return self.style; 146 | } 147 | return NECollectionViewFlowLayoutPinToVisibleBoundsNone; 148 | } 149 | 150 | 151 | - (BOOL)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)layout backgroundVisibleForSectionAtIndex:(NSInteger)section { 152 | return YES; 153 | } 154 | 155 | - (BOOL)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)layout backgroundIncludeSupplementarysForSectionAtIndex:(NSInteger)section { 156 | return NO; 157 | } 158 | 159 | - (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)layout backgroundInsetsForSectionAtIndex:(NSInteger)section { 160 | return UIEdgeInsetsMake(-10, -10, -10, -10); 161 | } 162 | 163 | - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath { 164 | 165 | } 166 | 167 | - (void)pin { 168 | NECollectionViewFlowLayout *layout = (NECollectionViewFlowLayout *)self.collectionViewLayout; 169 | layout.pinToVisibleBoundsEnable = !layout.pinToVisibleBoundsEnable; 170 | [self updateTitle]; 171 | } 172 | 173 | - (void)pinStyle { 174 | self.style ++; 175 | if (self.style > NECollectionViewFlowLayoutPinToVisibleBoundsAlways) { 176 | self.style = NECollectionViewFlowLayoutPinToVisibleBoundsNone; 177 | } 178 | [self.collectionViewLayout invalidateLayout]; 179 | [self updateTitle]; 180 | } 181 | 182 | - (void)updateTitle { 183 | self.title = [NSString stringWithFormat:@"%@-%@", 184 | @([(NECollectionViewFlowLayout *)self.collectionViewLayout pinToVisibleBoundsEnable]), 185 | NECollectionViewFlowLayoutPinToVisibleBoundsToReadable(self.style)]; 186 | } 187 | 188 | @end 189 | -------------------------------------------------------------------------------- /Example/NECollectionViewLayout/NEAlignmentCollectionViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // NEAlignmentCollectionViewController.m 3 | // NECollectionViewLayout_Example 4 | // 5 | // Created by Daniel on 2019/12/9. 6 | // Copyright © 2019 Daniel. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "NETextCollectionViewCell.h" 12 | #import "NEHeaderCollectionReusableView.h" 13 | #import "NEFooterCollectionReusableView.h" 14 | #import "NEAlignmentCollectionViewController.h" 15 | 16 | @interface NEAlignmentCollectionViewController () 17 | @property (nonatomic, assign) NECollectionViewFlowLayoutAlignment hStyle; 18 | @property (nonatomic, assign) NECollectionViewFlowLayoutAlignment vStyle; 19 | 20 | @end 21 | 22 | @implementation NEAlignmentCollectionViewController 23 | 24 | - (instancetype)init 25 | { 26 | NECollectionViewFlowLayout *layout = [NECollectionViewFlowLayout new]; 27 | layout.pinToVisibleBoundsEnable = YES; 28 | // UICollectionViewFlowLayout *layout = [UICollectionViewFlowLayout new]; 29 | layout.minimumLineSpacing = 10; 30 | layout.minimumInteritemSpacing = 10; 31 | // UICollectionViewLayout *layout = [NECollectionViewFlowLayout new]; 32 | self = [self initWithCollectionViewLayout:layout]; 33 | if (self) { 34 | 35 | } 36 | return self; 37 | } 38 | 39 | - (void)loadView { 40 | self.collectionView = [[NEOptimizeCollectionView alloc] initWithFrame:UIScreen.mainScreen.bounds 41 | collectionViewLayout:self.collectionViewLayout]; 42 | self.collectionView.backgroundColor = UIColor.blackColor; 43 | 44 | // self.collectionView = [[UICollectionView alloc] initWithFrame:UIScreen.mainScreen.bounds 45 | // collectionViewLayout:self.collectionViewLayout]; 46 | } 47 | 48 | - (void)viewDidLoad { 49 | [super viewDidLoad]; 50 | 51 | [self updateTitle]; 52 | __auto_type hStyle = [[UIBarButtonItem alloc] initWithTitle:@"hstyle" style:UIBarButtonItemStylePlain target:self action:@selector(switchHStyle)]; 53 | __auto_type vStyle = [[UIBarButtonItem alloc] initWithTitle:@"vstyle" style:UIBarButtonItemStylePlain target:self action:@selector(switchVStyle)]; 54 | self.navigationItem.rightBarButtonItems = @[hStyle, vStyle]; 55 | 56 | // Register cell classes 57 | [self.collectionView registerClass:[NETextCollectionViewCell class] forCellWithReuseIdentifier:NSStringFromClass(NETextCollectionViewCell.class)]; 58 | [self.collectionView registerClass:[NEHeaderCollectionReusableView class] 59 | forSupplementaryViewOfKind:UICollectionElementKindSectionHeader 60 | withReuseIdentifier:NSStringFromClass(NEHeaderCollectionReusableView.class)]; 61 | [self.collectionView registerClass:[NEFooterCollectionReusableView class] 62 | forSupplementaryViewOfKind:UICollectionElementKindSectionFooter 63 | withReuseIdentifier:NSStringFromClass(NEFooterCollectionReusableView.class)]; 64 | [self.collectionView registerClass:[UICollectionReusableView class] 65 | forSupplementaryViewOfKind:NECollectionElementKindSectionBackground 66 | withReuseIdentifier:NSStringFromClass(UICollectionReusableView.class)]; 67 | 68 | } 69 | 70 | #pragma mark 71 | 72 | - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { 73 | return 4; 74 | } 75 | 76 | 77 | - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { 78 | return 40; 79 | } 80 | 81 | - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { 82 | NETextCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:NSStringFromClass(NETextCollectionViewCell.class) forIndexPath:indexPath]; 83 | cell.backgroundColor = [UIColor grayColor]; 84 | cell.textLabel.text = [NSString stringWithFormat:@"%@-%@", @(indexPath.section).stringValue, @(indexPath.item).stringValue]; 85 | return cell; 86 | } 87 | 88 | - (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath { 89 | if ([kind isEqualToString:UICollectionElementKindSectionHeader]) { 90 | NEHeaderCollectionReusableView *view = [collectionView dequeueReusableSupplementaryViewOfKind:kind 91 | withReuseIdentifier:NSStringFromClass(NEHeaderCollectionReusableView.class) 92 | forIndexPath:indexPath]; 93 | view.backgroundColor = [UIColor yellowColor]; 94 | view.textLabel.text = [NSString stringWithFormat:@"Header - %@", @(indexPath.section).stringValue]; 95 | return view; 96 | } 97 | else if ([kind isEqualToString:UICollectionElementKindSectionFooter]) { 98 | NEFooterCollectionReusableView *view = [collectionView dequeueReusableSupplementaryViewOfKind:kind 99 | withReuseIdentifier:NSStringFromClass(NEFooterCollectionReusableView.class) 100 | forIndexPath:indexPath]; 101 | view.backgroundColor = [UIColor blueColor]; 102 | view.textLabel.text = [NSString stringWithFormat:@"Footer - %@", @(indexPath.section).stringValue]; 103 | return view; 104 | } 105 | else { 106 | UICollectionReusableView *view = [collectionView dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:NSStringFromClass(UICollectionReusableView.class) forIndexPath:indexPath]; 107 | view.backgroundColor = [UIColor colorWithWhite:1 alpha:0.3]; 108 | view.clipsToBounds = YES; 109 | view.layer.cornerRadius = 10; 110 | return view; 111 | } 112 | return nil; 113 | } 114 | 115 | - (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section { 116 | return UIEdgeInsetsMake(20, 20, 20, 20); 117 | } 118 | 119 | - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section { 120 | return CGSizeMake(collectionView.frame.size.width, 60); 121 | } 122 | 123 | - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section { 124 | return CGSizeMake(collectionView.frame.size.width, 30); 125 | } 126 | 127 | - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath { 128 | return CGSizeMake(collectionView.frame.size.width / 6, indexPath.item % 3 ? 44 : 88); 129 | } 130 | 131 | - (NECollectionViewFlowLayoutAlignment)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)layout alignVerticalForSectionAtIndex:(NSInteger)section { 132 | return self.vStyle; 133 | } 134 | 135 | - (NECollectionViewFlowLayoutAlignment)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)layout alignHorizontalForSectionAtIndex:(NSInteger)section { 136 | return self.hStyle; 137 | } 138 | 139 | - (BOOL)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)layout backgroundVisibleForSectionAtIndex:(NSInteger)section { 140 | return YES; 141 | } 142 | 143 | - (BOOL)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)layout backgroundIncludeSupplementarysForSectionAtIndex:(NSInteger)section { 144 | return NO; 145 | } 146 | 147 | - (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)layout backgroundInsetsForSectionAtIndex:(NSInteger)section { 148 | return UIEdgeInsetsMake(-10, -10, -10, -10); 149 | } 150 | 151 | - (UICollectionViewScrollDirection)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)layout scrollDirectionForSectionAtIndex:(NSInteger)section { 152 | if (section >= 1) { 153 | return UICollectionViewScrollDirectionHorizontal; 154 | } 155 | return UICollectionViewScrollDirectionVertical; 156 | } 157 | 158 | - (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)layout heightForScrollHorizontalSectionAtIndex:(NSInteger)section { 159 | if (section == 2) { 160 | return 300; 161 | } 162 | return 0; 163 | } 164 | 165 | - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath { 166 | 167 | } 168 | 169 | - (void)switchHStyle { 170 | self.hStyle ++; 171 | if (self.hStyle > NECollectionViewFlowLayoutAlignSpacingBetween) { 172 | self.hStyle = NECollectionViewFlowLayoutAlignLeading; 173 | } 174 | [self.collectionViewLayout invalidateLayout]; 175 | [self updateTitle]; 176 | } 177 | - (void)switchVStyle { 178 | self.vStyle ++; 179 | if (self.vStyle > NECollectionViewFlowLayoutAlignSpacingBetween) { 180 | self.vStyle = NECollectionViewFlowLayoutAlignLeading; 181 | } 182 | [self.collectionViewLayout invalidateLayout]; 183 | [self updateTitle]; 184 | } 185 | 186 | - (void)updateTitle { 187 | self.title = [NSString stringWithFormat:@"h: %@, v: %@", 188 | NECollectionViewFlowLayoutAlignmentToReadable(self.hStyle), 189 | NECollectionViewFlowLayoutAlignmentToReadable(self.vStyle)]; 190 | } 191 | 192 | @end 193 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /NECollectionViewLayout/Classes/NECollectionViewFlowLayout/private/NECollectionViewFlowLayoutCalculator.h: -------------------------------------------------------------------------------- 1 | // 2 | // NECollectionViewFlowLayoutCalculator.h 3 | // Pods 4 | // 5 | // Created by Daniel on 2019/11/28. 6 | // 7 | 8 | #ifndef NECollectionViewFlowLayoutCalculator_h 9 | #define NECollectionViewFlowLayoutCalculator_h 10 | 11 | #include 12 | #include 13 | #include 14 | #include "NECollectionViewLayoutHelpers.h" 15 | #include "NECollectionViewFlowLayoutContext.h" 16 | #include "NECollectionViewFlowLayoutSection.h" 17 | #include "NECollectionViewFlowLayoutInvalidation.h" 18 | #import "NECollectionViewFlowLayoutInvalidationContext.h" 19 | #include "NECollectionViewFlowLayoutCollection.h" 20 | #include "NECollectionViewUpdates.h" 21 | 22 | namespace NE::CollectionViewFlowLayout { 23 | 24 | class Calculator : public Container { 25 | public: 26 | using Updates = CollectionView::Updates; 27 | 28 | Calculator() {} 29 | ~Calculator() override {} 30 | 31 | void setBounds(const CGRect& bounds) { 32 | if (bounds_ != bounds) { 33 | bounds_ = bounds; 34 | markLayoutDirty(); 35 | } 36 | } 37 | 38 | void setContext(Context&& ctx) { context_ = ctx; } 39 | Context& context() { return context_; } 40 | 41 | Updates& updates() { return updates_; } 42 | 43 | const auto& sections() const { return sections_; } 44 | 45 | bool invalidateFlowLayoutDelegateMetricsWhenUpdates() { 46 | return invalidateFlowLayoutDelegateMetricsWhenUpdates_; 47 | } 48 | void setInvalidateFlowLayoutDelegateMetricsWhenUpdates(bool b) { 49 | invalidateFlowLayoutDelegateMetricsWhenUpdates_ = b; 50 | } 51 | 52 | void invalidate(NECollectionViewFlowLayoutInvalidationContext *ctx) { 53 | if (ctx.invalidateEverything) invalidation_.invalidateEverything(); 54 | if (ctx.invalidateDataSourceCounts) invalidation_.invalidateDataSourceCounts(); 55 | if (ctx.invalidateFlowLayoutDelegateMetrics) invalidation_.invalidateFlowLayoutDelegateMetrics(); 56 | if (ctx.invalidateFlowLayoutAttributes) invalidation_.invalidateFlowLayoutAttributes(); 57 | if (ctx.invalidatedItemIndexPaths.count) { 58 | for (NSIndexPath *idx in ctx.invalidatedItemIndexPaths) { 59 | invalidation_.invalidateItem(idx); 60 | } 61 | } 62 | if (ctx.invalidatedSupplementaryIndexPaths.count) { 63 | [ctx.invalidatedSupplementaryIndexPaths enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, NSArray * _Nonnull obj, BOOL * _Nonnull stop) { 64 | for (NSIndexPath *idx in obj) { 65 | invalidation_.invalidateSumplementary(key, idx); 66 | } 67 | }]; 68 | } 69 | if (ctx.invalidatedDecorationIndexPaths.count) { 70 | [ctx.invalidatedDecorationIndexPaths enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, NSArray * _Nonnull obj, BOOL * _Nonnull stop) { 71 | for (NSIndexPath *idx in obj) { 72 | invalidation_.invalidateDecoration(key, idx); 73 | } 74 | }]; 75 | } 76 | if (ctx.invalidatedSectionScrollOffsets.count) { 77 | [ctx.invalidatedSectionScrollOffsets enumerateKeysAndObjectsUsingBlock:^(NSNumber * _Nonnull key, NSValue * _Nonnull obj, BOOL * _Nonnull stop) { 78 | invalidation_.invalidateSectionContentOffset(key.unsignedIntegerValue, obj.CGPointValue); 79 | }]; 80 | } 81 | } 82 | 83 | void calculateLayoutIfNeeded() { 84 | NSUInteger minSection = NSNotFound; 85 | bool refreshSize = false; 86 | bool refreshSizeAll = false; 87 | if (!invalidation_.isInvalidateEverything() && invalidation_.isInvalidateDataSourceCounts()) { 88 | // Update actions(delete, insert) needs prev attributes. We cache it here ONLY update occurs. 89 | cacheResult(); 90 | } 91 | else { 92 | clearCachedResult(); 93 | } 94 | 95 | if (invalidation_.isInvalidateEverything()) { 96 | refreshAll(); 97 | refreshSizeAll = refreshSize = true; 98 | minSection = 0; 99 | } 100 | else if (invalidation_.isInvalidateDataSourceCounts()) { 101 | if (!invalidateFlowLayoutDelegateMetricsWhenUpdates_ && updates_.hasModified()) { 102 | markUpdates(); 103 | refreshSize = true; 104 | minSection = updates_.minSection(); 105 | } 106 | else { 107 | refreshAll(); 108 | refreshSizeAll = refreshSize = true; 109 | minSection = 0; 110 | } 111 | } 112 | else if (invalidation_.isInvalidateFlowLayoutDelegateMetrics()) { 113 | refreshAll(); 114 | refreshSizeAll = refreshSize = true; 115 | minSection = 0; 116 | } 117 | if (!refreshSizeAll && invalidation_.isInvalidateFlowLayoutAttributes() && invalidation_.hasInvalidateAttributes()) { 118 | markInvalidation(); 119 | refreshSize = true; 120 | minSection = std::min(minSection, invalidation_.minSection()); 121 | } 122 | 123 | auto& offsets = invalidation_.invalidatedSectionContentOffsets(); 124 | 125 | if (!refreshSize) { 126 | if (!offsets.empty()) { 127 | relayoutSectionContentOffset(offsets); 128 | } 129 | } 130 | else { 131 | updateContentOffsets(offsets); 132 | relayout(minSection == NSNotFound ? 0 : minSection); 133 | } 134 | 135 | invalidation_.reset(); 136 | updates_.reset(); 137 | } 138 | 139 | const auto& cachedPrevSections() { return cachedSections_; } 140 | 141 | void cacheResult() { 142 | cachedSections_ = sections_; 143 | } 144 | 145 | void clearCachedResult() { 146 | cachedSections_.clear(); 147 | } 148 | 149 | #pragma mark - attributes 150 | void collectLayoutAttributesInRect(LayoutCollection& collection, const CGRect& rect, const CGRect& visibleRect) { 151 | for (auto& section : sections_) { 152 | section.collectLayoutAttributesInRect(context(), collection, rect, visibleRect); 153 | } 154 | } 155 | 156 | UICollectionViewLayoutAttributes *itemAttributedAtIndexPath(NSIndexPath *indexPath) { 157 | auto section = indexPath.section; 158 | if (section < sections_.size()) { 159 | return sections_.at(section).itemAttributeAtIndex(context_, indexPath.item); 160 | } 161 | return nil; 162 | } 163 | 164 | UICollectionViewLayoutAttributes *prevItemAttributedAtIndexPath(NSIndexPath *indexPath) { 165 | auto section = indexPath.section; 166 | if (section < cachedSections_.size()) { 167 | return cachedSections_.at(section).itemAttributeAtIndex(context_, indexPath.item); 168 | } 169 | return nil; 170 | } 171 | 172 | UICollectionViewLayoutAttributes *supplementaryAtIndexPath(NSString *kind, NSIndexPath *indexPath, const CGRect& visibleRect) { 173 | auto section = indexPath.section; 174 | if (section < sections_.size()) { 175 | return sections_.at(section).supplementaryAttributeAtIndex(context_, kind, indexPath.item, visibleRect); 176 | } 177 | return nil; 178 | } 179 | 180 | UICollectionViewLayoutAttributes *prevSupplementaryAtIndexPath(NSString *kind, NSIndexPath *indexPath, const CGRect& visibleRect) { 181 | auto section = indexPath.section; 182 | if (section < cachedSections_.size()) { 183 | return cachedSections_.at(section).supplementaryAttributeAtIndex(context_, kind, indexPath.item, visibleRect); 184 | } 185 | return nil; 186 | } 187 | 188 | UICollectionViewLayoutAttributes *prevDecorationAtIndexPath(NSString *kind, NSIndexPath *indexPath) { 189 | auto section = indexPath.section; 190 | if (section < cachedSections_.size()) { 191 | return cachedSections_.at(section).decorationAttributeAtIndex(context_, kind, indexPath.item); 192 | } 193 | return nil; 194 | } 195 | 196 | UICollectionViewLayoutAttributes *decorationAtIndexPath(NSString *kind, NSIndexPath *indexPath) { 197 | auto section = indexPath.section; 198 | if (section < sections_.size()) { 199 | return sections_.at(section).decorationAttributeAtIndex(context_, kind, indexPath.item); 200 | } 201 | return nil; 202 | } 203 | 204 | NSArray *sectionHeaderPinToVisibleIndexes() { 205 | NSMutableArray *array = [NSMutableArray array]; 206 | for (auto& section : sections_) { 207 | if (section.headerPin() != PinToVisibleBounds::None 208 | && section.header().contentSize() != CGSizeZero) { 209 | [array addObject:[NSIndexPath indexPathForItem:0 inSection:section.section()]]; 210 | } 211 | } 212 | return array; 213 | } 214 | NSArray *sectionFooterPinToVisibleIndexes() { 215 | NSMutableArray *array = [NSMutableArray array]; 216 | for (auto& section : sections_) { 217 | if (section.footerPin() != PinToVisibleBounds::None 218 | && section.footer().contentSize() != CGSizeZero) { 219 | [array addObject:[NSIndexPath indexPathForItem:0 inSection:section.section()]]; 220 | } 221 | } 222 | return array; 223 | } 224 | 225 | NSIndexPath *targetIndexPathAtPosition(const CGPoint& point) { 226 | for (auto& section : sections_) { 227 | if (CGRectContainsPoint(section.frame(), point)) { 228 | return section.targetIndexPathAtPosition(point); 229 | } 230 | } 231 | return nil; 232 | } 233 | 234 | CGSize contentSize() { 235 | return frame().size; 236 | } 237 | protected: 238 | 239 | void markLayoutDirty() { 240 | for (auto& section : sections_) { 241 | section.markLayoutDirty(); 242 | } 243 | } 244 | 245 | void markInvalidation() { 246 | for (auto& idx : invalidation_.invalidateItems()) { 247 | auto& section = sections_.at(idx.section()); 248 | section.markItemDataSourceDirty(idx.item()); 249 | } 250 | for (auto& [kind, idxes] : invalidation_.invalidateSumplementaries()) { 251 | for (auto& idx : idxes) { 252 | auto& section = sections_.at(idx.section()); 253 | section.markSupplementaryDataSourceDirty(kind, idx.item()); 254 | } 255 | } 256 | for (auto& [kind, idxes] : invalidation_.invalidateDecorations()) { 257 | for (auto& idx : idxes) { 258 | auto& section = sections_.at(idx.section()); 259 | section.markDecorationDataSrouceDirty(kind, idx.item()); 260 | } 261 | } 262 | } 263 | 264 | void markUpdates() { 265 | using namespace NE::CollectionView; 266 | for (auto& item : updates_.updateItems()) { 267 | switch (item.action()) { 268 | case UpdateAction::Insert: 269 | if (item.isSection()) { 270 | auto& sections = item.sections(); 271 | insertSections(sections); 272 | } 273 | else { 274 | auto& indexPaths = item.indexPaths(); 275 | for (auto& indexPath : indexPaths) { 276 | auto& section = sections_.at(indexPath.section()); 277 | section.insertItem(indexPath.item()); 278 | } 279 | } 280 | break; 281 | case UpdateAction::Delete: 282 | if (item.isSection()) { 283 | auto& sections = item.sections(); 284 | deleteSections(sections); 285 | } 286 | else { 287 | auto& indexPaths = item.indexPaths(); 288 | for (auto& indexPath : indexPaths) { 289 | auto& section = sections_.at(indexPath.section()); 290 | section.deleteItem(indexPath.item()); 291 | } 292 | } 293 | break; 294 | case UpdateAction::Reload: 295 | if (item.isSection()) { 296 | auto& sections = item.sections(); 297 | for (auto& section : sections) { 298 | sections_.at(section).markDataSourceDirty(); 299 | } 300 | } 301 | else { 302 | auto& items = item.indexPaths(); 303 | for (auto& indexPath : items) { 304 | sections_.at(indexPath.section()).markItemDataSourceDirty(indexPath.item()); 305 | } 306 | } 307 | break; 308 | case UpdateAction::Move: 309 | if (item.isSection()) { 310 | auto fromIt = sections_.begin() + item.moveSections().first; 311 | auto toIt = sections_.begin() + item.moveSections().second; 312 | if (fromIt == toIt) { 313 | return; 314 | } 315 | if (fromIt < toIt) { 316 | fromIt->markLayoutDirty(); 317 | for (; fromIt != toIt; ++fromIt) { 318 | std::iter_swap(fromIt, fromIt + 1); 319 | fromIt->markLayoutDirty(); 320 | } 321 | } 322 | else { 323 | fromIt->markLayoutDirty(); 324 | for (; fromIt != toIt; --fromIt) { 325 | std::iter_swap(fromIt, fromIt - 1); 326 | fromIt->markLayoutDirty(); 327 | } 328 | } 329 | } 330 | else { 331 | auto& from = item.moveIndexPaths().first; 332 | auto& to = item.moveIndexPaths().second; 333 | if (from.section() == to.section()) { 334 | auto& section = sections_.at(from.section()); 335 | section.markLayoutDirty(); 336 | } 337 | else { 338 | auto& fromSection = sections_.at(from.section()); 339 | auto& toSection = sections_.at(to.section()); 340 | fromSection.deleteItem(from.item()); 341 | toSection.insertItem(to.item()); 342 | } 343 | } 344 | break; 345 | } 346 | } 347 | } 348 | 349 | void updateContentOffsets(const std::unordered_map& invalidatedOffsets) { 350 | if (invalidatedOffsets.empty()) return; 351 | for (auto& [index, offset] : invalidatedOffsets) { 352 | if (index < sections_.size()) { 353 | auto& section = sections_.at(index); 354 | section.setContentOffset(offset); 355 | } 356 | } 357 | } 358 | 359 | void refreshAll() { 360 | refreshDataSource(); 361 | for (auto& section : sections_) { 362 | section.markDataSourceDirty(); 363 | } 364 | } 365 | void refreshDataSource() { 366 | auto numberOfSections = context().numberOfSections(); 367 | if (sections_.size() != numberOfSections) { 368 | sections_.resize(numberOfSections); 369 | } 370 | } 371 | void refreshIndexes() { 372 | NSUInteger idx = 0; 373 | for (auto& section : sections_) { 374 | section.setSection(idx); 375 | ++ idx; 376 | } 377 | } 378 | 379 | void relayoutSectionContentOffset(const std::unordered_map& invalidatedOffsets) { 380 | if (invalidatedOffsets.empty()) return; 381 | for (auto& [index, offset] : invalidatedOffsets) { 382 | if (index < sections_.size()) { 383 | auto& section = sections_.at(index); 384 | section.relayoutContentOffsetChanged(offset); 385 | } 386 | } 387 | } 388 | 389 | void relayout(NSInteger fromSection = 0) { 390 | refreshIndexes(); 391 | auto origin = Node::origin(); 392 | 393 | NSUInteger i = 0; 394 | auto sectionSpacing = context().sectionSpacing(); 395 | if (context().sectionScrollDirection() == UICollectionViewScrollDirectionVertical) { 396 | auto contentSize = CGSize({ 397 | .width = context().isCustomSectionWidth() ? context().sectionWidth() : bounds_.size.width, 398 | .height = 0 399 | }); 400 | for (auto& section : sections_) { 401 | if (i >= fromSection) { 402 | auto newOrigin = CGPoint({ 403 | .x = origin.x, 404 | .y = origin.y + contentSize.height + (i != 0 ? sectionSpacing : 0) 405 | }); 406 | if (newOrigin != section.origin()) { 407 | section.setOrigin(newOrigin); 408 | section.markLayoutDirty(); 409 | } 410 | section.setFitSize(contentSize); 411 | 412 | section.relayout(context()); 413 | } 414 | 415 | contentSize.height += section.frame().size.height; 416 | if (i > 0) { 417 | contentSize.height += sectionSpacing; 418 | } 419 | ++i; 420 | } 421 | setSize(contentSize); 422 | } 423 | else { 424 | auto contentFitSize = CGSize({ 425 | .width = context().isCustomSectionWidth() ? context().sectionWidth() : bounds_.size.width, 426 | .height = bounds_.size.height 427 | }); 428 | auto contentSize = CGSize { 429 | .width = 0, 430 | .height = 0 431 | }; 432 | for (auto& section : sections_) { 433 | if (i >= fromSection) { 434 | auto newOrigin = CGPoint({ 435 | .x = origin.x + contentSize.width + (i != 0 ? sectionSpacing : 0), 436 | .y = origin.y 437 | }); 438 | if (newOrigin != section.origin()) { 439 | section.setOrigin(newOrigin); 440 | section.markLayoutDirty(); 441 | } 442 | section.setFitSize(contentFitSize); 443 | 444 | section.relayout(context()); 445 | } 446 | 447 | auto size = section.frame().size; 448 | contentSize.width += size.width; 449 | // height should use max section height? 450 | contentSize.height = std::max(contentSize.height, size.height); 451 | if (i > 0) { 452 | contentSize.width += sectionSpacing; 453 | } 454 | 455 | ++i; 456 | } 457 | setSize(contentSize); 458 | } 459 | } 460 | 461 | void deleteSections(const std::set& idxes) { 462 | for (auto it = idxes.rbegin(); it != idxes.rend(); ++it) { 463 | deleteSection(*it); 464 | } 465 | } 466 | 467 | void deleteSection(NSUInteger idx) { 468 | sections_.erase(sections_.begin() + idx); 469 | } 470 | 471 | void insertSections(const std::set& idxes) { 472 | for (auto it = idxes.rbegin(); it != idxes.rend(); ++it) { 473 | insertSection(*it); 474 | } 475 | } 476 | void insertSection(NSUInteger idx) { 477 | sections_.emplace(sections_.begin() + idx); 478 | } 479 | 480 | private: 481 | Context context_; 482 | Invalidation invalidation_; 483 | Updates updates_; 484 | CGRect bounds_; 485 | CGRect visibleRect_; 486 | std::vector
sections_; 487 | std::vector
cachedSections_; 488 | 489 | bool pinToVisibleBounds_ = false; 490 | bool invalidateFlowLayoutDelegateMetricsWhenUpdates_ = false; 491 | 492 | // calculate value 493 | bool dirty_ = true; 494 | bool scrollOffsetDirty_ = true; 495 | 496 | 497 | Calculator(Calculator&) = delete; 498 | Calculator& operator =(Calculator&) = delete; 499 | }; 500 | 501 | } 502 | 503 | #endif /* NECollectionViewFlowLayoutCalculator_h */ 504 | --------------------------------------------------------------------------------