Create condensing header bars like those seen in the Facebook, Square Cash, and Safari iOS apps.
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:
- Immediate subviews of a flexible height bar define how they want to look and where they want to be depending on the height of the bar. Properties like frame, transform, and alpha can all vary depending on the current height of the bar.
- A bar's behavior can be defined using a behavior definer instance. A behavior definer can be created to emulate Safari's header behavior, Facebook's header behavior, or something entirely new. Behaviors are completely modular and aren't coupled with the appearence of the bar.
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.
How do I install it?
pod 'BLKFlexibleHeightBar'to your project's Podfile.
- Clone this repo or click "Download ZIP" on the side.
- Copy all of the files in the "BLKFlexibleHeightBar" folder into your project. You probably want to check the box that says "Copy items if needed" as well as make sure that the target you want to add the files to is checked.
How do I use it?
###Before we get started, understand this:
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.0means the bar is at its maximum height.
progress == 0.5means the bar is halfway between its maximum height and minimum height.
progress == 1.0means 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.
view property is the same as its
tableView property, adding a
BLKFlexibleHeightBar instance as a subview of a
view results in the bar being a subview of the
tableView. I personally recommend just using a plain old
UIViewController so you can fully control its view's subviews. Alternatively, see this stackoverflow post.
First, import the main
BLKFlexibleHeightBar header into the class where you'll be configuring the bar.
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];
Configuring bar behavior
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.
SquareCashStyleBehaviorDefiner (inherits from
BLKFlexibleHeightBarBehaviorDefiner) contains all of the logic that controls how and when the height of the bar changes.
behaviorDefiner property to an instance of
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
Set your scrolling view's
UIScrollView delegate property to
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 behavior (optional)
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 (
transform, etc.) for key
progress == 0.0 (maximum bar height), your subview can have
alpha == 1.0.
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 *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];
// 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.
Congrats! You should now have something that looks and behaves like this:
Creating custom behavior definers
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.
- In most scenarios,
BLKFlexibleHeightBarBehaviorDefinerisn'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
The basic pattern for the definer is as follows:
-scrollViewDidScroll:is generally a useful starting point.
- Based on the current scroll position in
-scrollViewDidScroll:, calculate a new progress value for the behavior definer's
flexibleHeightBarproperty. It will be useful to ask the
self.flexibleHeightBar.progressto the calculated value from step 2.
- Notify the behavior definer's
flexibleHeightBarthat it needs to re-layout using
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
- Include a
SafariStyleBehaviorDefiner(uses velocity, not just scroll position).
- Support for Auto Layout based layout attributes would simplify some of the trickier bar designs, removing the need to perform final frame and size calculations yourself when defining layout attributes.
Let me know what you think!
You can get in touch with me via Twitter @BKyourway19