Skip to content

Commit

Permalink
Add example simulating the airbnb header view
Browse files Browse the repository at this point in the history
  • Loading branch information
gskbyte committed Mar 2, 2016
1 parent fe44cda commit 56e37b8
Show file tree
Hide file tree
Showing 13 changed files with 335 additions and 66 deletions.
13 changes: 12 additions & 1 deletion Example/Example.xcodeproj/project.pbxproj
Expand Up @@ -24,6 +24,7 @@
C0116C2A1C81A3D9003EDACA /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C0116C291C81A3D9003EDACA /* LaunchScreen.storyboard */; };
C0116C2D1C81A4E7003EDACA /* GSKTestStretchyHeaderView.m in Sources */ = {isa = PBXBuildFile; fileRef = C0116C2C1C81A4E7003EDACA /* GSKTestStretchyHeaderView.m */; };
C0116C371C81A56F003EDACA /* GSKGradientView.m in Sources */ = {isa = PBXBuildFile; fileRef = C0116C361C81A56F003EDACA /* GSKGradientView.m */; };
C0276CCC1C86F46700AFDA42 /* GSKAirbnbStretchyHeaderView.m in Sources */ = {isa = PBXBuildFile; fileRef = C0276CCB1C86F46700AFDA42 /* GSKAirbnbStretchyHeaderView.m */; };
C03FC4E21C81B1E30015FAAA /* GSKExampleData.m in Sources */ = {isa = PBXBuildFile; fileRef = C03FC4DD1C81B1E30015FAAA /* GSKExampleData.m */; };
C03FC4E31C81B1E30015FAAA /* GSKExampleDataCell.m in Sources */ = {isa = PBXBuildFile; fileRef = C03FC4DF1C81B1E30015FAAA /* GSKExampleDataCell.m */; };
C03FC4E41C81B1E30015FAAA /* GSKExampleListViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C03FC4E11C81B1E30015FAAA /* GSKExampleListViewController.m */; };
Expand All @@ -39,6 +40,7 @@
C03FC5051C8348150015FAAA /* GSKTabsStretchyHeaderView.m in Sources */ = {isa = PBXBuildFile; fileRef = C03FC4FF1C8348150015FAAA /* GSKTabsStretchyHeaderView.m */; };
C03FC5061C8348150015FAAA /* GSKTabsStretchyHeaderView.xib in Resources */ = {isa = PBXBuildFile; fileRef = C03FC5001C8348150015FAAA /* GSKTabsStretchyHeaderView.xib */; };
C03FC5091C8349440015FAAA /* GSKExampleDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = C03FC5081C8349440015FAAA /* GSKExampleDataSource.m */; };
C066207A1C87311000F1AEF5 /* GSKAirbnbExampleViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C06620791C87311000F1AEF5 /* GSKAirbnbExampleViewController.m */; };
C086E0031C846309002A54C1 /* GSKExampleNavigationBarViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C086E0021C846309002A54C1 /* GSKExampleNavigationBarViewController.m */; };
C086E00A1C846C5E002A54C1 /* GSKExampleBaseTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C086E0091C846C5E002A54C1 /* GSKExampleBaseTableViewController.m */; };
/* End PBXBuildFile section */
Expand Down Expand Up @@ -86,6 +88,8 @@
C0116C2C1C81A4E7003EDACA /* GSKTestStretchyHeaderView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GSKTestStretchyHeaderView.m; sourceTree = "<group>"; };
C0116C351C81A56F003EDACA /* GSKGradientView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GSKGradientView.h; sourceTree = "<group>"; };
C0116C361C81A56F003EDACA /* GSKGradientView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GSKGradientView.m; sourceTree = "<group>"; };
C0276CCA1C86F46700AFDA42 /* GSKAirbnbStretchyHeaderView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GSKAirbnbStretchyHeaderView.h; sourceTree = "<group>"; };
C0276CCB1C86F46700AFDA42 /* GSKAirbnbStretchyHeaderView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GSKAirbnbStretchyHeaderView.m; sourceTree = "<group>"; };
C03FC4DC1C81B1E30015FAAA /* GSKExampleData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GSKExampleData.h; sourceTree = "<group>"; };
C03FC4DD1C81B1E30015FAAA /* GSKExampleData.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GSKExampleData.m; sourceTree = "<group>"; };
C03FC4DE1C81B1E30015FAAA /* GSKExampleDataCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GSKExampleDataCell.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -116,6 +120,8 @@
C03FC5001C8348150015FAAA /* GSKTabsStretchyHeaderView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = GSKTabsStretchyHeaderView.xib; sourceTree = "<group>"; };
C03FC5071C8349440015FAAA /* GSKExampleDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GSKExampleDataSource.h; sourceTree = "<group>"; };
C03FC5081C8349440015FAAA /* GSKExampleDataSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GSKExampleDataSource.m; sourceTree = "<group>"; };
C06620781C87311000F1AEF5 /* GSKAirbnbExampleViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GSKAirbnbExampleViewController.h; sourceTree = "<group>"; };
C06620791C87311000F1AEF5 /* GSKAirbnbExampleViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GSKAirbnbExampleViewController.m; sourceTree = "<group>"; };
C086E0011C846309002A54C1 /* GSKExampleNavigationBarViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GSKExampleNavigationBarViewController.h; sourceTree = "<group>"; };
C086E0021C846309002A54C1 /* GSKExampleNavigationBarViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GSKExampleNavigationBarViewController.m; sourceTree = "<group>"; };
C086E0081C846C5E002A54C1 /* GSKExampleBaseTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GSKExampleBaseTableViewController.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -266,6 +272,8 @@
C03FC4FB1C8348150015FAAA /* GSKExampleTabsViewController.m */,
C086E0011C846309002A54C1 /* GSKExampleNavigationBarViewController.h */,
C086E0021C846309002A54C1 /* GSKExampleNavigationBarViewController.m */,
C06620781C87311000F1AEF5 /* GSKAirbnbExampleViewController.h */,
C06620791C87311000F1AEF5 /* GSKAirbnbExampleViewController.m */,
);
name = "View Controllers";
sourceTree = "<group>";
Expand All @@ -284,6 +292,8 @@
C03FC4FE1C8348150015FAAA /* GSKTabsStretchyHeaderView.h */,
C03FC4FF1C8348150015FAAA /* GSKTabsStretchyHeaderView.m */,
C03FC5001C8348150015FAAA /* GSKTabsStretchyHeaderView.xib */,
C0276CCA1C86F46700AFDA42 /* GSKAirbnbStretchyHeaderView.h */,
C0276CCB1C86F46700AFDA42 /* GSKAirbnbStretchyHeaderView.m */,
);
name = "Header Views";
sourceTree = "<group>";
Expand Down Expand Up @@ -544,9 +554,11 @@
files = (
C03FC4E71C81B2FE0015FAAA /* UINavigationController+Transparency.m in Sources */,
C03FC4F41C820C9C0015FAAA /* GSKNibStretchyHeaderView.m in Sources */,
C066207A1C87311000F1AEF5 /* GSKAirbnbExampleViewController.m in Sources */,
C03FC5031C8348150015FAAA /* GSKExampleTabsViewController.m in Sources */,
6003F59E195388D20070C39A /* GSKAppDelegate.m in Sources */,
C0116C2D1C81A4E7003EDACA /* GSKTestStretchyHeaderView.m in Sources */,
C0276CCC1C86F46700AFDA42 /* GSKAirbnbStretchyHeaderView.m in Sources */,
C03FC5041C8348150015FAAA /* GSKTableViewCell.m in Sources */,
C03FC5051C8348150015FAAA /* GSKTabsStretchyHeaderView.m in Sources */,
C03FC4F01C81EC140015FAAA /* GSKSpotyLikeHeaderView.m in Sources */,
Expand Down Expand Up @@ -647,7 +659,6 @@
baseConfigurationReference = 00182E742192282BCFF3E012 /* Pods-Example.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = "GSKStretchyHeaderView/GSKStretchyHeaderView-Prefix.pch";
INFOPLIST_FILE = "GSKStretchyHeaderView/GSKStretchyHeaderView-Info.plist";
Expand Down
@@ -0,0 +1,5 @@
#import "GSKExampleBaseTableViewController.h"

@interface GSKAirbnbExampleViewController : GSKExampleBaseTableViewController

@end
57 changes: 57 additions & 0 deletions Example/GSKStretchyHeaderView/GSKAirbnbExampleViewController.m
@@ -0,0 +1,57 @@
#import "GSKAirbnbExampleViewController.h"
#import "GSKAirbnbStretchyHeaderView.h"

@interface GSKAirbnbExampleViewController () <GSKAirbnbStretchyHeaderViewDelegate>
@end

@implementation GSKAirbnbExampleViewController

- (void)viewDidLoad {
[super viewDidLoad];

self.refreshControl = [[UIRefreshControl alloc] init];
[self.refreshControl addTarget:self action:@selector(beginRefreshing:) forControlEvents:UIControlEventValueChanged];

[self.tableView addSubview:self.refreshControl];
self.dataSource.cellColors = @[[UIColor whiteColor], [UIColor lightGrayColor]];
}

- (GSKStretchyHeaderView *)loadStretchyHeaderView {
CGRect frame = CGRectMake(0, 0, self.tableView.frame.size.width, 250);
GSKAirbnbStretchyHeaderView *headerView = [[GSKAirbnbStretchyHeaderView alloc] initWithFrame:frame];
headerView.maximumContentHeight = 300;
headerView.minimumContentHeight = 84;
headerView.contentBounces = NO;
headerView.delegate = self;
return headerView;
}

- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self.navigationController setNavigationBarHidden:YES animated:NO];
}

- (void)airbnbStretchyHeaderView:(GSKAirbnbStretchyHeaderView *)headerView
didTapBackButton:(id)sender {
[self.navigationController popViewControllerAnimated:YES];
}

- (void)airbnbStretchyHeaderView:(GSKAirbnbStretchyHeaderView *)headerView
didTapSearchButton:(id)sender {
CGPoint contentOffset = self.tableView.contentOffset;
if (contentOffset.y < -self.stretchyHeaderView.minimumContentHeight) {
contentOffset.y = -self.stretchyHeaderView.minimumContentHeight;
[self.tableView setContentOffset:contentOffset animated:YES];
}

// show a child search view controller

}

- (void)beginRefreshing:(id)sender {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self.refreshControl endRefreshing];
});
}

@end
18 changes: 18 additions & 0 deletions Example/GSKStretchyHeaderView/GSKAirbnbStretchyHeaderView.h
@@ -0,0 +1,18 @@
#import <GSKStretchyHeaderView/GSKStretchyHeaderView.h>

@protocol GSKAirbnbStretchyHeaderViewDelegate;

@interface GSKAirbnbStretchyHeaderView : GSKStretchyHeaderView

@property (nonatomic, weak) id<GSKAirbnbStretchyHeaderViewDelegate> delegate;

@end

@protocol GSKAirbnbStretchyHeaderViewDelegate <NSObject>

- (void)airbnbStretchyHeaderView:(GSKAirbnbStretchyHeaderView *)headerView
didTapBackButton:(id)sender;
- (void)airbnbStretchyHeaderView:(GSKAirbnbStretchyHeaderView *)headerView
didTapSearchButton:(id)sender;

@end
207 changes: 207 additions & 0 deletions Example/GSKStretchyHeaderView/GSKAirbnbStretchyHeaderView.m
@@ -0,0 +1,207 @@
#import "GSKAirbnbStretchyHeaderView.h"
#import <GSKStretchyHeaderView/UIView+GSKLayoutHelper.h>
#import <GSKStretchyHeaderView/GSKGeometry.h>

static const CGFloat kButtonEdge = 64;
static const CGFloat kAnimationDuration = 0.2;

typedef NS_ENUM(NSUInteger, GSKAirbnbSearchViewMode) {
GSKAirbnbSearchViewModeButton,
GSKAirbnbSearchViewModeTextField
};

@interface GSKAirbnbSearchView : UIView

@property (nonatomic, readonly) GSKAirbnbSearchViewMode mode;
@property (nonatomic) CGFloat searchFieldLeft;

@property (nonatomic) UIControl *contentControl;
@property (nonatomic) UIImageView *imageView;
@property (nonatomic) UILabel *placeholderLabel;

- (void)setMode:(GSKAirbnbSearchViewMode)mode animated:(BOOL)animated;

@end

@interface GSKAirbnbStretchyHeaderView ()

@property (nonatomic) UIView *statusBarBackground;
@property (nonatomic) UIButton *backButton;
@property (nonatomic) UIImageView *backgroundImageView;
@property (nonatomic) GSKAirbnbSearchView *searchView;
@property (nonatomic) UIView *bottomLine;

@end

@implementation GSKAirbnbStretchyHeaderView

- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
self.contentView.backgroundColor = [UIColor whiteColor];
[self setupSubviews];
[self setSearchViewMode:GSKAirbnbSearchViewModeButton animated:NO];
}
return self;
}

- (void)setupSubviews {
self.backgroundImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"landscape"]];
self.backgroundImageView.contentMode = UIViewContentModeScaleAspectFill;
self.backgroundImageView.clipsToBounds = YES;
[self.contentView addSubview:self.backgroundImageView];

self.searchView = [[GSKAirbnbSearchView alloc] initWithFrame:CGRectMake(0, 0, self.width, kButtonEdge)];
[self.searchView.contentControl addTarget:self action:@selector(didTapSearchButton:) forControlEvents:UIControlEventTouchUpInside];
[self.contentView addSubview:self.searchView];

self.backButton = [[UIButton alloc] init];
[self.backButton setTitle:@"<" forState:UIControlStateNormal];
self.backButton.titleLabel.font = [UIFont boldSystemFontOfSize:24];
[self.backButton addTarget:self action:@selector(didTapBackButton:) forControlEvents:UIControlEventTouchUpInside];
[self.contentView addSubview:self.backButton];

self.statusBarBackground = [[UIView alloc] init];
self.statusBarBackground.backgroundColor = [UIColor whiteColor];
[self.contentView addSubview:self.statusBarBackground];

self.bottomLine = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.width, 1)];
self.bottomLine.backgroundColor = [UIColor colorWithWhite:0.4 alpha:1];
[self.contentView addSubview:self.bottomLine];
}

- (void)layoutSubviews {
[super layoutSubviews];
self.statusBarBackground.frame = CGRectMake(0, 0, self.contentView.width, 20);

self.backButton.top = 20;
self.backButton.size = CGSizeMake(self.contentView.width * 0.14, kButtonEdge);

self.searchView.searchFieldLeft = self.backButton.right;
self.searchView.size = CGSizeMake(self.contentView.width, kButtonEdge);
self.searchView.bottom = self.contentView.height;

self.backgroundImageView.frame = CGRectMake(0, 0, self.contentView.width, self.contentView.height - self.searchView.height / 2);

self.bottomLine.width = self.contentView.width;
self.bottomLine.bottom = self.contentView.bottom;
}

- (void)didChangeStretchFactor:(CGFloat)stretchFactor {
[super didChangeStretchFactor:stretchFactor];
GSKAirbnbSearchViewMode mode = self.contentView.height > self.minimumContentHeight ? GSKAirbnbSearchViewModeButton : GSKAirbnbSearchViewModeTextField;
if (mode != self.searchView.mode) {
[self setSearchViewMode:mode animated:YES];
}
}

- (void)setSearchViewMode:(GSKAirbnbSearchViewMode)mode animated:(BOOL)animated {
[self.searchView setMode:mode animated:animated];
[UIView animateWithDuration:animated ? kAnimationDuration : 0 animations:^{
switch (mode) {
case GSKAirbnbSearchViewModeButton:
self.backgroundImageView.alpha = 1;
[self.backButton setTitleColor:[UIColor whiteColor]
forState:UIControlStateNormal];
self.bottomLine.alpha = 0;
break;

case GSKAirbnbSearchViewModeTextField:
self.backgroundImageView.alpha = 0;
[self.backButton setTitleColor:[UIColor darkGrayColor]
forState:UIControlStateNormal];
self.bottomLine.alpha = 1;
break;
}
}];
}

- (void)didTapBackButton:(id)sender {
[self.delegate airbnbStretchyHeaderView:self didTapBackButton:sender];
}

- (void)didTapSearchButton:(id)sender {
[self.delegate airbnbStretchyHeaderView:self didTapSearchButton:sender];
}

@end

#pragma mark - Search button

@implementation GSKAirbnbSearchView

- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self setupSubviews];
}
return self;
}

- (void)setupSubviews {
self.contentControl = [[UIControl alloc] initWithFrame:self.bounds];
self.contentControl.clipsToBounds = YES;
[self addSubview:self.contentControl];

UIImage *magnifyingGlass = [[UIImage imageNamed:@"magnifying_glass"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
self.imageView = [[UIImageView alloc] initWithImage:magnifyingGlass];
self.imageView.contentMode = UIViewContentModeScaleAspectFit;
[self.contentControl addSubview:self.imageView];

self.placeholderLabel = [[UILabel alloc] init];
self.placeholderLabel.textColor = [UIColor lightGrayColor];
self.placeholderLabel.font = [UIFont systemFontOfSize:16];
self.placeholderLabel.text = @"Where to go?";
[self.contentControl addSubview:self.placeholderLabel];
}

- (void)setMode:(GSKAirbnbSearchViewMode)mode animated:(BOOL)animated {
_mode = mode;

NSTimeInterval animationDuration = animated ? kAnimationDuration : 0;
UIColor *redColor = [UIColor colorWithRed:0.8 green:0.1 blue:0.1 alpha:1];

BOOL buttonMode = (mode == GSKAirbnbSearchViewModeButton);
[UIView animateWithDuration:animationDuration animations:^{
self.contentControl.backgroundColor = buttonMode ? redColor : [UIColor whiteColor];
self.imageView.tintColor = buttonMode ? [UIColor whiteColor] : redColor;
self.placeholderLabel.alpha = buttonMode ? 0 : 1;
[self layoutSubviews];
}];

CGFloat contentControlCornerRadius = buttonMode ? self.contentControl.height / 2 : 0;

CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"cornerRadius"];
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
animation.fromValue = @(self.contentControl.layer.cornerRadius);
animation.toValue = @(contentControlCornerRadius);
animation.duration = animationDuration;
self.contentControl.layer.cornerRadius = contentControlCornerRadius;
[self.contentControl.layer addAnimation:animation forKey:@"cornerRadius"];
}

- (void)layoutSubviews {
[super layoutSubviews];

switch (self.mode) {
case GSKAirbnbSearchViewModeButton: {
self.contentControl.frame = CGRectMake(24, 0, self.height, self.height);
CGFloat imageWidth = self.contentControl.width * 0.7;
self.imageView.frame = CGRectMake((self.contentControl.width - imageWidth) / 2, 0,
imageWidth, self.contentControl.height);

break;
}
case GSKAirbnbSearchViewModeTextField: {
self.contentControl.frame = CGRectMake(self.searchFieldLeft, 0, self.width - self.searchFieldLeft, self.height);
CGFloat imageWidth = self.contentControl.height * 0.4;
self.imageView.frame = CGRectMake(0, 0, imageWidth, self.contentControl.height);
break;
}
}

CGFloat textLeft = self.imageView.right + 8;
self.placeholderLabel.frame = CGRectMake(textLeft, 0, MAX(self.contentControl.width - textLeft, 0), self.contentControl.height);
}

@end
Expand Up @@ -51,6 +51,7 @@ - (void)viewWillAppear:(BOOL)animated {
if (!self.data.navigationBarVisible) {
[self.navigationController gsk_setNavigationBarTransparent:YES animated:NO];
self.navigationController.navigationBar.barStyle = UIBarStyleBlack;
self.navigationController.navigationBar.tintColor = [UIColor whiteColor];
}
}

Expand Down
1 change: 1 addition & 0 deletions Example/GSKStretchyHeaderView/GSKExampleDataSource.h
Expand Up @@ -4,6 +4,7 @@
UICollectionViewDataSource, UICollectionViewDelegateFlowLayout>

@property (nonatomic, readonly) NSUInteger numberOfRows;
@property (nonatomic) NSArray<UIColor *> *cellColors;

- (instancetype)initWithNumberOfRows:(NSUInteger)numberOfRows;
- (void)registerForTableView:(UITableView *)tableView;
Expand Down

0 comments on commit 56e37b8

Please sign in to comment.