├── 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 | 
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 |
--------------------------------------------------------------------------------