├── show.gif ├── StickyHeaderFlowLayout.xcodeproj ├── project.xcworkspace │ └── contents.xcworkspacedata ├── xcuserdata │ └── yohunl.xcuserdatad │ │ └── xcschemes │ │ ├── xcschememanagement.plist │ │ └── StickyHeaderFlowLayout.xcscheme └── project.pbxproj ├── .gitignore ├── StickyHeaderFlowLayout ├── ViewController.h ├── AppDelegate.h ├── SectionHeaderReusableView.h ├── CollectionViewCell.h ├── main.m ├── YLStickyHeaderFlowLayout.h ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── SectionHeaderReusableView.m ├── Info.plist ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── CollectionViewCell.m ├── AppDelegate.m ├── ViewController.m ├── UIView+Helpers.h ├── YLStickyHeaderFlowLayout.m └── UIView+Helpers.m └── README.md /show.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yohunl/StickyHeaderFlowLayout/HEAD/show.gif -------------------------------------------------------------------------------- /StickyHeaderFlowLayout.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | .DS_Store 3 | build/ 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | *.xcworkspace 13 | !default.xcworkspace 14 | xcuserdata 15 | xcuserstate 16 | profile 17 | *.moved-aside 18 | DerivedData 19 | .idea/ 20 | -------------------------------------------------------------------------------- /StickyHeaderFlowLayout/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // StickyHeaderFlowLayout 4 | // 5 | // Created by lingyohunl on 16/1/25. 6 | // Copyright © 2016年 yohunl. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UICollectionViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /StickyHeaderFlowLayout/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // StickyHeaderFlowLayout 4 | // 5 | // Created by lingyohunl on 16/1/25. 6 | // Copyright © 2016年 yohunl. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /StickyHeaderFlowLayout/SectionHeaderReusableView.h: -------------------------------------------------------------------------------- 1 | // 2 | // SectionHeaderReusableView.h 3 | // XLPlainFlowLayoutDemo 4 | // 5 | // Created by lingyohunl on 16/1/25. 6 | 7 | // Copyright © 2016年 yohunl. All rights reserved. 8 | 9 | #import 10 | 11 | @interface SectionHeaderReusableView : UICollectionReusableView 12 | @property (nonatomic,strong) UILabel *textLabel; 13 | @end 14 | -------------------------------------------------------------------------------- /StickyHeaderFlowLayout/CollectionViewCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // CollectionViewCell.h 3 | // StickyHeaderFlowLayout 4 | // 5 | // Created by lingyohunl on 16/1/25. 6 | // Copyright © 2016年 yohunl. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface CollectionViewCell : UICollectionViewCell 12 | @property (nonatomic,strong) UILabel *imgTitleLabel; 13 | @property (nonatomic,strong) UIImageView *imgView; 14 | @end 15 | -------------------------------------------------------------------------------- /StickyHeaderFlowLayout/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // StickyHeaderFlowLayout 4 | // 5 | // Created by lingyohunl on 16/1/25. 6 | // Copyright © 2016年 yohunl. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /StickyHeaderFlowLayout/YLStickyHeaderFlowLayout.h: -------------------------------------------------------------------------------- 1 | // 2 | // YLStickyHeaderFlowLayout.h 3 | // StickyHeaderFlowLayout 4 | // 5 | // Created by lingyohunl on 16/1/25. 6 | // Copyright © 2016年 yohunl. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface YLStickyHeaderFlowLayout : UICollectionViewFlowLayout 12 | @property (nonatomic,assign)BOOL disableStickyFlow; 13 | 14 | /** 15 | * 固定header的section数组.如果不为空,则会自动设置disableStickyFlow 的值 16 | */ 17 | @property (nonatomic,strong) NSArray *stickySections; 18 | @end 19 | -------------------------------------------------------------------------------- /StickyHeaderFlowLayout.xcodeproj/xcuserdata/yohunl.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | StickyHeaderFlowLayout.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 09AAE15E1C56176200CD89B8 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /StickyHeaderFlowLayout/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /StickyHeaderFlowLayout/SectionHeaderReusableView.m: -------------------------------------------------------------------------------- 1 | // 2 | // SectionHeaderReusableView.m 3 | // XLPlainFlowLayoutDemo 4 | // 5 | // Created by lingyohunl on 16/1/25. 6 | 7 | // Copyright © 2016年 yohunl. All rights reserved. 8 | 9 | #import "SectionHeaderReusableView.h" 10 | 11 | @implementation SectionHeaderReusableView 12 | - (instancetype)initWithFrame:(CGRect)frame 13 | { 14 | if (self = [super initWithFrame:frame]) { 15 | [self initialization]; 16 | 17 | } 18 | return self; 19 | } 20 | 21 | - (id)initWithCoder:(NSCoder *)aDecoder 22 | { 23 | if (self = [super initWithCoder:aDecoder]) { 24 | [self initialization]; 25 | 26 | } 27 | return self; 28 | } 29 | 30 | - (void)initialization 31 | { 32 | 33 | _textLabel = [[UILabel alloc]initWithFrame:CGRectMake(0, 0, 60, 14)]; 34 | _textLabel.font = [UIFont systemFontOfSize:13]; 35 | _textLabel.textColor = [UIColor whiteColor]; 36 | _textLabel.textAlignment = NSTextAlignmentCenter; 37 | _textLabel.numberOfLines = 0; 38 | _textLabel.backgroundColor = [UIColor clearColor]; 39 | [self addSubview:_textLabel]; 40 | 41 | } 42 | 43 | - (void)layoutSubviews { 44 | [super layoutSubviews]; 45 | _textLabel.frame = self.bounds; 46 | 47 | } 48 | 49 | @end 50 | -------------------------------------------------------------------------------- /StickyHeaderFlowLayout/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 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /StickyHeaderFlowLayout/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 | -------------------------------------------------------------------------------- /StickyHeaderFlowLayout/CollectionViewCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // CollectionViewCell.m 3 | // StickyHeaderFlowLayout 4 | // 5 | // Created by lingyohunl on 16/1/25. 6 | // Copyright © 2016年 yohunl. All rights reserved. 7 | // 8 | 9 | #import "CollectionViewCell.h" 10 | #import "UIView+Helpers.h" 11 | @implementation CollectionViewCell 12 | - (instancetype)initWithFrame:(CGRect)frame 13 | { 14 | if (self = [super initWithFrame:frame]) { 15 | [self initialization]; 16 | 17 | } 18 | return self; 19 | } 20 | 21 | - (id)initWithCoder:(NSCoder *)aDecoder 22 | { 23 | if (self = [super initWithCoder:aDecoder]) { 24 | [self initialization]; 25 | 26 | } 27 | return self; 28 | } 29 | 30 | - (void)initialization 31 | { 32 | _imgView = [[UIImageView alloc]initWithFrame:self.bounds]; 33 | [self.contentView addSubview:_imgView]; 34 | 35 | _imgTitleLabel = [[UILabel alloc]initWithFrame:CGRectMake(0, 0, 60, 14)]; 36 | _imgTitleLabel.font = [UIFont systemFontOfSize:13]; 37 | _imgTitleLabel.textColor = [UIColor whiteColor]; 38 | _imgTitleLabel.textAlignment = NSTextAlignmentCenter; 39 | _imgTitleLabel.numberOfLines = 0; 40 | _imgTitleLabel.backgroundColor = [UIColor clearColor]; 41 | [self.contentView addSubview:_imgTitleLabel]; 42 | 43 | _imgView.backgroundColor = [UIColor colorWithRed:245/255.0 green:245/255.0 blue:245/255.0 alpha:1.0]; 44 | _imgView.contentMode = UIViewContentModeScaleAspectFill; 45 | } 46 | 47 | - (void)layoutSubviews { 48 | [super layoutSubviews]; 49 | _imgView.frame = self.bounds; 50 | _imgTitleLabel.frameSize = CGSizeMake(self.frameSizeWidth - 10, 14); 51 | [_imgTitleLabel bottomAlignForSuperViewOffset:5]; 52 | _imgTitleLabel.hidden = !_imgTitleLabel.text; 53 | 54 | } 55 | 56 | @end 57 | -------------------------------------------------------------------------------- /StickyHeaderFlowLayout/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // StickyHeaderFlowLayout 4 | // 5 | // Created by lingyohunl on 16/1/25. 6 | // Copyright © 2016年 yohunl. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @interface AppDelegate () 12 | 13 | @end 14 | 15 | @implementation AppDelegate 16 | 17 | 18 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 19 | // Override point for customization after application launch. 20 | return YES; 21 | } 22 | 23 | - (void)applicationWillResignActive:(UIApplication *)application { 24 | // 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. 25 | // 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. 26 | } 27 | 28 | - (void)applicationDidEnterBackground:(UIApplication *)application { 29 | // 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. 30 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 31 | } 32 | 33 | - (void)applicationWillEnterForeground:(UIApplication *)application { 34 | // 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. 35 | } 36 | 37 | - (void)applicationDidBecomeActive:(UIApplication *)application { 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 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 43 | } 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /StickyHeaderFlowLayout/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 | -------------------------------------------------------------------------------- /StickyHeaderFlowLayout.xcodeproj/xcuserdata/yohunl.xcuserdatad/xcschemes/StickyHeaderFlowLayout.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | StickyHeaderFlowLayout 2 | ============== 3 | 4 | 实现UICollectionView的section header悬停效果.效果和UITableView的plain模式下的header section效果相同,支持指定某一个或者多个sction header的悬停

5 | 关键在于设置下面的这个属性 6 | ```objective-c 7 | @property (nonatomic,strong) NSArray *stickySections; 8 | ``` 9 | 10 | ![image](./show.gif)

11 | 12 | 13 | 14 | 使用示例 EXAMPLE 15 | =============== 16 | 将我们的UICollectionView的布局替换为YLStickyHeaderFlowLayout即可 17 | 18 | YLStickyHeaderFlowLayout核心源码 19 | =============== 20 | ```objective-c 21 | - (NSArray *) layoutAttributesForElementsInRect:(CGRect)rect { 22 | if (self.disableStickyFlow) { 23 | return [super layoutAttributesForElementsInRect:rect]; 24 | } 25 | NSMutableArray *allItems ; 26 | //collectionView中的item(包括cell和header、footer这些)的《结构信息》.关键!!!!cell,header,footer都是利用这个数组的,在这个中,原来创建的section等,要按顺序存放到数组中来! 27 | NSArray *originalAttributes = [super layoutAttributesForElementsInRect:rect]; 28 | //allItems = (NSMutableArray *)originalAttributes ; 29 | allItems = [originalAttributes mutableCopy];//实际上layoutAttributesForElementsInRect返回的是NSMutableArray,所以,可以直接强转 30 | 31 | NSMutableDictionary *headers = [[NSMutableDictionary alloc] init];//存放每个section的header 32 | NSMutableDictionary *lastCells = [[NSMutableDictionary alloc] init];//存放的是每个section的最后一个cell 33 | [allItems enumerateObjectsUsingBlock:^(UICollectionViewLayoutAttributes * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 34 | 35 | NSIndexPath *indexPath = [obj indexPath]; 36 | BOOL isHeader = [[obj representedElementKind] isEqualToString:UICollectionElementKindSectionHeader]; 37 | BOOL isFooter = [[obj representedElementKind] isEqualToString:UICollectionElementKindSectionFooter]; 38 | 39 | if (isHeader) { 40 | headers[@(indexPath.section)] = obj; 41 | } else if (isFooter) { 42 | // 不处理 43 | } else { 44 | //其实用这两句也是可以的 45 | //NSNumber *sectionObj = @(indexPath.section); 46 | //lastCells[sectionObj] = obj; 47 | 48 | UICollectionViewLayoutAttributes *currentAttribute = lastCells[@(indexPath.section)]; 49 | // 确保取到的是section中最后一个cell 50 | if ( ! currentAttribute || indexPath.row > currentAttribute.indexPath.row) { 51 | [lastCells setObject:obj forKey:@(indexPath.section)]; 52 | } 53 | } 54 | 55 | //如果按照正常情况下,header离开屏幕被系统回收,而header的层次关系又与cell相等,如果不去理会,会出现cell在header上面的情况 56 | //通过打印可以知道cell的层次关系zIndex数值为0,我们可以将header的zIndex设置成1,如果不放心,也可以将它设置成非常大,这里随便填了个1024 57 | if (isHeader) { 58 | obj.zIndex = 1024; 59 | } else { 60 | // For iOS 7.0, the cell zIndex should be above sticky section header 61 | obj.zIndex = 1; 62 | } 63 | 64 | }]; 65 | 66 | 67 | [lastCells enumerateKeysAndObjectsUsingBlock:^(NSNumber * _Nonnull key, UICollectionViewLayoutAttributes * _Nonnull obj, BOOL * _Nonnull stop) { 68 | NSIndexPath *indexPath = obj.indexPath; 69 | NSNumber *indexPathKey = @(indexPath.section); 70 | 71 | UICollectionViewLayoutAttributes *header = headers[indexPathKey]; 72 | 73 | if ( ! header) { 74 | // CollectionView自动将不再bounds内的headers移除了,所以,Headers可能为nil.这种情况下我们需要重新将其加回来 automatically removes headers not in bounds 75 | header = [self layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionHeader 76 | atIndexPath:[NSIndexPath indexPathForItem:0 inSection:indexPath.section]]; 77 | 78 | if (!CGSizeEqualToSize(CGSizeZero, header.frame.size)) { 79 | [allItems addObject:header]; 80 | } 81 | } 82 | if (!CGSizeEqualToSize(CGSizeZero, header.frame.size)) { 83 | [self updateHeaderAttributes:header lastCellAttributes:lastCells[indexPathKey]]; 84 | } 85 | }]; 86 | 87 | 88 | return allItems; 89 | 90 | } 91 | ``` 92 | 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /StickyHeaderFlowLayout/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // StickyHeaderFlowLayout 4 | // 5 | // Created by lingyohunl on 16/1/25. 6 | // Copyright © 2016年 yohunl. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | #import "SectionHeaderReusableView.h" 11 | #import "YLStickyHeaderFlowLayout.h" 12 | #import "CollectionViewCell.h" 13 | @interface ViewController () 14 | 15 | @end 16 | 17 | @implementation ViewController 18 | 19 | - (void)viewDidLoad { 20 | [super viewDidLoad]; 21 | [self reloadLayout]; 22 | 23 | self.collectionView.backgroundColor = [UIColor whiteColor]; 24 | [self.collectionView registerClass:[CollectionViewCell class] forCellWithReuseIdentifier:@"UICollectionViewCellID"]; 25 | [self.collectionView registerClass:[SectionHeaderReusableView class] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"HeadIdentifier"]; 26 | [self.collectionView registerClass:[SectionHeaderReusableView class] forSupplementaryViewOfKind:UICollectionElementKindSectionFooter withReuseIdentifier:@"FootIdentifier"]; 27 | 28 | 29 | 30 | } 31 | - (void)reloadLayout { 32 | YLStickyHeaderFlowLayout *layout = (id)self.collectionViewLayout; 33 | 34 | if ([layout isKindOfClass:[YLStickyHeaderFlowLayout class]]) { 35 | layout.itemSize = CGSizeMake((self.view.frame.size.width - 5)/2, 100); 36 | //layout.itemSize = CGSizeMake(self.view.frame.size.width / 3.0, 44); 37 | //layout.sectionInset = UIEdgeInsetsMake(0, 0, 0, 0); 38 | //layout.minimumLineSpacing = 5; 39 | //layout.minimumInteritemSpacing = 5; 40 | layout.disableStickyFlow = NO; 41 | 42 | layout.stickySections = @[@2,@3]; 43 | } 44 | } 45 | 46 | - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView 47 | { 48 | return 5; 49 | } 50 | 51 | - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section 52 | { 53 | return 14; 54 | } 55 | 56 | -(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath 57 | { 58 | CollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"UICollectionViewCellID" forIndexPath:indexPath]; 59 | NSInteger temp = indexPath.section%3; 60 | if (temp == 0) { 61 | cell.imgView.backgroundColor = [UIColor yellowColor]; 62 | } 63 | else if (temp == 1) { 64 | cell.imgView.backgroundColor = [UIColor blueColor]; 65 | } 66 | else if (temp == 2) { 67 | cell.imgView.backgroundColor = [UIColor greenColor]; 68 | } 69 | cell.imgTitleLabel.textColor = [UIColor blackColor]; 70 | cell.imgTitleLabel.text = [NSString stringWithFormat:@"%ld,%ld",(long)indexPath.section,(long)indexPath.row]; 71 | return cell; 72 | } 73 | 74 | - (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath { 75 | 76 | if (kind==UICollectionElementKindSectionFooter) { 77 | SectionHeaderReusableView *footer = [collectionView dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:@"FootIdentifier" forIndexPath:indexPath]; 78 | footer.backgroundColor = [UIColor colorWithRed:0.4016 green:0.4015 blue:0.0 alpha:1.0]; 79 | footer.textLabel.text = [NSString stringWithFormat:@"第%ld个分区的footer",indexPath.section]; 80 | return footer; 81 | } 82 | else if (kind==UICollectionElementKindSectionHeader) { 83 | SectionHeaderReusableView *footer = [collectionView dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:@"HeadIdentifier" forIndexPath:indexPath]; 84 | footer.backgroundColor = [UIColor colorWithRed:0.7127 green:0.1776 blue:0.7478 alpha:1.0]; 85 | footer.textLabel.text = [NSString stringWithFormat:@"第%ld个分区的header",indexPath.section]; 86 | return footer; 87 | } 88 | 89 | return nil; 90 | } 91 | 92 | - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section 93 | { 94 | return CGSizeMake(0, 44); 95 | } 96 | 97 | - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section 98 | { 99 | return CGSizeMake(0, 20); 100 | } 101 | 102 | -(BOOL)prefersStatusBarHidden 103 | { 104 | return YES; 105 | } 106 | 107 | 108 | @end 109 | -------------------------------------------------------------------------------- /StickyHeaderFlowLayout/UIView+Helpers.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+Helpers.h 3 | // 4 | // Created by Andrew Carter on 11/9/11. 5 | /* 6 | 用来进行相对布局,方便的获取view,设置view的某些和大小相关的属性的拓展.测试的添加文字 7 | */ 8 | #import 9 | 10 | @interface UIView (Helpers) 11 | 12 | typedef NS_ENUM(NSUInteger, UIViewAlignment) 13 | { 14 | UIViewAlignmentLeft = 1 << 0, 15 | UIViewAlignmentRight = 1 << 1, 16 | UIViewAlignmentTop = 1 << 2, 17 | UIViewAlignmentBottom = 1 << 3, 18 | 19 | UIViewAlignmentLeftEdge = 1 << 5, 20 | UIViewAlignmentRightEdge = 1 << 6, 21 | UIViewAlignmentTopEdge = 1 << 7, 22 | UIViewAlignmentBottomEdge = 1 << 8, 23 | 24 | UIViewAlignmentHorizontalCenter = 1 << 9, 25 | UIViewAlignmentVerticalCenter = 1 << 10, 26 | }; 27 | 28 | typedef NS_ENUM(NSInteger, VerticalLayoutType) { 29 | VerticalLayoutTypeTop, 30 | VerticalLayoutTypeCenter, 31 | VerticalLayoutTypeBottom 32 | }; 33 | 34 | @property (nonatomic) CGFloat frameOriginX; 35 | @property (nonatomic) CGFloat frameOriginY; 36 | @property (nonatomic) CGFloat frameSizeWidth; 37 | @property (nonatomic) CGFloat frameSizeHeight; 38 | @property (nonatomic) CGSize frameSize; 39 | @property (nonatomic) CGPoint frameOrigin; 40 | 41 | @property (nonatomic) CGFloat frameMaxX; 42 | @property (nonatomic) CGFloat frameMaxY; 43 | 44 | 45 | @property (nonatomic) CGPoint frameCenter; 46 | @property (nonatomic) CGFloat frameCenterX; 47 | @property (nonatomic) CGFloat frameCenterY; 48 | 49 | 50 | 51 | 52 | + (CGRect)alignRect:(CGRect)startingRect 53 | toRect:(CGRect)referenceRect 54 | withAlignment:(UIViewAlignment)alignment 55 | insets:(UIEdgeInsets)insets 56 | andReferenceIsSuperView:(BOOL)isReferenceSuperView; 57 | 58 | // Init 59 | - (id)initWithSize:(CGSize)size; 60 | 61 | //Alignment 62 | - (void)alignRelativeToView:(UIView*)alignView 63 | withAlignment:(UIViewAlignment)alignment 64 | andInsets:(UIEdgeInsets)insets; 65 | 66 | - (void)alignRelativeToSuperView:(UIView*)alignView 67 | withAlignment:(UIViewAlignment)alignment 68 | andInsets:(UIEdgeInsets)insets; 69 | 70 | - (void)centerAlignHorizontalForView:(UIView *)view; 71 | - (void)centerAlignVerticalForView:(UIView *)view; 72 | 73 | - (void)centerAlignHorizontalForView:(UIView *)view offset:(CGFloat)offset; 74 | - (void)centerAlignVerticalForView:(UIView *)view offset:(CGFloat)offset; 75 | - (void)centerAlignForView:(UIView *)view; 76 | 77 | - (void)centerAlignForSuperview; 78 | - (void)centerAlignHorizontalForSuperView; 79 | 80 | - (void)centerAlignVerticalForSuperView; 81 | 82 | - (void)leftAlignForView:(UIView *)view; 83 | - (void)rightAlignForView:(UIView *)view; 84 | - (void)topAlignForView:(UIView *)view; 85 | - (void)bottomAlignForView:(UIView *)view; 86 | 87 | - (void)leftAlignForView:(UIView *)view offset:(CGFloat)offset; 88 | - (void)rightAlignForView:(UIView *)view offset:(CGFloat)offset; 89 | - (void)topAlignForView:(UIView *)view offset:(CGFloat)offset; 90 | - (void)bottomAlignForView:(UIView *)view offset:(CGFloat)offset; 91 | 92 | - (void)topAlignForSuperView; 93 | - (void)bottomAlignForSuperView; 94 | - (void)leftAlignForSuperView; 95 | - (void)rightAlignForSuperView; 96 | 97 | - (void)topAlignForSuperViewOffset:(CGFloat)offset; 98 | - (void)bottomAlignForSuperViewOffset:(CGFloat)offset; 99 | - (void)leftAlignForSuperViewOffset:(CGFloat)offset; 100 | - (void)rightAlignForSuperViewOffset:(CGFloat)offset; 101 | 102 | 103 | 104 | //Positioning Relative to View 105 | - (void)setFrameOriginYBelowView:(UIView *)view; 106 | - (void)setFrameOriginYAboveView:(UIView *)view; 107 | - (void)setFrameOriginXRightOfView:(UIView *)view; 108 | - (void)setFrameOriginXLeftOfView:(UIView *)view; 109 | 110 | - (void)setFrameOriginYBelowView:(UIView *)view offset:(CGFloat)offset; 111 | - (void)setFrameOriginYAboveView:(UIView *)view offset:(CGFloat)offset; 112 | - (void)setFrameOriginXRightOfView:(UIView *)view offset:(CGFloat)offset; 113 | - (void)setFrameOriginXLeftOfView:(UIView *)view offset:(CGFloat)offset; 114 | 115 | //Resizing 116 | - (void)setFrameSizeToImageSize; 117 | 118 | //Making rounded corners 119 | - (void)roundCornersTopLeft:(CGFloat)topLeft topRight:(CGFloat)topRight bottomLeft:(CGFloat)bottomLeft bottomRight:(CGFloat)bottomRight; 120 | static inline UIImage* createRoundedCornerMask(CGRect rect, CGFloat radius_tl, CGFloat radius_tr, CGFloat radius_bl, CGFloat radius_br); 121 | 122 | //Fade Edges 123 | - (void)setHorizontalFadeMaskWithLeftOffset:(CGFloat)leftOffset rightOffset:(CGFloat)rightOffset; 124 | - (void)setVerticalFadeMaskWithTopOffset:(CGFloat)topOffset bottomOffset:(CGFloat)bottomOffset; 125 | 126 | /** 127 | Create a snapshot image of the complete view hierarchy. 128 | */ 129 | - (UIImage *)snapshotImage; 130 | 131 | /** 132 | Create a snapshot image of the complete view hierarchy. 133 | @discussion It's faster than "snapshotImage", but may cause screen updates. 134 | See -[UIView drawViewHierarchyInRect:afterScreenUpdates:] for more information. 135 | */ 136 | - (UIImage *)snapshotImageAfterScreenUpdates:(BOOL)afterUpdates; 137 | 138 | /* 139 | * iOS 6 and prior: calls -snapshotView and wraps the result in a UIImageView 140 | * on iOS 7 and up: calls and returns the stock -snapshotView method 141 | */ 142 | 143 | //- (UIView *)snapshotImageView; 144 | 145 | /* Debug 146 | * 147 | * The functions below are only performed in DEBUG mode 148 | * @param "showInRelease" will apply the function in both DEBUG and RELEASE mode 149 | */ 150 | - (void)showDebugFrame; 151 | - (void)hideDebugFrame; 152 | - (void)showDebugFrame:(BOOL)showInRelease; 153 | 154 | - (void)logFrameChanges; 155 | 156 | // Layout Helpers 157 | + (CGFloat)alignVertical:(VerticalLayoutType)type 158 | views:(NSArray*)views 159 | withSpacing:(CGFloat)spacing 160 | inView:(UIView*)view 161 | shrinkSpacingToFit:(BOOL)shrinkSpacingToFit; 162 | 163 | + (CGFloat)alignVertical:(VerticalLayoutType)type 164 | views:(NSArray*)views 165 | withSpacingArray:(NSArray*)spacing 166 | inView:(UIView*)view 167 | shrinkSpacingToFit:(BOOL)shrinkSpacingToFit; 168 | 169 | // subviews 170 | + (UIView *)firstResponder; 171 | - (UIView *)firstResponderInSubviews; 172 | - (NSArray *)subviewsOfClass:(Class)aClass recursive:(BOOL)recursive; 173 | 174 | /** 175 | * 获取cell所在的tableview,一般只是用来确定UITableViewCell的所属UITableview 176 | * 177 | * @return 找到的UITableview 178 | */ 179 | - (UITableView *)fdd_superTableView; 180 | 181 | 182 | /** 183 | * 获取view所在的tableViewCell 184 | * 185 | * @return view所在的tableViewCell 186 | */ 187 | - (UITableViewCell*)fdd_superTableCell; 188 | 189 | /** 190 | * 获取第一响应对象,可以是自己,或者自己的子类中寻找 191 | * 192 | * @return 第一响应对象 193 | */ 194 | - (UIView *)fdd_findFirstResponder; 195 | 196 | - (UIViewController *)fdd_viewController; 197 | 198 | @end 199 | -------------------------------------------------------------------------------- /StickyHeaderFlowLayout/YLStickyHeaderFlowLayout.m: -------------------------------------------------------------------------------- 1 | // 2 | // YLStickyHeaderFlowLayout.m 3 | // StickyHeaderFlowLayout 4 | // 5 | // Created by lingyohunl on 16/1/25. 6 | // Copyright © 2016年 yohunl. All rights reserved. 7 | // 8 | 9 | #import "YLStickyHeaderFlowLayout.h" 10 | 11 | @implementation YLStickyHeaderFlowLayout 12 | #pragma mark - 设置方法 13 | 14 | - (void)setStickySections:(NSArray *)stickySections { 15 | _stickySections = stickySections; 16 | _disableStickyFlow = (_stickySections.count > 0)?NO:YES; 17 | } 18 | #pragma mark - 重载方法 19 | - (BOOL) shouldInvalidateLayoutForBoundsChange:(CGRect)newBound 20 | { 21 | if (self.disableStickyFlow) { 22 | return [super shouldInvalidateLayoutForBoundsChange:newBound]; 23 | } 24 | /*每滑动一点,就会调用这个,然后layoutAttributesForElementsInRect又被调用,其实有点浪费了,但是我们又没有好的方式避免,毕竟,如果要悬浮,就要每时每刻计算是否应该悬浮 25 | 一旦滑动就实时调用上面这个layoutAttributesForElementsInRect:方法 26 | 实际上我们很多自定义的layout,都是要根据实际需要,返回YES或者NO的 27 | 28 | 当collection view的bounds改变时,布局需要告诉collection view是否需要重新计算布局。我的猜想是:当collection view改变大小时,大多数布局会被作废,比如设备旋转的时候。因此,一个幼稚的实现可能只会简单的返回YES。虽然实现功能很重要,但是scroll view的bounds在滚动时也会改变,这意味着你的布局每秒会被丢弃多次。根据计算的复杂性判断,这将会对性能产生很大的影响。 29 | 30 | 当collection view的宽度改变时,我们自定义的布局必须被丢弃,但这滚动并不会影响到布局。幸运的是,collection view将它的新bounds传给shouldInvalidateLayoutForBoundsChange: method。这样我们便能比较视图当前的bounds和新的bounds来确定返回值: 31 | 32 | 33 | - (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds { 34 | // not sure about his.. 35 | if ((self.collectionView.bounds.size.width != newBounds.size.width) || (self.collectionView.bounds.size.height != newBounds.size.height)) { 36 | return YES; 37 | } 38 | return NO; 39 | } 40 | 其子类flowLayout的 41 | - (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds { 42 | // we need to recalculate on width changes 43 | if ((_visibleBounds.size.width != newBounds.size.width && self.scrollDirection == PSTCollectionViewScrollDirectionVertical) || (_visibleBounds.size.height != newBounds.size.height && self.scrollDirection == PSTCollectionViewScrollDirectionHorizontal)) { 44 | _visibleBounds = self.collectionView.bounds; 45 | return YES; 46 | } 47 | return NO; 48 | } 49 | */ 50 | return YES; 51 | } 52 | 53 | - (NSArray *) layoutAttributesForElementsInRect:(CGRect)rect { 54 | if (self.disableStickyFlow) { 55 | return [super layoutAttributesForElementsInRect:rect]; 56 | } 57 | NSMutableArray *allItems ; 58 | //collectionView中的item(包括cell和header、footer这些)的《结构信息》.关键!!!!cell,header,footer都是利用这个数组的,在这个中,原来创建的section等,要按顺序存放到数组中来! 59 | NSArray *originalAttributes = [super layoutAttributesForElementsInRect:rect]; 60 | //allItems = (NSMutableArray *)originalAttributes ; 61 | allItems = [originalAttributes mutableCopy];//实际上layoutAttributesForElementsInRect返回的是NSMutableArray,所以,可以直接强转 62 | 63 | NSMutableDictionary *headers = [[NSMutableDictionary alloc] init];//存放每个section的header 64 | NSMutableDictionary *lastCells = [[NSMutableDictionary alloc] init];//存放的是每个section的最后一个cell 65 | [allItems enumerateObjectsUsingBlock:^(UICollectionViewLayoutAttributes * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 66 | 67 | NSIndexPath *indexPath = [obj indexPath]; 68 | BOOL isHeader = [[obj representedElementKind] isEqualToString:UICollectionElementKindSectionHeader]; 69 | BOOL isFooter = [[obj representedElementKind] isEqualToString:UICollectionElementKindSectionFooter]; 70 | 71 | if (isHeader) { 72 | headers[@(indexPath.section)] = obj; 73 | } else if (isFooter) { 74 | // 不处理 75 | } else { 76 | //其实用这两句也是可以的 77 | //NSNumber *sectionObj = @(indexPath.section); 78 | //lastCells[sectionObj] = obj; 79 | 80 | UICollectionViewLayoutAttributes *currentAttribute = lastCells[@(indexPath.section)]; 81 | // 确保取到的是section中最后一个cell 82 | if ( ! currentAttribute || indexPath.row > currentAttribute.indexPath.row) { 83 | [lastCells setObject:obj forKey:@(indexPath.section)]; 84 | } 85 | } 86 | 87 | //如果按照正常情况下,header离开屏幕被系统回收,而header的层次关系又与cell相等,如果不去理会,会出现cell在header上面的情况 88 | //通过打印可以知道cell的层次关系zIndex数值为0,我们可以将header的zIndex设置成1,如果不放心,也可以将它设置成非常大,这里随便填了个1024 89 | if (isHeader) { 90 | obj.zIndex = 1024; 91 | } else { 92 | // For iOS 7.0, the cell zIndex should be above sticky section header 93 | obj.zIndex = 1; 94 | } 95 | 96 | }]; 97 | 98 | 99 | [lastCells enumerateKeysAndObjectsUsingBlock:^(NSNumber * _Nonnull key, UICollectionViewLayoutAttributes * _Nonnull obj, BOOL * _Nonnull stop) { 100 | NSIndexPath *indexPath = obj.indexPath; 101 | NSNumber *indexPathKey = @(indexPath.section); 102 | 103 | UICollectionViewLayoutAttributes *header = headers[indexPathKey]; 104 | 105 | if ( ! header) { 106 | // CollectionView自动将不再bounds内的headers移除了,所以,Headers可能为nil.这种情况下我们需要重新将其加回来 automatically removes headers not in bounds 107 | header = [self layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionHeader 108 | atIndexPath:[NSIndexPath indexPathForItem:0 inSection:indexPath.section]]; 109 | 110 | if (!CGSizeEqualToSize(CGSizeZero, header.frame.size)) { 111 | if ([self haveSection:indexPath.section]) { 112 | [allItems addObject:header]; 113 | } 114 | 115 | 116 | } 117 | } 118 | if (!CGSizeEqualToSize(CGSizeZero, header.frame.size)) { 119 | 120 | if ([self haveSection:indexPath.section]) { 121 | [self updateHeaderAttributes:header lastCellAttributes:lastCells[indexPathKey]]; 122 | } 123 | 124 | 125 | } 126 | }]; 127 | 128 | 129 | return allItems; 130 | 131 | } 132 | 133 | 134 | #pragma mark - 辅助方法 135 | - (void)updateHeaderAttributes:(UICollectionViewLayoutAttributes *)attributes lastCellAttributes:(UICollectionViewLayoutAttributes *)lastCellAttributes 136 | { 137 | //lastCellAttributes是section中最后一个cell的attribute 138 | CGRect currentBounds = self.collectionView.bounds; 139 | 140 | CGPoint origin = attributes.frame.origin; 141 | 142 | //sectionMaxY是header的originY最大可达到的地方 143 | CGFloat sectionMaxY = CGRectGetMaxY(lastCellAttributes.frame) - CGRectGetHeight(attributes.frame); 144 | CGFloat y = CGRectGetMinY(currentBounds) + self.collectionView.contentInset.top; 145 | 146 | 147 | CGFloat originY = MIN(MAX(y, attributes.frame.origin.y), sectionMaxY); 148 | 149 | origin.y = originY; 150 | 151 | attributes.frame = (CGRect){ 152 | origin, 153 | attributes.frame.size 154 | }; 155 | } 156 | 157 | 158 | 159 | - (BOOL)haveSection:(NSInteger )section { 160 | __block BOOL flag = NO; 161 | if (_stickySections.count == 0) {//为空表示所有的header都是要悬停的 162 | flag = YES; 163 | } 164 | else { 165 | [_stickySections enumerateObjectsUsingBlock:^(NSNumber * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 166 | if (obj.integerValue == section) { 167 | flag = YES; 168 | *stop = YES; 169 | } 170 | }]; 171 | } 172 | 173 | return flag; 174 | } 175 | @end 176 | -------------------------------------------------------------------------------- /StickyHeaderFlowLayout.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 093ADD351C561ABE00514643 /* SectionHeaderReusableView.m in Sources */ = {isa = PBXBuildFile; fileRef = 093ADD341C561ABE00514643 /* SectionHeaderReusableView.m */; }; 11 | 093ADD391C56228600514643 /* CollectionViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 093ADD381C56228600514643 /* CollectionViewCell.m */; }; 12 | 093ADD3F1C5623AC00514643 /* UIView+Helpers.m in Sources */ = {isa = PBXBuildFile; fileRef = 093ADD3E1C5623AC00514643 /* UIView+Helpers.m */; }; 13 | 09AAE1641C56176200CD89B8 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 09AAE1631C56176200CD89B8 /* main.m */; }; 14 | 09AAE1671C56176200CD89B8 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 09AAE1661C56176200CD89B8 /* AppDelegate.m */; }; 15 | 09AAE16A1C56176200CD89B8 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 09AAE1691C56176200CD89B8 /* ViewController.m */; }; 16 | 09AAE16D1C56176200CD89B8 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 09AAE16B1C56176200CD89B8 /* Main.storyboard */; }; 17 | 09AAE16F1C56176200CD89B8 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 09AAE16E1C56176200CD89B8 /* Assets.xcassets */; }; 18 | 09AAE1721C56176200CD89B8 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 09AAE1701C56176200CD89B8 /* LaunchScreen.storyboard */; }; 19 | 09AAE17C1C5617E200CD89B8 /* YLStickyHeaderFlowLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = 09AAE17B1C5617E200CD89B8 /* YLStickyHeaderFlowLayout.m */; }; 20 | /* End PBXBuildFile section */ 21 | 22 | /* Begin PBXFileReference section */ 23 | 093ADD331C561ABE00514643 /* SectionHeaderReusableView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SectionHeaderReusableView.h; sourceTree = ""; }; 24 | 093ADD341C561ABE00514643 /* SectionHeaderReusableView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SectionHeaderReusableView.m; sourceTree = ""; }; 25 | 093ADD371C56228600514643 /* CollectionViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CollectionViewCell.h; sourceTree = ""; }; 26 | 093ADD381C56228600514643 /* CollectionViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CollectionViewCell.m; sourceTree = ""; }; 27 | 093ADD3D1C5623AC00514643 /* UIView+Helpers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIView+Helpers.h"; sourceTree = ""; }; 28 | 093ADD3E1C5623AC00514643 /* UIView+Helpers.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIView+Helpers.m"; sourceTree = ""; }; 29 | 09AAE15F1C56176200CD89B8 /* StickyHeaderFlowLayout.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = StickyHeaderFlowLayout.app; sourceTree = BUILT_PRODUCTS_DIR; }; 30 | 09AAE1631C56176200CD89B8 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 31 | 09AAE1651C56176200CD89B8 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 32 | 09AAE1661C56176200CD89B8 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 33 | 09AAE1681C56176200CD89B8 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 34 | 09AAE1691C56176200CD89B8 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 35 | 09AAE16C1C56176200CD89B8 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 36 | 09AAE16E1C56176200CD89B8 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 37 | 09AAE1711C56176200CD89B8 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 38 | 09AAE1731C56176200CD89B8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 39 | 09AAE17A1C5617E200CD89B8 /* YLStickyHeaderFlowLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = YLStickyHeaderFlowLayout.h; sourceTree = ""; }; 40 | 09AAE17B1C5617E200CD89B8 /* YLStickyHeaderFlowLayout.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = YLStickyHeaderFlowLayout.m; sourceTree = ""; }; 41 | /* End PBXFileReference section */ 42 | 43 | /* Begin PBXFrameworksBuildPhase section */ 44 | 09AAE15C1C56176200CD89B8 /* Frameworks */ = { 45 | isa = PBXFrameworksBuildPhase; 46 | buildActionMask = 2147483647; 47 | files = ( 48 | ); 49 | runOnlyForDeploymentPostprocessing = 0; 50 | }; 51 | /* End PBXFrameworksBuildPhase section */ 52 | 53 | /* Begin PBXGroup section */ 54 | 09AAE1561C56176200CD89B8 = { 55 | isa = PBXGroup; 56 | children = ( 57 | 09AAE1611C56176200CD89B8 /* StickyHeaderFlowLayout */, 58 | 09AAE1601C56176200CD89B8 /* Products */, 59 | ); 60 | sourceTree = ""; 61 | }; 62 | 09AAE1601C56176200CD89B8 /* Products */ = { 63 | isa = PBXGroup; 64 | children = ( 65 | 09AAE15F1C56176200CD89B8 /* StickyHeaderFlowLayout.app */, 66 | ); 67 | name = Products; 68 | sourceTree = ""; 69 | }; 70 | 09AAE1611C56176200CD89B8 /* StickyHeaderFlowLayout */ = { 71 | isa = PBXGroup; 72 | children = ( 73 | 09AAE1791C5617AC00CD89B8 /* Layout */, 74 | 09AAE1651C56176200CD89B8 /* AppDelegate.h */, 75 | 09AAE1661C56176200CD89B8 /* AppDelegate.m */, 76 | 09AAE1681C56176200CD89B8 /* ViewController.h */, 77 | 09AAE1691C56176200CD89B8 /* ViewController.m */, 78 | 093ADD331C561ABE00514643 /* SectionHeaderReusableView.h */, 79 | 093ADD341C561ABE00514643 /* SectionHeaderReusableView.m */, 80 | 093ADD371C56228600514643 /* CollectionViewCell.h */, 81 | 093ADD381C56228600514643 /* CollectionViewCell.m */, 82 | 093ADD3D1C5623AC00514643 /* UIView+Helpers.h */, 83 | 093ADD3E1C5623AC00514643 /* UIView+Helpers.m */, 84 | 09AAE16B1C56176200CD89B8 /* Main.storyboard */, 85 | 09AAE16E1C56176200CD89B8 /* Assets.xcassets */, 86 | 09AAE1701C56176200CD89B8 /* LaunchScreen.storyboard */, 87 | 09AAE1731C56176200CD89B8 /* Info.plist */, 88 | 09AAE1621C56176200CD89B8 /* Supporting Files */, 89 | ); 90 | path = StickyHeaderFlowLayout; 91 | sourceTree = ""; 92 | }; 93 | 09AAE1621C56176200CD89B8 /* Supporting Files */ = { 94 | isa = PBXGroup; 95 | children = ( 96 | 09AAE1631C56176200CD89B8 /* main.m */, 97 | ); 98 | name = "Supporting Files"; 99 | sourceTree = ""; 100 | }; 101 | 09AAE1791C5617AC00CD89B8 /* Layout */ = { 102 | isa = PBXGroup; 103 | children = ( 104 | 09AAE17A1C5617E200CD89B8 /* YLStickyHeaderFlowLayout.h */, 105 | 09AAE17B1C5617E200CD89B8 /* YLStickyHeaderFlowLayout.m */, 106 | ); 107 | name = Layout; 108 | sourceTree = ""; 109 | }; 110 | /* End PBXGroup section */ 111 | 112 | /* Begin PBXNativeTarget section */ 113 | 09AAE15E1C56176200CD89B8 /* StickyHeaderFlowLayout */ = { 114 | isa = PBXNativeTarget; 115 | buildConfigurationList = 09AAE1761C56176200CD89B8 /* Build configuration list for PBXNativeTarget "StickyHeaderFlowLayout" */; 116 | buildPhases = ( 117 | 09AAE15B1C56176200CD89B8 /* Sources */, 118 | 09AAE15C1C56176200CD89B8 /* Frameworks */, 119 | 09AAE15D1C56176200CD89B8 /* Resources */, 120 | ); 121 | buildRules = ( 122 | ); 123 | dependencies = ( 124 | ); 125 | name = StickyHeaderFlowLayout; 126 | productName = StickyHeaderFlowLayout; 127 | productReference = 09AAE15F1C56176200CD89B8 /* StickyHeaderFlowLayout.app */; 128 | productType = "com.apple.product-type.application"; 129 | }; 130 | /* End PBXNativeTarget section */ 131 | 132 | /* Begin PBXProject section */ 133 | 09AAE1571C56176200CD89B8 /* Project object */ = { 134 | isa = PBXProject; 135 | attributes = { 136 | LastUpgradeCheck = 0720; 137 | ORGANIZATIONNAME = yohunl; 138 | TargetAttributes = { 139 | 09AAE15E1C56176200CD89B8 = { 140 | CreatedOnToolsVersion = 7.2; 141 | }; 142 | }; 143 | }; 144 | buildConfigurationList = 09AAE15A1C56176200CD89B8 /* Build configuration list for PBXProject "StickyHeaderFlowLayout" */; 145 | compatibilityVersion = "Xcode 3.2"; 146 | developmentRegion = English; 147 | hasScannedForEncodings = 0; 148 | knownRegions = ( 149 | en, 150 | Base, 151 | ); 152 | mainGroup = 09AAE1561C56176200CD89B8; 153 | productRefGroup = 09AAE1601C56176200CD89B8 /* Products */; 154 | projectDirPath = ""; 155 | projectRoot = ""; 156 | targets = ( 157 | 09AAE15E1C56176200CD89B8 /* StickyHeaderFlowLayout */, 158 | ); 159 | }; 160 | /* End PBXProject section */ 161 | 162 | /* Begin PBXResourcesBuildPhase section */ 163 | 09AAE15D1C56176200CD89B8 /* Resources */ = { 164 | isa = PBXResourcesBuildPhase; 165 | buildActionMask = 2147483647; 166 | files = ( 167 | 09AAE1721C56176200CD89B8 /* LaunchScreen.storyboard in Resources */, 168 | 09AAE16F1C56176200CD89B8 /* Assets.xcassets in Resources */, 169 | 09AAE16D1C56176200CD89B8 /* Main.storyboard in Resources */, 170 | ); 171 | runOnlyForDeploymentPostprocessing = 0; 172 | }; 173 | /* End PBXResourcesBuildPhase section */ 174 | 175 | /* Begin PBXSourcesBuildPhase section */ 176 | 09AAE15B1C56176200CD89B8 /* Sources */ = { 177 | isa = PBXSourcesBuildPhase; 178 | buildActionMask = 2147483647; 179 | files = ( 180 | 093ADD391C56228600514643 /* CollectionViewCell.m in Sources */, 181 | 09AAE17C1C5617E200CD89B8 /* YLStickyHeaderFlowLayout.m in Sources */, 182 | 09AAE16A1C56176200CD89B8 /* ViewController.m in Sources */, 183 | 093ADD351C561ABE00514643 /* SectionHeaderReusableView.m in Sources */, 184 | 093ADD3F1C5623AC00514643 /* UIView+Helpers.m in Sources */, 185 | 09AAE1671C56176200CD89B8 /* AppDelegate.m in Sources */, 186 | 09AAE1641C56176200CD89B8 /* main.m in Sources */, 187 | ); 188 | runOnlyForDeploymentPostprocessing = 0; 189 | }; 190 | /* End PBXSourcesBuildPhase section */ 191 | 192 | /* Begin PBXVariantGroup section */ 193 | 09AAE16B1C56176200CD89B8 /* Main.storyboard */ = { 194 | isa = PBXVariantGroup; 195 | children = ( 196 | 09AAE16C1C56176200CD89B8 /* Base */, 197 | ); 198 | name = Main.storyboard; 199 | sourceTree = ""; 200 | }; 201 | 09AAE1701C56176200CD89B8 /* LaunchScreen.storyboard */ = { 202 | isa = PBXVariantGroup; 203 | children = ( 204 | 09AAE1711C56176200CD89B8 /* Base */, 205 | ); 206 | name = LaunchScreen.storyboard; 207 | sourceTree = ""; 208 | }; 209 | /* End PBXVariantGroup section */ 210 | 211 | /* Begin XCBuildConfiguration section */ 212 | 09AAE1741C56176200CD89B8 /* Debug */ = { 213 | isa = XCBuildConfiguration; 214 | buildSettings = { 215 | ALWAYS_SEARCH_USER_PATHS = NO; 216 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 217 | CLANG_CXX_LIBRARY = "libc++"; 218 | CLANG_ENABLE_MODULES = YES; 219 | CLANG_ENABLE_OBJC_ARC = YES; 220 | CLANG_WARN_BOOL_CONVERSION = YES; 221 | CLANG_WARN_CONSTANT_CONVERSION = YES; 222 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 223 | CLANG_WARN_EMPTY_BODY = YES; 224 | CLANG_WARN_ENUM_CONVERSION = YES; 225 | CLANG_WARN_INT_CONVERSION = YES; 226 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 227 | CLANG_WARN_UNREACHABLE_CODE = YES; 228 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 229 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 230 | COPY_PHASE_STRIP = NO; 231 | DEBUG_INFORMATION_FORMAT = dwarf; 232 | ENABLE_STRICT_OBJC_MSGSEND = YES; 233 | ENABLE_TESTABILITY = YES; 234 | GCC_C_LANGUAGE_STANDARD = gnu99; 235 | GCC_DYNAMIC_NO_PIC = NO; 236 | GCC_NO_COMMON_BLOCKS = YES; 237 | GCC_OPTIMIZATION_LEVEL = 0; 238 | GCC_PREPROCESSOR_DEFINITIONS = ( 239 | "DEBUG=1", 240 | "$(inherited)", 241 | ); 242 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 243 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 244 | GCC_WARN_UNDECLARED_SELECTOR = YES; 245 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 246 | GCC_WARN_UNUSED_FUNCTION = YES; 247 | GCC_WARN_UNUSED_VARIABLE = YES; 248 | IPHONEOS_DEPLOYMENT_TARGET = 9.2; 249 | MTL_ENABLE_DEBUG_INFO = YES; 250 | ONLY_ACTIVE_ARCH = YES; 251 | SDKROOT = iphoneos; 252 | }; 253 | name = Debug; 254 | }; 255 | 09AAE1751C56176200CD89B8 /* Release */ = { 256 | isa = XCBuildConfiguration; 257 | buildSettings = { 258 | ALWAYS_SEARCH_USER_PATHS = NO; 259 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 260 | CLANG_CXX_LIBRARY = "libc++"; 261 | CLANG_ENABLE_MODULES = YES; 262 | CLANG_ENABLE_OBJC_ARC = YES; 263 | CLANG_WARN_BOOL_CONVERSION = YES; 264 | CLANG_WARN_CONSTANT_CONVERSION = YES; 265 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 266 | CLANG_WARN_EMPTY_BODY = YES; 267 | CLANG_WARN_ENUM_CONVERSION = YES; 268 | CLANG_WARN_INT_CONVERSION = YES; 269 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 270 | CLANG_WARN_UNREACHABLE_CODE = YES; 271 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 272 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 273 | COPY_PHASE_STRIP = NO; 274 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 275 | ENABLE_NS_ASSERTIONS = NO; 276 | ENABLE_STRICT_OBJC_MSGSEND = YES; 277 | GCC_C_LANGUAGE_STANDARD = gnu99; 278 | GCC_NO_COMMON_BLOCKS = YES; 279 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 280 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 281 | GCC_WARN_UNDECLARED_SELECTOR = YES; 282 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 283 | GCC_WARN_UNUSED_FUNCTION = YES; 284 | GCC_WARN_UNUSED_VARIABLE = YES; 285 | IPHONEOS_DEPLOYMENT_TARGET = 9.2; 286 | MTL_ENABLE_DEBUG_INFO = NO; 287 | SDKROOT = iphoneos; 288 | VALIDATE_PRODUCT = YES; 289 | }; 290 | name = Release; 291 | }; 292 | 09AAE1771C56176200CD89B8 /* Debug */ = { 293 | isa = XCBuildConfiguration; 294 | buildSettings = { 295 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 296 | INFOPLIST_FILE = StickyHeaderFlowLayout/Info.plist; 297 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 298 | PRODUCT_BUNDLE_IDENTIFIER = Yohunl.StickyHeaderFlowLayout; 299 | PRODUCT_NAME = "$(TARGET_NAME)"; 300 | }; 301 | name = Debug; 302 | }; 303 | 09AAE1781C56176200CD89B8 /* Release */ = { 304 | isa = XCBuildConfiguration; 305 | buildSettings = { 306 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 307 | INFOPLIST_FILE = StickyHeaderFlowLayout/Info.plist; 308 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 309 | PRODUCT_BUNDLE_IDENTIFIER = Yohunl.StickyHeaderFlowLayout; 310 | PRODUCT_NAME = "$(TARGET_NAME)"; 311 | }; 312 | name = Release; 313 | }; 314 | /* End XCBuildConfiguration section */ 315 | 316 | /* Begin XCConfigurationList section */ 317 | 09AAE15A1C56176200CD89B8 /* Build configuration list for PBXProject "StickyHeaderFlowLayout" */ = { 318 | isa = XCConfigurationList; 319 | buildConfigurations = ( 320 | 09AAE1741C56176200CD89B8 /* Debug */, 321 | 09AAE1751C56176200CD89B8 /* Release */, 322 | ); 323 | defaultConfigurationIsVisible = 0; 324 | defaultConfigurationName = Release; 325 | }; 326 | 09AAE1761C56176200CD89B8 /* Build configuration list for PBXNativeTarget "StickyHeaderFlowLayout" */ = { 327 | isa = XCConfigurationList; 328 | buildConfigurations = ( 329 | 09AAE1771C56176200CD89B8 /* Debug */, 330 | 09AAE1781C56176200CD89B8 /* Release */, 331 | ); 332 | defaultConfigurationIsVisible = 0; 333 | defaultConfigurationName = Release; 334 | }; 335 | /* End XCConfigurationList section */ 336 | }; 337 | rootObject = 09AAE1571C56176200CD89B8 /* Project object */; 338 | } 339 | -------------------------------------------------------------------------------- /StickyHeaderFlowLayout/UIView+Helpers.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+Helpers.m 3 | // 4 | // Created by Andrew Carter on 11/9/11. 5 | 6 | #import "UIView+Helpers.h" 7 | 8 | #import 9 | 10 | #define nameOfVar(x) [NSString stringWithFormat:@"%s", #x] 11 | 12 | static inline CGRect CGRectRound(CGRect rect) {return CGRectMake((NSInteger)rect.origin.x, (NSInteger)rect.origin.y, (NSInteger)rect.size.width, (NSInteger)rect.size.height); } 13 | static NSString * const UIVIEW_HELPERS_FRAME_KVO_KEY = @"frame"; 14 | 15 | @implementation UIView (Helpers) 16 | 17 | #pragma mark - 18 | #pragma mark Init 19 | 20 | - (id)initWithSize:(CGSize)size 21 | { 22 | self = [self init]; 23 | if (self) 24 | { 25 | [self setFrameSize:size]; 26 | } 27 | return self; 28 | } 29 | 30 | + (CGRect)alignRect:(CGRect)startingRect toRect:(CGRect)referenceRect withAlignment:(UIViewAlignment)alignment insets:(UIEdgeInsets)insets andReferenceIsSuperView:(BOOL)isReferenceSuperView 31 | { 32 | CGRect newRect = startingRect; 33 | 34 | // X alignments 35 | if (alignment & UIViewAlignmentLeft) 36 | { 37 | newRect.origin.x += CGRectGetMinX(referenceRect) - (CGRectGetWidth(startingRect) + insets.right); 38 | } 39 | else if (alignment & UIViewAlignmentRight) 40 | { 41 | newRect.origin.x = CGRectGetMaxX(referenceRect) + insets.left; 42 | } 43 | else if (alignment & UIViewAlignmentLeftEdge) 44 | { 45 | if (isReferenceSuperView) 46 | { 47 | newRect.origin.x = insets.left; 48 | } 49 | else 50 | { 51 | newRect.origin.x = referenceRect.origin.x + insets.left; 52 | } 53 | } 54 | else if (alignment & UIViewAlignmentRightEdge) 55 | { 56 | if (isReferenceSuperView) 57 | { 58 | newRect.origin.x = CGRectGetWidth(referenceRect) - (CGRectGetWidth(startingRect) + insets.right); 59 | } 60 | else 61 | { 62 | newRect.origin.x = CGRectGetMaxX(referenceRect) - (CGRectGetWidth(startingRect) + insets.right); 63 | } 64 | } 65 | else if (alignment & UIViewAlignmentHorizontalCenter) 66 | { 67 | if (isReferenceSuperView) 68 | { 69 | newRect.origin.x = (((CGRectGetWidth(referenceRect) - CGRectGetWidth(startingRect))) / 2.0f); 70 | } 71 | else 72 | { 73 | newRect.origin.x = CGRectGetMinX(referenceRect) + 74 | (((CGRectGetWidth(referenceRect) - CGRectGetWidth(startingRect))) / 2.0f); 75 | } 76 | } 77 | 78 | // Y alignments 79 | if (alignment & UIViewAlignmentTop) 80 | { 81 | newRect.origin.y = CGRectGetMinY(referenceRect) - (CGRectGetHeight(startingRect) + insets.bottom); 82 | } 83 | else if (alignment & UIViewAlignmentBottom) 84 | { 85 | newRect.origin.y = CGRectGetMaxY(referenceRect) + insets.top; 86 | } 87 | else if (alignment & UIViewAlignmentBottomEdge) 88 | { 89 | if (isReferenceSuperView) 90 | { 91 | newRect.origin.y = CGRectGetHeight(referenceRect) - (CGRectGetHeight(startingRect) + insets.bottom); 92 | } 93 | else 94 | { 95 | newRect.origin.y = CGRectGetMaxY(referenceRect) - (CGRectGetHeight(startingRect) + insets.bottom); 96 | } 97 | } 98 | else if (alignment & UIViewAlignmentTopEdge) 99 | { 100 | if (isReferenceSuperView) 101 | { 102 | newRect.origin.y = insets.top; 103 | } 104 | else 105 | { 106 | newRect.origin.y = CGRectGetMinY(referenceRect) + insets.top; 107 | } 108 | } 109 | else if (alignment & UIViewAlignmentVerticalCenter) 110 | { 111 | if (isReferenceSuperView) 112 | { 113 | newRect.origin.y = ((CGRectGetHeight(referenceRect) - CGRectGetHeight(startingRect)) / 2.0f) + insets.top - insets.bottom; 114 | } 115 | else 116 | { 117 | newRect.origin.y = CGRectGetMinY(referenceRect) + 118 | ((CGRectGetHeight(referenceRect) - CGRectGetHeight(startingRect)) / 2.0f) + insets.top - insets.bottom; 119 | } 120 | } 121 | 122 | return CGRectIntegral(newRect); 123 | } 124 | 125 | - (void)alignRelativeToView:(UIView*)alignView withAlignment:(UIViewAlignment)alignment andInsets:(UIEdgeInsets)insets 126 | { 127 | [self setFrame:[UIView alignRect:[self frame] 128 | toRect:[alignView frame] 129 | withAlignment:alignment 130 | insets:insets 131 | andReferenceIsSuperView:NO]]; 132 | } 133 | 134 | - (void)alignRelativeToSuperView:(UIView*)alignView withAlignment:(UIViewAlignment)alignment andInsets:(UIEdgeInsets)insets 135 | { 136 | [self setFrame:[UIView alignRect:[self frame] 137 | toRect:[alignView frame] 138 | withAlignment:alignment 139 | insets:insets 140 | andReferenceIsSuperView:YES]]; 141 | } 142 | 143 | 144 | #pragma mark - 145 | #pragma mark Alignment 146 | 147 | - (void)centerAlignHorizontalForView:(UIView *)view 148 | { 149 | [self centerAlignHorizontalForView:view offset:0]; 150 | } 151 | 152 | - (void)centerAlignVerticalForView:(UIView *)view 153 | { 154 | [self centerAlignVerticalForView:view offset:0]; 155 | } 156 | 157 | - (void)centerAlignHorizontalForSuperView 158 | { 159 | [self centerAlignHorizontalForView:[self superview]]; 160 | } 161 | 162 | - (void)centerAlignVerticalForSuperView 163 | { 164 | [self centerAlignVerticalForView:[self superview]]; 165 | } 166 | 167 | - (void)centerAlignHorizontalForSuperView:(CGFloat)offset 168 | { 169 | [self centerAlignHorizontalForView:[self superview] offset:offset]; 170 | } 171 | 172 | - (void)centerAlignVerticalForSuperView:(CGFloat)offset 173 | { 174 | [self centerAlignVerticalForView:[self superview] offset:offset]; 175 | } 176 | 177 | - (void)centerAlignHorizontalForView:(UIView *)view offset:(CGFloat)offset 178 | { 179 | [self setFrame:CGRectRound(CGRectMake((CGRectGetWidth([view frame]) / 2.0f) - (CGRectGetWidth([self frame]) / 2.0f) + offset, CGRectGetMinY([self frame]), CGRectGetWidth([self frame]), CGRectGetHeight([self frame])))]; 180 | } 181 | 182 | - (void)centerAlignVerticalForView:(UIView *)view offset:(CGFloat)offset 183 | { 184 | [self setFrame:CGRectRound(CGRectMake(CGRectGetMinX([self frame]), (CGRectGetHeight([view frame]) / 2.0f) - (CGRectGetHeight([self frame]) / 2.0f) + offset, CGRectGetWidth([self frame]), CGRectGetHeight([self frame])))]; 185 | } 186 | 187 | - (void)leftAlignForView:(UIView *)view 188 | { 189 | [self leftAlignForView:view offset:0]; 190 | } 191 | 192 | - (void)rightAlignForView:(UIView *)view 193 | { 194 | [self rightAlignForView:view offset:0]; 195 | } 196 | 197 | - (void)topAlignForView:(UIView *)view 198 | { 199 | [self topAlignForView:view offset:0]; 200 | } 201 | 202 | - (void)bottomAlignForView:(UIView *)view 203 | { 204 | [self bottomAlignForView:view offset:0]; 205 | } 206 | 207 | - (void)leftAlignForView:(UIView *)view offset:(CGFloat)offset 208 | { 209 | [self setFrame:CGRectRound(CGRectMake(CGRectGetMinX([view frame]) + offset, CGRectGetMinY([self frame]), CGRectGetWidth([self frame]), CGRectGetHeight([self frame])))]; 210 | } 211 | 212 | - (void)rightAlignForView:(UIView *)view offset:(CGFloat)offset 213 | { 214 | [self setFrame:CGRectRound(CGRectMake(CGRectGetMaxX([view frame]) - CGRectGetWidth([self frame]) - offset, CGRectGetMinY([self frame]), CGRectGetWidth([self frame]), CGRectGetHeight([self frame])))]; 215 | } 216 | 217 | - (void)topAlignForView:(UIView *)view offset:(CGFloat)offset 218 | { 219 | [self setFrame:CGRectRound(CGRectMake(CGRectGetMinX([self frame]), [view frame].origin.y + offset, CGRectGetWidth([self frame]), CGRectGetHeight([self frame])))]; 220 | } 221 | 222 | - (void)bottomAlignForView:(UIView *)view offset:(CGFloat)offset 223 | { 224 | [self setFrame:CGRectRound(CGRectMake(CGRectGetMinX([self frame]), CGRectGetMaxY([view frame]) - CGRectGetHeight([self frame]) - offset, CGRectGetWidth([self frame]), CGRectGetHeight([self frame])))]; 225 | } 226 | 227 | - (void)topAlignForSuperViewOffset:(CGFloat)offset 228 | { 229 | [self setFrameOriginY:offset]; 230 | } 231 | 232 | - (void)bottomAlignForSuperViewOffset:(CGFloat)offset 233 | { 234 | [self setFrameOriginY:[[self superview] frameSizeHeight] - [self frameSizeHeight] - offset]; 235 | } 236 | 237 | - (void)leftAlignForSuperViewOffset:(CGFloat)offset 238 | { 239 | [self setFrameOriginX:offset]; 240 | } 241 | 242 | - (void)rightAlignForSuperViewOffset:(CGFloat)offset 243 | { 244 | [self setFrameOriginX:[[self superview] frameSizeWidth] - [self frameSizeWidth] - offset]; 245 | } 246 | 247 | - (void)topAlignForSuperView 248 | { 249 | [self topAlignForSuperViewOffset:0]; 250 | } 251 | 252 | - (void)bottomAlignForSuperView 253 | { 254 | [self bottomAlignForSuperViewOffset:0]; 255 | } 256 | 257 | - (void)leftAlignForSuperView 258 | { 259 | [self leftAlignForSuperViewOffset:0]; 260 | } 261 | 262 | - (void)rightAlignForSuperView 263 | { 264 | [self rightAlignForSuperViewOffset:0]; 265 | } 266 | 267 | - (void)centerAlignForView:(UIView *)view 268 | { 269 | [self centerAlignHorizontalForView:view]; 270 | [self centerAlignVerticalForView:view]; 271 | } 272 | 273 | - (void)centerAlignForSuperview 274 | { 275 | [self centerAlignForView:[self superview]]; 276 | } 277 | 278 | #pragma mark - 279 | #pragma mark Convenience Getters 280 | 281 | 282 | - (CGFloat)frameMaxX 283 | { 284 | return CGRectGetMaxX(self.frame); 285 | } 286 | -(void)setFrameMaxX:(CGFloat)maxX { 287 | if (maxX > self.frameOriginX) { 288 | CGFloat width = maxX - self.frameOriginX; 289 | self.frameSizeWidth = width; 290 | } 291 | } 292 | - (CGFloat)frameMaxY 293 | { 294 | return CGRectGetMaxY(self.frame); 295 | } 296 | -(void)setFrameMaxY:(CGFloat)maxY { 297 | if (maxY > self.frameOriginY) { 298 | CGFloat height = maxY - self.frameOriginY; 299 | self.frameSizeHeight = height; 300 | } 301 | } 302 | 303 | - (CGPoint)frameOrigin 304 | { 305 | return [self frame].origin; 306 | } 307 | 308 | - (CGSize)frameSize 309 | { 310 | return [self frame].size; 311 | } 312 | 313 | - (CGFloat)frameOriginX 314 | { 315 | return CGRectGetMinX(self.frame); 316 | } 317 | 318 | - (CGFloat)frameOriginY 319 | { 320 | return [self frame].origin.y; 321 | } 322 | 323 | - (CGFloat)frameSizeWidth 324 | { 325 | return [self frame].size.width; 326 | } 327 | 328 | - (CGFloat)frameSizeHeight 329 | { 330 | return [self frame].size.height; 331 | } 332 | 333 | 334 | 335 | #pragma mark - 336 | #pragma mark Frame Adjustments 337 | 338 | - (void)setFrameOrigin:(CGPoint)origin 339 | { 340 | [self setFrameOriginY:origin.y]; 341 | [self setFrameOriginX:origin.x]; 342 | } 343 | 344 | - (void)setFrameSize:(CGSize)size 345 | { 346 | [self setFrame:CGRectMake(CGRectGetMinX([self frame]), CGRectGetMinY([self frame]), size.width, size.height)]; 347 | } 348 | 349 | - (void)setFrameOriginY:(CGFloat)y 350 | { 351 | [self setFrame:CGRectMake(CGRectGetMinX([self frame]), y, CGRectGetWidth([self frame]), CGRectGetHeight([self frame]))]; 352 | } 353 | 354 | - (void)setFrameOriginX:(CGFloat)x 355 | { 356 | [self setFrame:CGRectMake(x, CGRectGetMinY([self frame]), CGRectGetWidth([self frame]), CGRectGetHeight([self frame]))]; 357 | } 358 | 359 | - (void)setFrameSizeWidth:(CGFloat)width 360 | { 361 | [self setFrame:CGRectMake(CGRectGetMinX([self frame]), CGRectGetMinY([self frame]), width, CGRectGetHeight([self frame]))]; 362 | } 363 | 364 | - (void)setFrameSizeHeight:(CGFloat)height 365 | { 366 | [self setFrame:CGRectMake(CGRectGetMinX([self frame]), CGRectGetMinY([self frame]), CGRectGetWidth([self frame]), height)]; 367 | } 368 | 369 | 370 | 371 | -(void)setFrameCenter:(CGPoint)center { 372 | self.center = center; 373 | } 374 | 375 | -(CGPoint)frameCenter { 376 | return self.center ; 377 | } 378 | 379 | -(void)setFrameCenterX:(CGFloat)centerx { 380 | self.center = CGPointMake(centerx, self.center.y); 381 | } 382 | 383 | -(CGFloat)frameCenterX { 384 | return self.center.x ; 385 | } 386 | 387 | -(void)setFrameCenterY:(CGFloat)centery { 388 | self.center = CGPointMake(self.center.x,centery ); 389 | } 390 | 391 | -(CGFloat)frameCenterY { 392 | return self.center.y ; 393 | } 394 | 395 | #pragma mark - 396 | #pragma mark Positioning Relative to View 397 | 398 | - (void)setFrameOriginYBelowView:(UIView *)view 399 | { 400 | [self setFrameOriginYBelowView:view offset:0]; 401 | } 402 | 403 | - (void)setFrameOriginYAboveView:(UIView *)view 404 | { 405 | [self setFrameOriginYAboveView:view offset:0]; 406 | } 407 | 408 | - (void)setFrameOriginXRightOfView:(UIView *)view 409 | { 410 | [self setFrameOriginXRightOfView:view offset:0]; 411 | } 412 | 413 | - (void)setFrameOriginXLeftOfView:(UIView *)view 414 | { 415 | [self setFrameOriginXLeftOfView:view offset:0]; 416 | } 417 | 418 | - (void)setFrameOriginYBelowView:(UIView *)view offset:(CGFloat)offset 419 | { 420 | CGRect frame = [self frame]; 421 | CGRect viewFrame = [view frame]; 422 | 423 | [self setFrame:CGRectRound(CGRectMake(CGRectGetMinX(frame), CGRectGetMaxY(viewFrame) + offset, CGRectGetWidth(frame), CGRectGetHeight(frame)))]; 424 | } 425 | 426 | - (void)setFrameOriginYAboveView:(UIView *)view offset:(CGFloat)offset 427 | { 428 | CGRect frame = [self frame]; 429 | CGRect viewFrame = [view frame]; 430 | 431 | [self setFrame:CGRectRound(CGRectMake(CGRectGetMinX(frame), CGRectGetMinY(viewFrame) - CGRectGetHeight([self frame]) - offset, CGRectGetWidth(frame), CGRectGetHeight(frame)))]; 432 | } 433 | 434 | - (void)setFrameOriginXRightOfView:(UIView *)view offset:(CGFloat)offset 435 | { 436 | CGRect frame = [self frame]; 437 | CGRect viewFrame = [view frame]; 438 | 439 | [self setFrame:CGRectRound(CGRectMake(CGRectGetMaxX(viewFrame) + offset, CGRectGetMinY(frame), CGRectGetWidth(frame), CGRectGetHeight(frame)))]; 440 | } 441 | 442 | - (void)setFrameOriginXLeftOfView:(UIView *)view offset:(CGFloat)offset 443 | { 444 | CGRect frame = [self frame]; 445 | CGRect viewFrame = [view frame]; 446 | 447 | [self setFrame:CGRectRound(CGRectMake(CGRectGetMinX(viewFrame) - CGRectGetWidth(frame) - offset, CGRectGetMinY(frame), CGRectGetWidth(frame), CGRectGetHeight(frame)))]; 448 | } 449 | 450 | #pragma mark - 451 | #pragma mark Resizing 452 | 453 | - (void)setFrameSizeToImageSize 454 | { 455 | if ([self isKindOfClass:[UIButton class]]) 456 | { 457 | UIImage *image = [(UIButton *)self imageForState:UIControlStateNormal]; 458 | 459 | if (!image) 460 | { 461 | image = [(UIButton *)self backgroundImageForState:UIControlStateNormal]; 462 | } 463 | 464 | if (image) 465 | { 466 | [self setFrame:CGRectMake(CGRectGetMinX([self frame]), CGRectGetMinY([self frame]), [image size].width, [image size].height)]; 467 | } 468 | } 469 | else if ([self isKindOfClass:[UIImageView class]]) 470 | { 471 | UIImage *image = [(UIImageView *)self image]; 472 | if (image) 473 | { 474 | [self setFrame:CGRectMake(CGRectGetMinX([self frame]), CGRectGetMinY([self frame]), [image size].width, [image size].height)]; 475 | } 476 | } 477 | } 478 | 479 | #pragma mark - Corners and Masks 480 | 481 | - (void)roundCornersTopLeft:(CGFloat)topLeft topRight:(CGFloat)topRight bottomLeft:(CGFloat)bottomLeft bottomRight:(CGFloat)bottomRight 482 | { 483 | UIImage *mask = createRoundedCornerMask([self bounds], topLeft, topRight, bottomLeft, bottomRight); 484 | CALayer *layerMask = [CALayer layer]; 485 | [layerMask setFrame:[self bounds]]; 486 | [layerMask setContents:(id)[mask CGImage]]; 487 | [[self layer] setMask:layerMask]; 488 | } 489 | 490 | - (void)setVerticalFadeMaskWithTopOffset:(CGFloat)topOffset bottomOffset:(CGFloat)bottomOffset 491 | { 492 | CAGradientLayer *maskLayer = [CAGradientLayer layer]; 493 | 494 | UIColor *outerColor = [UIColor colorWithWhite:0.0f alpha:0.0f]; 495 | UIColor *innerColor = [UIColor colorWithWhite:0.0f alpha:1.0f]; 496 | [maskLayer setColors:@[ 497 | (id)[outerColor CGColor], 498 | (id)[innerColor CGColor], 499 | (id)[innerColor CGColor], 500 | (id)[outerColor CGColor] 501 | ]]; 502 | [maskLayer setLocations:@[ 503 | @(0.0f), 504 | @(topOffset), 505 | @(1.0f - bottomOffset), 506 | @(1.0f) 507 | ]]; 508 | [maskLayer setStartPoint:CGPointMake(0.5f, 1.0f)]; 509 | [maskLayer setEndPoint:CGPointMake(0.5f, 0.0f)]; 510 | [maskLayer setBounds:[self bounds]]; 511 | [maskLayer setAnchorPoint:CGPointZero]; 512 | [[self layer] setMask:maskLayer]; 513 | } 514 | 515 | - (void)setHorizontalFadeMaskWithLeftOffset:(CGFloat)leftOffset rightOffset:(CGFloat)rightOffset 516 | { 517 | CAGradientLayer *maskLayer = [CAGradientLayer layer]; 518 | 519 | UIColor *outerColor = [UIColor colorWithWhite:0.0f alpha:0.0f]; 520 | UIColor *innerColor = [UIColor colorWithWhite:0.0f alpha:1.0f]; 521 | [maskLayer setColors:@[ 522 | (id)[outerColor CGColor], 523 | (id)[innerColor CGColor], 524 | (id)[innerColor CGColor], 525 | (id)[outerColor CGColor] 526 | ]]; 527 | [maskLayer setLocations:@[ 528 | @(0.0f), 529 | @(leftOffset), 530 | @(1.0f - rightOffset), 531 | @(1.0f) 532 | ]]; 533 | [maskLayer setStartPoint:CGPointMake(0.0f, 0.5f)]; 534 | [maskLayer setEndPoint:CGPointMake(1.0f, 0.5f)]; 535 | [maskLayer setBounds:[self bounds]]; 536 | [maskLayer setAnchorPoint:CGPointZero]; 537 | [[self layer] setMask:maskLayer]; 538 | } 539 | 540 | static inline UIImage* createRoundedCornerMask(CGRect rect, CGFloat radius_tl, CGFloat radius_tr, CGFloat radius_bl, CGFloat radius_br) 541 | { 542 | CGContextRef context; 543 | CGColorSpaceRef colorSpace; 544 | 545 | colorSpace = CGColorSpaceCreateDeviceRGB(); 546 | 547 | float scaleFactor = [[UIScreen mainScreen] scale]; 548 | context = CGBitmapContextCreate( NULL, 549 | rect.size.width * scaleFactor, 550 | rect.size.height * scaleFactor, 551 | 8, 552 | rect.size.width * scaleFactor * 4, 553 | colorSpace, 554 | (CGBitmapInfo)kCGImageAlphaPremultipliedLast ); 555 | 556 | CGColorSpaceRelease(colorSpace); 557 | 558 | if (context == NULL) 559 | { 560 | return NULL; 561 | } 562 | 563 | CGContextScaleCTM(context, scaleFactor, scaleFactor); 564 | 565 | CGFloat minx = CGRectGetMinX( rect ), midx = CGRectGetMidX( rect ), maxx = CGRectGetMaxX( rect ); 566 | CGFloat miny = CGRectGetMinY( rect ), midy = CGRectGetMidY( rect ), maxy = CGRectGetMaxY( rect ); 567 | 568 | CGContextBeginPath( context ); 569 | CGContextSetGrayFillColor( context, 1.0, 0.0 ); 570 | CGContextAddRect( context, rect ); 571 | CGContextClosePath( context ); 572 | CGContextDrawPath( context, kCGPathFill ); 573 | 574 | CGContextSetGrayFillColor( context, 1.0, 1.0 ); 575 | CGContextBeginPath( context ); 576 | CGContextMoveToPoint( context, minx, midy ); 577 | CGContextAddArcToPoint( context, minx, miny, midx, miny, radius_bl ); 578 | CGContextAddArcToPoint( context, maxx, miny, maxx, midy, radius_br ); 579 | CGContextAddArcToPoint( context, maxx, maxy, midx, maxy, radius_tr ); 580 | CGContextAddArcToPoint( context, minx, maxy, minx, midy, radius_tl ); 581 | CGContextClosePath( context ); 582 | CGContextDrawPath( context, kCGPathFill ); 583 | 584 | CGImageRef bitmapContext = CGBitmapContextCreateImage( context ); 585 | CGContextRelease( context ); 586 | 587 | UIImage *mask = [UIImage imageWithCGImage:bitmapContext]; 588 | 589 | CGImageRelease(bitmapContext); 590 | 591 | return mask; 592 | } 593 | 594 | #pragma mark - Snapshotting 595 | 596 | - (UIImageView *)createSnapshot 597 | { 598 | UIGraphicsBeginImageContextWithOptions([self bounds].size, YES, 0); 599 | 600 | CGContextTranslateCTM(UIGraphicsGetCurrentContext(), -[self bounds].origin.x, -[self bounds].origin.y); 601 | 602 | [[self layer] renderInContext:UIGraphicsGetCurrentContext()]; 603 | UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); 604 | UIGraphicsEndImageContext(); 605 | 606 | UIImageView *snapshot = [[UIImageView alloc] initWithImage:image]; 607 | [snapshot setFrame:[self frame]]; 608 | 609 | return snapshot; 610 | } 611 | 612 | - (UIImage *)snapshotImage 613 | { 614 | UIGraphicsBeginImageContextWithOptions([self bounds].size, YES, 0); 615 | 616 | CGContextTranslateCTM(UIGraphicsGetCurrentContext(), -[self bounds].origin.x, -[self bounds].origin.y); 617 | 618 | [[self layer] renderInContext:UIGraphicsGetCurrentContext()]; 619 | UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); 620 | UIGraphicsEndImageContext(); 621 | 622 | return image; 623 | } 624 | 625 | - (UIImage *)snapshotImageAfterScreenUpdates:(BOOL)afterUpdates { 626 | if (![self respondsToSelector:@selector(drawViewHierarchyInRect:afterScreenUpdates:)]) { 627 | return [self snapshotImage]; 628 | } 629 | UIGraphicsBeginImageContextWithOptions(self.bounds.size, self.opaque, 0); 630 | [self drawViewHierarchyInRect:self.bounds afterScreenUpdates:afterUpdates]; 631 | UIImage *snap = UIGraphicsGetImageFromCurrentImageContext(); 632 | UIGraphicsEndImageContext(); 633 | return snap; 634 | } 635 | 636 | #pragma mark - Debugging 637 | 638 | - (void)showDebugFrame 639 | { 640 | [self showDebugFrame:NO]; 641 | } 642 | 643 | - (void)hideDebugFrame 644 | { 645 | [[self layer] setBorderColor:nil]; 646 | [[self layer] setBorderWidth:0.0f]; 647 | } 648 | 649 | - (void)showDebugFrame:(BOOL)showInRelease 650 | { 651 | [self performInRelease:showInRelease 652 | block:^{ 653 | 654 | [[self layer] setBorderColor:[[UIColor redColor] CGColor]]; 655 | [[self layer] setBorderWidth:1.0f]; 656 | 657 | }]; 658 | 659 | } 660 | 661 | - (void)logFrameChanges 662 | { 663 | [self performInDebug:^{ 664 | 665 | [self frameDidChange]; 666 | [self addObserver:self forKeyPath:UIVIEW_HELPERS_FRAME_KVO_KEY options:0 context:0]; 667 | 668 | }]; 669 | } 670 | 671 | - (void)frameDidChange 672 | { 673 | [self performInDebug:^{ 674 | 675 | NSLog(@"%@ <%@: %p; frame = %@>", nameOfVar(self), 676 | NSStringFromClass([self class]), 677 | self, 678 | NSStringFromCGRect([self frame])); 679 | 680 | }]; 681 | } 682 | 683 | #pragma mark - LayoutHelpers 684 | 685 | - (BOOL)isViewVisible 686 | { 687 | BOOL isViewHidden = [self isHidden] || [self alpha] == 0 || CGRectIsEmpty([self frame]); 688 | return !isViewHidden; 689 | } 690 | 691 | + (CGFloat)alignVertical:(VerticalLayoutType)type 692 | views:(NSArray*)views 693 | withSpacing:(CGFloat)spacing 694 | inView:(UIView*)view 695 | shrinkSpacingToFit:(BOOL)shrinkSpacingToFit 696 | { 697 | __block CGFloat height = 0; 698 | __block int numVisibleViews = 0; 699 | 700 | if (type == VerticalLayoutTypeCenter || (shrinkSpacingToFit && spacing > 0)) 701 | { 702 | [views enumerateObjectsUsingBlock:^(UIView* obj, NSUInteger idx, BOOL *stop) { 703 | if ([obj isViewVisible]) { 704 | height += [obj frameSizeHeight] + ((idx > 0) ? spacing : 0); 705 | numVisibleViews += 1; 706 | } 707 | }]; 708 | 709 | if (numVisibleViews == 0) 710 | { 711 | return 0; 712 | } 713 | 714 | if (shrinkSpacingToFit && height > [view frameSizeHeight]) 715 | { 716 | CGFloat d = (height - [view frameSizeHeight]) / (CGFloat)(numVisibleViews-1); 717 | d = MIN(spacing, d); 718 | spacing -= d; 719 | height -= (d * (CGFloat)(numVisibleViews-1)); 720 | } 721 | } 722 | 723 | __block CGFloat y = 0; 724 | if (type == VerticalLayoutTypeCenter) 725 | { 726 | y = ([view frameSizeHeight] - height) * 0.5; 727 | } else if (type == VerticalLayoutTypeBottom) 728 | { 729 | y = [view frameSizeHeight]; 730 | } 731 | 732 | CGFloat startY = y; 733 | [views enumerateObjectsWithOptions:(type == VerticalLayoutTypeBottom ? NSEnumerationReverse : 0) 734 | usingBlock:^(id obj, NSUInteger idx, BOOL *stop) { 735 | 736 | if (type == VerticalLayoutTypeBottom) 737 | { 738 | CGFloat height = [obj frameSizeHeight]; 739 | [obj setFrameOriginY: y-height]; 740 | if ([obj isViewVisible]) 741 | { 742 | y -= height+spacing; 743 | } 744 | } 745 | else 746 | { 747 | [obj setFrameOriginY:y]; 748 | if ([obj isViewVisible]) 749 | { 750 | y += [obj frameSizeHeight]+spacing; 751 | } 752 | } 753 | }]; 754 | 755 | CGFloat ret = ABS(y - startY)-spacing; 756 | return ret; 757 | } 758 | 759 | + (CGFloat)alignVertical:(VerticalLayoutType)type 760 | views:(NSArray*)views 761 | withSpacingArray:(NSArray*)spacing 762 | inView:(UIView*)view 763 | shrinkSpacingToFit:(BOOL)shrinkSpacingToFit 764 | { 765 | __block CGFloat height = 0; 766 | __block int numVisibleViews = 0; 767 | CGFloat spacingModifier = 0; 768 | 769 | if (type == VerticalLayoutTypeCenter || (shrinkSpacingToFit && spacing > 0)) 770 | { 771 | __block CGFloat totalSpacing = 0; 772 | [views enumerateObjectsUsingBlock:^(UIView* obj, NSUInteger idx, BOOL *stop) { 773 | if ([obj isViewVisible]) 774 | { 775 | CGFloat space = ((idx > 0 && idx-1 < spacing.count) ? [spacing[idx-1] floatValue] : 0); 776 | totalSpacing += space; 777 | height += [obj frameSizeHeight] + space; 778 | numVisibleViews += 1; 779 | } 780 | }]; 781 | 782 | if (numVisibleViews == 0) 783 | { 784 | return 0; 785 | } 786 | 787 | if (shrinkSpacingToFit && height > [view frameSizeHeight]) 788 | { 789 | CGFloat d = MIN(totalSpacing, (height - [view frameSizeHeight])); 790 | spacingModifier = (d / totalSpacing); 791 | } 792 | } 793 | 794 | __block CGFloat y = 0; 795 | if (type == VerticalLayoutTypeCenter) 796 | { 797 | y = ([view frameSizeHeight] - height) * 0.5; 798 | } 799 | else if (type == VerticalLayoutTypeBottom) 800 | { 801 | y = [view frameSizeHeight]; 802 | } 803 | 804 | CGFloat startY = y; 805 | [views enumerateObjectsWithOptions:(type == VerticalLayoutTypeBottom ? NSEnumerationReverse : 0) 806 | usingBlock:^(id obj, NSUInteger idx, BOOL *stop) { 807 | 808 | CGFloat space = ((idx < spacing.count) ? [spacing[idx] floatValue] : 0); 809 | space -= (space * spacingModifier); 810 | 811 | if (type == VerticalLayoutTypeBottom) 812 | { 813 | CGFloat height = [obj frameSizeHeight]; 814 | [obj setFrameOriginY: y-height]; 815 | if ([obj isViewVisible]) 816 | { 817 | y -= height+space; 818 | } 819 | } 820 | else 821 | { 822 | [obj setFrameOriginY:y]; 823 | if ([obj isViewVisible]) 824 | { 825 | y += [obj frameSizeHeight]+space; 826 | } 827 | } 828 | }]; 829 | 830 | CGFloat ret = ABS(y - startY); 831 | return ret; 832 | } 833 | 834 | #pragma mark - Subviews 835 | 836 | + (UIView *)firstResponder 837 | { 838 | UIView *view = [[UIApplication sharedApplication] keyWindow]; 839 | return [view firstResponderInSubviews]; 840 | } 841 | 842 | - (UIView *)firstResponderInSubviews 843 | { 844 | UIView *responder; 845 | 846 | for (UIView *subview in [self subviews]) 847 | { 848 | if ([subview isFirstResponder]) 849 | { 850 | responder = subview; 851 | } 852 | else 853 | { 854 | responder = [subview firstResponderInSubviews]; 855 | } 856 | 857 | if (responder) 858 | { 859 | break; 860 | } 861 | } 862 | 863 | return responder; 864 | } 865 | 866 | - (NSArray *)subviewsOfClass:(Class)aClass recursive:(BOOL)recursive 867 | { 868 | NSMutableArray *subviews = [@[] mutableCopy]; 869 | 870 | for (UIView *subview in [self subviews]) 871 | { 872 | if ([subview isKindOfClass:aClass]) 873 | { 874 | [subviews addObject:subview]; 875 | } 876 | if (recursive) 877 | { 878 | [subviews addObjectsFromArray:[subview subviewsOfClass:aClass recursive:YES]]; 879 | } 880 | } 881 | 882 | return subviews; 883 | } 884 | 885 | #pragma mark - KVO 886 | 887 | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context 888 | { 889 | if ([keyPath isEqualToString:UIVIEW_HELPERS_FRAME_KVO_KEY]) 890 | { 891 | [self frameDidChange]; 892 | } 893 | } 894 | 895 | #pragma mark - Helpers 896 | 897 | - (void)performInDebug:(void (^)(void))block 898 | { 899 | [self performInRelease:NO block:block]; 900 | } 901 | 902 | - (void)performInRelease:(BOOL)release block:(void (^)(void))block 903 | { 904 | if (block) 905 | { 906 | #ifdef DEBUG 907 | block(); 908 | #else 909 | if (release) 910 | { 911 | block(); 912 | } 913 | #endif 914 | } 915 | } 916 | 917 | 918 | 919 | - (UITableView *)fdd_superTableView 920 | { 921 | if ([self isKindOfClass:[UITableView class]]) { 922 | return (UITableView *)self; 923 | } 924 | if (self.superview) { 925 | UITableView * tableView= [self.superview fdd_superTableView]; 926 | if (tableView != nil) { 927 | return tableView; 928 | } 929 | } 930 | return nil; 931 | } 932 | 933 | 934 | 935 | - (UITableViewCell*)fdd_superTableCell 936 | { 937 | if ([self isKindOfClass:[UITableViewCell class]]) { 938 | return (UITableViewCell *)self; 939 | } 940 | if (self.superview) { 941 | UITableViewCell * tableViewCell = [self.superview fdd_superTableCell]; 942 | if (tableViewCell != nil) { 943 | return tableViewCell; 944 | } 945 | } 946 | return nil; 947 | } 948 | 949 | 950 | - (UIView *)fdd_findFirstResponder 951 | { 952 | if (self.isFirstResponder) { 953 | return self; 954 | } 955 | for (UIView *subView in self.subviews) { 956 | UIView *firstResponder = [subView fdd_findFirstResponder]; 957 | if (firstResponder != nil) { 958 | return firstResponder; 959 | } 960 | } 961 | return nil; 962 | } 963 | 964 | - (UIViewController *)fdd_viewController { 965 | for (UIView *view = self; view; view = view.superview) { 966 | UIResponder *nextResponder = [view nextResponder]; 967 | if ([nextResponder isKindOfClass:[UIViewController class]]) { 968 | return (UIViewController *)nextResponder; 969 | } 970 | } 971 | return nil; 972 | } 973 | @end 974 | --------------------------------------------------------------------------------