Skip to content

Commit 3b9c6f3

Browse files
author
Jeff Verkoeyen
committed
[FlexibleHeader] Add the component with a simple example.
Reviewers: #material_components_ios_owners, junius, randallli Reviewed By: #material_components_ios_owners, junius, randallli Projects: #material_components_ios Differential Revision: http://codereview.cc/D120
1 parent cea53f8 commit 3b9c6f3

13 files changed

+2301
-1
lines changed

catalog/Podfile.lock

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
PODS:
22
- material-components-ios (0.1.0):
3+
- material-components-ios/FlexibleHeader (= 0.1.0)
34
- material-components-ios/Ink (= 0.1.0)
45
- material-components-ios/PageControl (= 0.1.0)
56
- material-components-ios/private (= 0.1.0)
@@ -11,6 +12,7 @@ PODS:
1112
- material-components-ios/Switch (= 0.1.0)
1213
- material-components-ios/Typography (= 0.1.0)
1314
- material-components-ios-catalog (0.1.0)
15+
- material-components-ios/FlexibleHeader (0.1.0)
1416
- material-components-ios/Ink (0.1.0)
1517
- material-components-ios/PageControl (0.1.0)
1618
- material-components-ios/private (0.1.0):
@@ -42,7 +44,7 @@ EXTERNAL SOURCES:
4244
:path: ../
4345

4446
SPEC CHECKSUMS:
45-
material-components-ios: b1e1bc101fe89ed301cdfeb479597d90b1fcf911
47+
material-components-ios: 463b80af2a7388584df19727b09f00f9ec43e4b1
4648
material-components-ios-catalog: 9e0e0b97321c38c0335293b8355d838071614f78
4749

4850
COCOAPODS: 0.39.0

components/FlexibleHeader/README.md

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
# Flexible header
2+
3+
The flexible header component is a container view whose height and vertical offset react to
4+
UIScrollViewDelegate events.
5+
6+
## Installation with CocoaPods
7+
8+
To add the Flexible Header to your Xcode project using CocoaPods, add the following to your
9+
`Podfile`:
10+
11+
pod 'material-components-ios-catalog/FlexibleHeader'
12+
13+
Then, run the following command:
14+
15+
$ pod install
16+
17+
## Design considerations
18+
19+
Most view controllers own their own header in a material app. These headers are flexible, provide
20+
navigation information and actions, and often display high quality photography that complements the
21+
underlying content. Flexible Header was designed with the these expectations in mind.
22+
23+
This deviates from the typical UIKit convention of having a UINavigationController that owns and
24+
manages a single UINavigationBar. The benefits of this deviation are:
25+
26+
- It is easier to build custom transitions from one view controller.
27+
- Questions such as "what happens whe the header is 50pt tall and we push a view controller wanting
28+
a 20pt tall header?" are no longer part of the discussion. With UINavigationBar — or any shared
29+
navigation bar for that matter — resolving this leads to difficult architectural trade offs.
30+
31+
### What's inside
32+
33+
TODO(featherless): Discuss the three classes in this component, their relationship to one another,
34+
and lead from this to the "Integration" section.
35+
36+
## Integration
37+
38+
TODO(featherless): Go over this section with an editing comb.
39+
40+
TODO(featherless): Discuss injection. Compare this to UITableViewController and how it federates
41+
access to UITableView.
42+
43+
@interface MyViewController () <MDCFlexibleHeaderParentViewController>
44+
45+
This protocol defines a flexible header view property which you will need to synthesize.
46+
47+
@implementation MyViewController
48+
49+
@synthesize headerViewController;
50+
51+
In order to populate the property, call the `addToParent:` method.
52+
53+
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
54+
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
55+
if (self) {
56+
[MDCFlexibleHeaderViewController addToParent:self];
57+
...
58+
59+
Within your viewDidLoad you can now create and initialize any subviews that you'd like to add to
60+
your header view.
61+
62+
- (void)viewDidLoad {
63+
[super viewDidLoad];
64+
...
65+
66+
// Create custom views here.
67+
UIView *myCustomView = [UIView new];
68+
myCustomView.frame = self.headerViewController.headerView.bounds;
69+
myCustomView.autoresizingMask = (UIViewAutoresizingFlexibleWidth
70+
| UIViewAutoresizingFlexibleHeight);
71+
[self.headerViewController.headerView addSubview:myCustomView];
72+
73+
[self.headerViewController addFlexibleHeaderViewToParentViewControllerView];
74+
}
75+
76+
Note that any views added to the flexible header view should set their autoresizing masks to
77+
flexible width and height so that they expand/contract along with the header view.
78+
79+
TODO(featherless): Include "manual" example of using the standard UIKit APIs to add the
80+
view/controller.
81+
82+
### A note on subclasses
83+
84+
A subclass of your view controller may add additional views in their viewDidLoad, potentially
85+
resulting in the header being covered by the new views. It is the responsibility of the subclass to
86+
take the z-index into account:
87+
88+
[self.view insertSubview:myCustomView belowSubview:self.headerViewController.headerView];
89+
90+
### Usage with UINavigationController**
91+
92+
You may use an instance of UINavigationController to push and pop view controllers that are managing
93+
their own header view controller. UINavigationController does have its own navigation bar, so be
94+
sure to set `navigationBarHidden` to YES either all the time (if all of your view controllers have
95+
headers, or on the `viewWillAppear:` method).
96+
97+
Do **not** forget to do this if you support app state restoration, or your app will launch with
98+
double navigation bars.
99+
100+
## Tracking a scroll view
101+
102+
In most situations you will want the header to track a UIScrollView's scrolling behavior. This
103+
allows the header to expand, collapse, and shift off-screen.
104+
105+
To track a scroll view please follow these steps:
106+
107+
### Step 1: Set the tracking scroll view
108+
109+
In your viewDidLoad, set the `trackingScrollView` property on the header view:
110+
111+
self.headerViewController.headerView.trackingScrollView = scrollView;
112+
113+
`scrollView` might be a table view, collection view, or a plain UIScrollView.
114+
115+
### Step 2: Forward scroll view delegate events to the header view
116+
117+
There are two ways to forward scroll events.
118+
119+
**Set headerViewController as the delegate**
120+
121+
You may use this approach if you do not need to implement any of the delegate's methods yourself
122+
**and your scroll view is not a collection view**.
123+
124+
scrollView.delegate = self.headerViewController;
125+
126+
**Forward the UIScrollViewDelegate methods to the header view**
127+
128+
If you need to implement any of the UIScrollViewDelegate methods yourself then you will need to
129+
manually forward the following methods to the flexible header view.
130+
131+
#pragma mark - UIScrollViewDelegate
132+
133+
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
134+
if (scrollView == self.headerViewController.headerView.trackingScrollView) {
135+
[self.headerViewController.headerView trackingScrollViewDidScroll];
136+
}
137+
}
138+
139+
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
140+
if (scrollView == self.headerViewController.headerView.trackingScrollView) {
141+
[self.headerViewController.headerView trackingScrollViewDidEndDecelerating];
142+
}
143+
}
144+
145+
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
146+
if (scrollView == self.headerViewController.headerView.trackingScrollView) {
147+
[self.headerViewController.headerView trackingScrollViewDidEndDraggingWillDecelerate:decelerate];
148+
}
149+
}
150+
151+
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView
152+
withVelocity:(CGPoint)velocity
153+
targetContentOffset:(inout CGPoint *)targetContentOffset {
154+
if (scrollView == self.headerViewController.headerView.trackingScrollView) {
155+
[self.headerViewController.headerView trackingScrollViewWillEndDraggingWithVelocity:velocity
156+
targetContentOffset:targetContentOffset];
157+
}
158+
}
159+
160+
### Step 3: Implement prefersStatusBarHidden and query the flexible header view controller
161+
162+
In order to affect the status bar's visiblity you must query the header view controller.
163+
164+
- (BOOL)prefersStatusBarHidden {
165+
return self.headerViewController.prefersStatusBarHidden;
166+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/*
2+
Copyright 2015-present Google Inc. All Rights Reserved.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
#import <UIKit/UIKit.h>
18+
19+
#import "MaterialFlexibleHeader.h"
20+
21+
@interface FlexibleHeaderTypicalUseViewController : UITableViewController <MDCFlexibleHeaderParentViewController>
22+
@end
23+
24+
@implementation FlexibleHeaderTypicalUseViewController
25+
26+
@synthesize headerViewController;
27+
28+
// TODO: Support other categorizational methods.
29+
+ (NSArray *)catalogHierarchy {
30+
return @[ @"Flexible Header", @"Typical use" ];
31+
}
32+
33+
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
34+
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
35+
if (self) {
36+
[MDCFlexibleHeaderViewController addToParent:self];
37+
}
38+
return self;
39+
}
40+
41+
- (void)viewDidLoad {
42+
[super viewDidLoad];
43+
44+
UIToolbar *bar = [UIToolbar new];
45+
bar.items = @[ [[UIBarButtonItem alloc] initWithTitle:@"Back"
46+
style:UIBarButtonItemStyleDone
47+
target:self
48+
action:@selector(didTapButton:)] ];
49+
bar.frame = self.headerViewController.headerView.bounds;
50+
bar.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
51+
[self.headerViewController.headerView addSubview:bar];
52+
53+
self.headerViewController.headerView.trackingScrollView = self.tableView;
54+
self.tableView.delegate = self.headerViewController;
55+
56+
[self.headerViewController addFlexibleHeaderViewToParentViewControllerView];
57+
}
58+
59+
- (void)viewWillAppear:(BOOL)animated {
60+
[super viewWillAppear:animated];
61+
62+
[self.navigationController setNavigationBarHidden:YES animated:animated];
63+
}
64+
65+
- (void)didTapButton:(id)button {
66+
[self.navigationController popViewControllerAnimated:YES];
67+
}
68+
69+
#pragma mark - UITableViewDataSource
70+
71+
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
72+
return 50;
73+
}
74+
75+
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
76+
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"cell"];
77+
if (!cell) {
78+
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"];
79+
}
80+
cell.textLabel.text = [NSString stringWithFormat:@"%ld", indexPath.row];
81+
return cell;
82+
}
83+
84+
@end
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
#import <UIKit/UIKit.h>
2+
3+
@class MDCFlexibleHeaderViewController;
4+
5+
/**
6+
The MDCFlexibleHeaderContainerViewController controller is a straightforward container of a
7+
content view controller and a MDCFlexibleHeaderViewController.
8+
9+
This view controller may be used in situations where the content view controller can't have a
10+
header injected into its view hierarchy. UIPageViewController is one such view controller.
11+
*/
12+
@interface MDCFlexibleHeaderContainerViewController : UIViewController
13+
14+
- (nonnull instancetype)initWithContentViewController:(nullable UIViewController *)contentViewController
15+
NS_DESIGNATED_INITIALIZER;
16+
17+
/**
18+
The header view controller owned by this container view controller.
19+
20+
Created during initialization.
21+
*/
22+
@property(nonatomic, strong, nonnull, readonly) MDCFlexibleHeaderViewController *headerViewController;
23+
24+
/** The content view controller to be displayed behind the header. */
25+
@property(nonatomic, strong, nullable) UIViewController *contentViewController;
26+
27+
/**
28+
Returns a Boolean indicating whether the status bar should be hidden or not.
29+
30+
Must be called by the parent view controller's -prefersStatusBarHidden implementation.
31+
*/
32+
- (BOOL)prefersStatusBarHidden;
33+
34+
/** Calculates the status bar style based on the header view's background color. */
35+
- (UIStatusBarStyle)preferredStatusBarStyle;
36+
37+
@end
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
#if !defined(__has_feature) || !__has_feature(objc_arc)
2+
#error "This file requires ARC support."
3+
#endif
4+
5+
#import "MDCFlexibleHeaderContainerViewController.h"
6+
7+
#import "MDCFlexibleHeaderView.h"
8+
#import "MDCFlexibleHeaderViewController.h"
9+
10+
@interface MDCFlexibleHeaderContainerViewController () <MDCFlexibleHeaderParentViewController>
11+
@end
12+
13+
@implementation MDCFlexibleHeaderContainerViewController
14+
15+
- (instancetype)initWithContentViewController:(UIViewController *)contentViewController {
16+
self = [super initWithNibName:nil bundle:nil];
17+
if (self) {
18+
[MDCFlexibleHeaderViewController addToParent:self];
19+
20+
self.contentViewController = contentViewController;
21+
}
22+
return self;
23+
}
24+
25+
- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
26+
return [self initWithContentViewController:nil];
27+
}
28+
29+
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
30+
return [self initWithContentViewController:nil];
31+
}
32+
33+
- (void)viewDidLoad {
34+
[super viewDidLoad];
35+
36+
[self.view addSubview:self.contentViewController.view];
37+
[self.contentViewController didMoveToParentViewController:self];
38+
39+
[self.headerViewController addFlexibleHeaderViewToParentViewControllerView];
40+
}
41+
42+
- (BOOL)prefersStatusBarHidden {
43+
return _headerViewController.prefersStatusBarHidden;
44+
}
45+
46+
- (UIStatusBarStyle)preferredStatusBarStyle {
47+
return _headerViewController.preferredStatusBarStyle;
48+
}
49+
50+
#pragma mark - Public
51+
52+
- (void)setHeaderViewController:(MDCFlexibleHeaderViewController *)headerViewController {
53+
_headerViewController = headerViewController;
54+
}
55+
56+
- (void)setContentViewController:(UIViewController *)contentViewController {
57+
if (_contentViewController == contentViewController) {
58+
return;
59+
}
60+
61+
// Teardown of the old controller
62+
63+
[_contentViewController willMoveToParentViewController:nil];
64+
if ([_contentViewController isViewLoaded]) {
65+
[_contentViewController.view removeFromSuperview];
66+
}
67+
[_contentViewController removeFromParentViewController];
68+
69+
// Setup of the new controller
70+
71+
_contentViewController = contentViewController;
72+
73+
[self addChildViewController:contentViewController];
74+
if ([self isViewLoaded]) {
75+
[self.view insertSubview:contentViewController.view
76+
belowSubview:self.headerViewController.headerView];
77+
[contentViewController didMoveToParentViewController:self];
78+
}
79+
}
80+
81+
@end

0 commit comments

Comments
 (0)