The key is to use the scroll offset to set the alpha for the visualEffectView.
Then when the image is the height of navigationBar from being completely off the screen you switch from the transparent nav bar to the normal translucent one. I created a view controller class that can do this using a UIScrollView
, but the same principle applies to UITableView
or UICollectionView
.
import UIKit
class NavigationBlurViewController: UIViewController, UIScrollViewDelegate {
// Might not want to hard code the height of the navBar but YOLO
let navBarHeight: CGFloat = 66.0
lazy var scrollView: UIScrollView = {
let scrollView = UIScrollView()
scrollView.translatesAutoresizingMaskIntoConstraints = false
return scrollView
}()
let contentView = UIView()
let imageView: UIImageView = {
let imageView = UIImageView()
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.image = UIImage(named: "Swift")
imageView.clipsToBounds = true
imageView.contentMode = .ScaleAspectFill
return imageView
}()
lazy var visualEffectView: UIVisualEffectView = {
let blurEffect = UIBlurEffect(style: .Light)
let visualEffectView = UIVisualEffectView(effect: blurEffect)
visualEffectView.translatesAutoresizingMaskIntoConstraints = false
visualEffectView.alpha = 0.0
return visualEffectView
}()
override func viewDidLoad()
{
super.viewDidLoad()
self.view.addSubview(self.scrollView)
self.scrollView.addSubview(self.contentView)
self.contentView.addSubview(self.imageView)
self.contentView.addSubview(self.visualEffectView)
self.navigationController?.navigationBar.setBackgroundImage(UIImage(), forBarMetrics: UIBarMetrics.Default)
self.navigationController?.navigationBar.shadowImage = UIImage()
self.navigationController?.navigationBar.tintColor = UIColor.blackColor()
}
override func updateViewConstraints()
{
super.updateViewConstraints()
self.scrollView.topAnchor.constraintEqualToAnchor(self.view.topAnchor).active = true
self.scrollView.leadingAnchor.constraintEqualToAnchor(self.view.leadingAnchor).active = true
self.scrollView.trailingAnchor.constraintEqualToAnchor(self.view.trailingAnchor).active = true
self.scrollView.bottomAnchor.constraintEqualToAnchor(self.view.bottomAnchor).active = true
self.imageView.topAnchor.constraintEqualToAnchor(self.contentView.topAnchor, constant: -navBarHeight).active = true
self.imageView.leadingAnchor.constraintEqualToAnchor(self.contentView.leadingAnchor).active = true
self.imageView.trailingAnchor.constraintEqualToAnchor(self.contentView.trailingAnchor).active = true
// 150.0 or however tall you want your image
self.imageView.heightAnchor.constraintEqualToConstant(150.0 + navBarHeight).active = true
self.visualEffectView.centerXAnchor.constraintEqualToAnchor(self.imageView.centerXAnchor).active = true
self.visualEffectView.centerYAnchor.constraintEqualToAnchor(self.imageView.centerYAnchor).active = true
self.visualEffectView.widthAnchor.constraintEqualToAnchor(self.imageView.widthAnchor).active = true
self.visualEffectView.heightAnchor.constraintEqualToAnchor(self.imageView.heightAnchor).active = true
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
scrollView.delegate = self
}
override func viewDidLayoutSubviews()
{
super.viewDidLayoutSubviews()
// Height just 1000 for example
self.scrollView.contentSize = CGSize(width: self.view.bounds.width, height: 1000.0)
self.contentView.frame = CGRect(x: 0.0, y: 0.0, width: self.scrollView.contentSize.width, height: self.scrollView.contentSize.height)
}
func scrollViewDidScroll(scrollView: UIScrollView)
{
// Decrease size of denominator to make it blur faster
self.visualEffectView.alpha = scrollView.contentOffset.y * 1.0 / (self.imageView.frame.height - (2.0 * navBarHeight))
if scrollView.contentOffset.y > (self.imageView.frame.height - (2.0 * navBarHeight)) && self.navigationController?.navigationBar.backgroundImageForBarMetrics(UIBarMetrics.Default) != nil
{
self.navigationController?.navigationBar.setBackgroundImage(nil, forBarMetrics: UIBarMetrics.Default)
self.navigationController?.navigationBar.shadowImage = nil
}
else if scrollView.contentOffset.y < (self.imageView.frame.height - (2.0 * navBarHeight)) && self.navigationController?.navigationBar.backgroundImageForBarMetrics(UIBarMetrics.Default) == nil
{
self.navigationController?.navigationBar.setBackgroundImage(UIImage(), forBarMetrics: UIBarMetrics.Default)
self.navigationController?.navigationBar.shadowImage = UIImage()
}
}
}
Most of the effect logic is in scrollViewDidScroll
. You start with the effect view over the image view but completely transparent as the y offset increases you increase the opacity and vice verse. Once you get to a point were only the height of the navigation bar is left for the image switch to the UINavigationBar
's default background otherwise use UIImage()
to make it transparent.
The result is:
Or a gif here
Obviously, Apple Music does other image manipulation to get soft vignettes, and you'll probably need to play around with the values to get it the way that you want it but this is should get you most of the way there.