TestsTested | ✗ |
LangLanguage | Obj-CObjective C |
License | MIT |
ReleasedLast Release | Apr 2015 |
Maintained by Bryan Keller.
BLKFlexibleHeightBar
allows you to create header bars with flexible heights. Often, this sort of UI paradigm is used to hide chrome and make room for more content as a user is scrolling. This is seen in third party apps like Facebook and Square Cash, as well as first party apps like Safari.
BLKFlexibleHeightBar
can create bars that look and act any way you'd like:
Due to this library's modular, extensible nature, you are not limited to any one look or any one feel. What UICollectionView does for presenting collections of data, BLKFlexibleHeightBar
does for creating header bars.
The height of the bar is not set directy by adjusting the bar's frame. Rather, height adjustments are made by setting the progress
property of the bar. The progress property represents how much the bar has shrunk from its maximum height to its minimum height
progress == 0.0
means the bar is at its maximum height.progress == 0.5
means the bar is halfway between its maximum height and minimum height.progress == 1.0
means the bar is at its minimum height.A good starting place is to have a project with some kind of scrolling view (i.e. UITableView, UICollectionView, UIWebView, UIScrollView, etc). Make sure you've set up a property to access your scrolling view since we'll need to set its delegate property later on.
First, import the main BLKFlexibleHeightBar
header into the class where you'll be configuring the bar.
#import "BLKFlexibleHeightBar.h"
Next, create an instance of BLKFlexibleHeightBar
and configure it. When we initWithFrame:
, the bar's maximumBarHeight
property is autmatically set to the frame's height. We can manually set the minimumBarHeight
from its default value of 20.0. Lastly, we can give it a color and add our bar to the view hierarchy.
BLKFlexibleHeightBar *myBar = [[BLKFlexibleHeightBar alloc] initWithFrame:CGRectMake(0.0, 0.0, self.view.frame.size.width, 100.0)];
myBar.minimumBarHeight = 50.0;
myBar.backgroundColor = [UIColor colorWithRed:0.86 green:0.25 blue:0.23 alpha:1];
[self.view addSubview:myBar];
Now we've got a bar with no subviews and no behavior, but a defined maximum height and minimum height.
To give the bar a behavior, we can use one of the included behaviors. Square Cash's bar behavior is simple to understand. When scrolled to the top, the bar is at its maximum height. As the user scrolls down, the bar begins to hide. The only way to make the bar visible again is by scrolling to the top.
#import "SquareCashStyleBehaviorDefiner.h"
The SquareCashStyleBehaviorDefiner
(inherits from BLKFlexibleHeightBarBehaviorDefiner
) contains all of the logic that controls how and when the height of the bar changes.
Set myBar
's behaviorDefiner
property to an instance of SquareCashStyleBehaviorDefiner
.
myBar.behaviorDefiner = [SquareCashStyleBehaviorDefiner new];
The behavior of a BLKFlexibleHeightBarBehaviorDefiner
is directly dependent on what's going on with our scrolling view. Therefore, BLKFlexibleHeightBarBehaviorDefiner
(and of course, all of its subclasses) conform to UIScrollViewDelegate
.
Set your scrolling view's UIScrollView
delegate property to myBar
's behaviorDefiner
property (casting if necessary). This gives our behavior definer the information it needs from our scrolling view to properly control the height of our bar.
self.tableView.delegate = (id<UITableViewDelegate>)myBar.behaviorDefiner;
Snapping forces your bar to animate to a final position when the user stops scrolling. Any subclass of BLKFlexibleHeightBarBehaviorDefiner
automatically gets snapping functionality for free. Snapping works by defining a final bar progress
to which the bar will animate whenever the user stops scrolling and the bar is between some range of progress values.
[myBar.behaviorDefiner addSnappingPositionProgress:0.0 forProgressRangeStart:0.0 end:0.5];
[myBar.behaviorDefiner addSnappingPositionProgress:1.0 forProgressRangeStart:0.5 end:1.0];
The above code simply causes the bar to snap to the maxmium height (progress == 0.0
) or minimum height (progress == 1.0
) depending on which is closer. Additional snapping positions can be defined and don't necessarily have to follow the "whichever is closer" rule.
Subviews of a BLKFlexibleHeightBar
instance define layout attribtes (frame
, alpha
, transform
, etc.) for key progress
values.
At progress == 0.0
(maximum bar height), your subview can have alpha == 1.0
.
At progress == 1.0
(minimum bar height), your subview can have alpha == 0.0
and transform == CGAffineTransformMakeScale(0.2, 0.2)
As the bar condenses from its maximum height to its minimum height, your subview will smoothly shrink and fade away.
Start by creating and adding a subview to your bar. Don't bother giving it a frame - the layout attributes it defines in the next step will determine how it looks and where it appears.
UILabel *label = [[UILabel alloc] init];
label.text = @"TrendyStartup.io";
label.font = [UIFont systemFontOfSize:25.0];
label.textColor = [UIColor whiteColor];
[label sizeToFit];
[myBar addSubview:label];
Next, we'll configure layout attributes for all of the discrete layout states of the subview. For this example, we want our label to fade, shrink, and slide up as the bar shrinks, disappearing entirely when the bar is fully condensed. We'll need to define 2 discrete states for our subview - the initial state, where the subview is entirely visible and full size, and the final state, where the subview is faded away and no longer visible. All of the in-between states will be automatically filled in and with a smooth transition.
We define these discrete layout states by defining layout attributes for a subview using the BLKFlexibleHeightBarSubviewLayoutAttributes
class.
BLKFlexibleHeightBarSubviewLayoutAttributes *initialLayoutAttributes = [BLKFlexibleHeightBarSubviewLayoutAttributes new];
initialLayoutAttributes.size = label.frame.size;
initialLayoutAttributes.center = CGPointMake(CGRectGetMidX(myBar.bounds), CGRectGetMidY(myBar.bounds)+10.0);
// This is what we want the bar to look like at its maximum height (progress == 0.0)
[label addLayoutAttributes:initialLayoutAttributes forProgress:0.0];
and then
// Create a final set of layout attributes based on the same values as the initial layout attributes
BLKFlexibleHeightBarSubviewLayoutAttributes *finalLayoutAttributes = [[BLKFlexibleHeightBarSubviewLayoutAttributes alloc] initWithExistingLayoutAttributes:initialLayoutAttributes];
finalLayoutAttributes.alpha = 0.0;
CGAffineTransform translation = CGAffineTransformMakeTranslation(0.0, -30.0);
CGAffineTransform scale = CGAffineTransformMakeScale(0.2, 0.2);
finalLayoutAttributes.transform = CGAffineTransformConcat(scale, translation);
// This is what we want the bar to look like at its minimum height (progress == 1.0)
[label addLayoutAttributes:finalLayoutAttributes forProgress:1.0];
More layout attributes and more subviews can be added in exactly the same way, allowing you to create just about any flexible bar design imaginable.
If one of the included behavior definers isn't what you're looking for in your app, creating your own custom behavior definer might be the way to go.
In order to support any kind of behavior, BLKFlexibleHeightBar
was designed to feature a plug-n-play architecure for bar behaviors. By subclassing BLKFlexibleHeightBarBehaviorDefiner
, developers can have complete control over how and when a bar's progress
property (and therefore, height) is updated.
BLKFlexibleHeightBarBehaviorDefiner
isn't useful until subclassed. By itself, it only provides snapping functionality and a property that controls whether or not the bar bounces when it reaches its maximum height. It makes no attempt to adjust the height of the bar during scrolling.Start by creating a subclass BLKFlexibleHeightBarBehaviorDefiner
.
The basic pattern for the definer is as follows:
UIScrollViewDelegate
protocol methods. -scrollViewDidScroll:
is generally a useful starting point.-scrollViewDidScroll:
, calculate a new progress value for the behavior definer's flexibleHeightBar
property. It will be useful to ask the flexibleHeightBar
for its maximumBarHeight
and minimumBarHeight
properties.self.flexibleHeightBar.progress
to the calculated value from step 2.flexibleHeightBar
that it needs to re-layout using [self.flexibleHeightBar setNeedsLayout]
It may be useful to make other calculations outside of -scrollViewDidScroll:
. For example, the included FacebookStyleBehaviorDefiner
needs to apply scrolling thresholds before the bar should hide or reveal itself. This calculation is done inside of -scrollViewWillBeginDragging:
.
SafariStyleBehaviorDefiner
(uses velocity, not just scroll position).You can get in touch with me via Twitter @BKyourway19