iOS开发------响应TableView下拉设置NavigationBar的透明度

       最近工作不忙的时候会偷懒玩一会手机,会发现各大App中的一些小细节很吸引人,这次看到一个很有意思的效果,其实这个效果应用的已经很普遍,就是响应scrollView的下滑,NavigationBar从无到有(其实就是透明度从0到1的过程),看起来总是简单的,实现起来总会遇到点麻烦,这次也一样,经过查阅相关技术博客以及花点时间研究原理,也慢慢的理解实现了出来,个人还是觉得了解原理要比直接拿来代码要好的多,大体效果如下:


这里先附上代码的GitHub:https://github.com/YRunIntoLove/YSinaNavigationBarDemo


                                                                   iOS开发------响应TableView下拉设置NavigationBar的透明度_第1张图片


直接设置NavigationBar的背景颜色透明度----fail

首先想到的必然是直接修改背景(background)的alpha(透明度属性),即在scrollViewDidScroll中根据比例设置响应的透明度即可
self.navigationController?.navigationBar.backgroundColor = UIColor.orangeColor().colorWithAlphaComponent(alpha)

如果这么设置了,那么大家就和楼主一样想的太过简单了,尝试过后发现根本没有任何的效果,透明度没有任何的变化


设置NavigationBar的alpha----fail

如果设置背景颜色不能完成,那么我直接设置NavigationBar的透明度不就好了嘛
self.navigationController?.navigationBar.alpha = 0.3

这样子做当然可以,但是会发现一个小小的问题,就是贴在NavigationBar上的Title以及相关响应按钮等相关组件也会根据父视图的透明度变透明了,这似乎也不是咱们理想中的效果

利用runtime的关联属性,为NavigationBar添加一层自定义的视图层----Success

       没有办法,打开百度一搜,果然会发现有很多这样子的尝试,最终还是需要动用runtime来自定义添加一个视图最为合适,动态关联属性算是runtime中很简单的知识了,花点时间了解一下也就会理解了,也可以在前面的博客 iOS开发----runtime关联对象(动态添加属性)中稍微的回顾一下。
     
首先需要新建一个NavigationBar的类目(拓展),因为最近学习Swift,代替Objective-C中类目的是Swift中的extension,需要在拓展中添加一个属性,说是属性,实际上是添加了一个get和set方法,自定义一个视图
var key:String = "CoverView"

extension UINavigationBar
{
    
    var coverView:UIView?{
        
        set{
            //runtime添加动态关联的属性
            objc_setAssociatedObject(self, &key, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
        
        get{
            //runtime读取动态关联的属性
            return objc_getAssociatedObject(self, &key) as? UIView
        }
    }

然后就对这个动态关联的视图进行颜色的设置
/**
 *  设置 背景色
 */
@available(iOS 8.0,*)
func setViewColor(color:UIColor)
{
    //如果覆盖图层为nil
    if(self.coverView == nil)
    {
        //设置背景图片及度量
        self.setBackgroundImage(UIImage(), forBarMetrics: .Default)
        //去除自定义背景图后形成的下端黑色横线
        self.shadowImage = UIImage()
        //设置图层的frame
        let view = UIView(frame: CGRect(x: 0, y: -20, width: UIScreen.mainScreen().bounds.width, height: CGRectGetHeight(self.frame) + 20))
        view.userInteractionEnabled = false//人机不交互
        view.autoresizingMask = [.FlexibleWidth , .FlexibleHeight]//自适应宽度和高度
        //将图层添加到导航Bar的底层
        self.insertSubview(view, atIndex: 0)
        
        //因为这里不是一个真正的属性,是在runtime时进行关联的属性,所以相关属性的修改需要实例对象来"赋值"
        self.coverView = view
    }
    
    self.coverView?.backgroundColor = color
}

这里说一下,其实Xcode提供给大家一个查看UI层次的功能<#楼主才知道,- - 感觉对Xcode的了解太差劲了#> Debug -> View Debuging -> Capture View Hierarchy
它可以让我们看到不少组件中私有属性,在这里可以看到NavigationBar的子视图中有这个一个子视图(_ UINavigationBarBackground),他的子视图中有一个UIImageView,也就是当我们设置背景图片或者背景色的时候,应该就是这个视图在响应。

iOS开发------响应TableView下拉设置NavigationBar的透明度_第2张图片


当然设置透明度用上面的方法是可以的,在颜色中添加透明度即可,但是习惯性的还是写了一个接口
/**
 *  设置透明度
 */
@available (iOS 8.0, *)
func setViewAlpha(alpha:CGFloat)
{
    //如果view = self.coverView不成立,就return
    guard let view = self.coverView else
    {
        return
    }
    
    self.coverView!.backgroundColor = view.backgroundColor?.colorWithAlphaComponent(alpha)
}

上面的效果因为push之后出现的NavigationBar的颜色不一样,所需的效果也不一样,所以为了避免影响之后NavigationBar的效果,需要写一个移除该图层的方法,如下
/**
 *  清除图层,视图消失时需要调用该方法,不然会影响其他页面的效果
 */
@available (iOS 8.0, *)
func relieveCover()
{
    self.setBackgroundImage(nil, forBarMetrics: .Default)
    coverView?.removeFromSuperview()
    coverView = nil
}


当然上面的已经完全可以实现上面的功能了,但想要功能更加圆滑一点,便又封装了一个CustomHeaderView

首先需要定义几个相关的属性
class CustomHeaderView: UIView {
    
    /// 代理
    weak var delegate:CustomHeaderViewDelegate?

    ///底层控制ImageView缩放的View,后面通过更改它的frame属性来实现圆滑效果
    var contentView:UIView! = UIView()
    
    /// 存放外部传入的视图,即ImageView
    var subView:UIView
    
    /// 最大的下拉距离
    var maxContentOff:CGFloat
    
    /// 起点的纵坐标
    private let originY:CGFloat = -64

接着实现自定义构造方法
init(subView:UIView,maxContentOff:CGFloat,headerViewSize: CGSize,delegate: CustomHeaderViewDelegate)
{
    self.subView = subView//当前的imageView
    self.delegate = delegate
    self.maxContentOff = maxContentOff > 0 ? -maxContentOff : maxContentOff//因为向下滑动是负数,进行数字正负转换
    
    super.init(frame: CGRectMake(0, 0, headerViewSize.width, headerViewSize.height))
    
    //开始自动布局设置,意思是自动将subView的frame与superView相一致
    subView.autoresizingMask = [.FlexibleTopMargin,.FlexibleBottomMargin,.FlexibleLeftMargin,.FlexibleRightMargin,.FlexibleWidth,.FlexibleHeight]
    
    //此视图不显示越界的视图
    self.clipsToBounds = false
    self.contentView.frame = self.bounds
    self.contentView.addSubview(subView)
    
    //存放ImageView的视图需要显示越界的视图
    self.contentView.clipsToBounds = true
    self.addSubview(contentView)
}

required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
}


因为涉及到往外传值,所以必须使用回调,这里使用的是Delegate(委托)回调,当然是可以使用闭包传值的,定义协议如下:
protocol CustomHeaderViewDelegate : class
{
    
    /**
     滚动已经到达最大偏移量,需要锁定滚动视图
     
     :param: customHeaderView
     :param: maxContentOffSet 最大偏移量
     */
    @available(iOS 8.0,*)
    func customHeaderView(customHeaderView:CustomHeaderView,lockScrollView  maxContentOffSet:CGFloat)
    
    
    
    /**
     滚动过程中修改导航Bar的透明度
     
     :param: customHeaderView
     :param: alpha            透明度
     */
    @available(iOS 8.0,*)
    func customHeaderView(customHeaderView:CustomHeaderView,shouldChangeBarAlpha alpha:CGFloat)
}

最后就是需要一个对外开放的方法,设置之前说的相关contentView的frame实现圆滑效果以及触发Delegate方法
// MARK: - 对外接口
func layoutHeaderWillScroll(offSet:CGPoint)
{
    //获取垂直偏移量
    let contentOffY = offSet.y
    
    //如果偏移量大于最大偏移量,因为是负数,所以是小于
    if(contentOffY < maxContentOff)
    {
        //锁定坐标
        self.delegate?.customHeaderView(self, lockScrollView: maxContentOff)
    }
    
    else if(contentOffY < 0)//如果小于0,表示headerView还显示在ScrollView中
    {
        var rect = CGRectMake(0, 0, self.bounds.size.width, self.bounds.size.height)
        
        rect.origin.y += contentOffY ;
        rect.size.height -= contentOffY;
        self.contentView.frame = rect;
    }
    
    //64 + 当前的垂直偏移量
    let alpha = (-originY + contentOffY) / self.frame.size.height
    
    //设置透明度
    self.delegate?.customHeaderView(self, shouldChangeBarAlpha: alpha)
}


在主ViewController中使用即可

定义初始化的属性以及在ViewDidLoad中进行初始化
class ViewController: UIViewController,UITableViewDelegate,UITableViewDataSource,CustomHeaderViewDelegate{

    var tableView: UITableView!
    var imageView: UIImageView!
    
    var imageHeight:CGFloat?
    var imageDistance:CGFloat?
    
    let barColor = UIColor.orangeColor()
    
    override func viewDidLoad() {
        
        super.viewDidLoad()

        //设置导航栏的属性
        self.navigationController?.navigationBar.setViewColor(barColor.colorWithAlphaComponent(0.0))
        
        //设置列表属性
        tableView = UITableView(frame: self.view.bounds)
        tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "Cell")
        tableView.dataSource = self
        self.view.addSubview(tableView)
        
        
        //设置显示图片的视图
        imageView = UIImageView(frame: CGRectMake(0, 0, self.view.bounds.size.width, 100))
        imageView.contentMode = .ScaleAspectFill
        imageView.image = UIImage(named: "backGround.jpg")
        
        let customHeaderView = CustomHeaderView(subView: imageView, maxContentOff: -120, headerViewSize: CGSize(width: self.view.bounds.size.width,height: 100),delegate:self)
        tableView.tableHeaderView = customHeaderView

    }

为了不影响其他的控制器导航栏,不要忘记在消失的时候取消ScrollView的代理以及对NavigationBar图层的去除,
override func viewWillAppear(animated: Bool)
{
    super.viewWillAppear(animated)
    tableView.delegate = self;
}



override func viewWillDisappear(animated: Bool)
{
    tableView.delegate = nil
    super.viewWillDisappear(animated)
    self.navigationController?.navigationBar.relieveCover()
}

在tableView中实现ScrollViewDidScroll:协议方法,调用headerView的对外接口即可
//MARK: - UIScrollView Delegate
func scrollViewDidScroll(scrollView: UIScrollView){
    
    //获得当前的自定义HeaderView对象
    let customView:CustomHeaderView = (scrollView as! UITableView).tableHeaderView as! CustomHeaderView
    
    //设置滚动
    customView.layoutHeaderWillScroll(scrollView.contentOffset)
    
}

实现CustomHeaderView的代理方法
//MARK: - CustomHeaderViewDelegate
func customHeaderView(customHeaderView: CustomHeaderView, lockScrollView maxContentOffSet: CGFloat) {
   
    //锁定滚动视图
    self.tableView.contentOffset.y = maxContentOffSet
}

func customHeaderView(customHeaderView: CustomHeaderView, shouldChangeBarAlpha alpha:CGFloat) {
  
    //设置透明度
    self.navigationController?.navigationBar.setViewColor(self.barColor.colorWithAlphaComponent(alpha))
}

你可能感兴趣的:(iOS,Swift)