iOS 实现NavigationController的title

作者: 劉光軍_MVP | 来源:发表于2016-05-27 16:23 被阅读8011次

    早就心水简书的个人中心界面的NavigationController动态缩放titleView效果,也就是如下图的效果:

    screenShot.png

    自己动手用Object-C和Swift两种语言各写了一个简单的小demo,下面先说一下用Object-C实现的简单原理.

    知识补充=====>

    因为在这个效果实现的过程中我遇到一些关于tableView的contentInset和contentOffset的困扰,所以在这里我想先解释明白关于这两个属性,然后再谈怎样实现我们需要的效果。

    1:概念:

    contentSize:The size of the content view.其实就是scrollview可以滚动的区域。比如frame = (0, 0, 320, 480) contentSize = (320, 960),代表scrollview可以上下滚动,滚动区域为frame大小的两倍。
    contentOffset:The point at which the origin of the content view is offset from the origin of the scroll view.是scrollview当前显示区域定点相对于frame定点的偏移量,(向屏幕内拉,偏移量是负值。向屏幕外推,偏移量是正数),比如上个例子,从初始状态向下拉50像素,contentoffset就是(0 ,-50),从初始状态向上推tableview100像素,contentOffset就是(0 ,100)。
    contentInset:The distance that the content view is inset from the enclosing scroll view。是scrollview的contentview的顶点相对于scrollview的位置,例如你的contentInset = (0 ,100),那么你的contentview就是从scrollview的(0 ,100)开始显示。
    举个简单的例子:

    截图1.png

    这是一个带有导航栏的的控制器,控制器上有一个tableView。我将tableView初始化后并打印各个属性的值:如下图:

    截图2.png

    打印属性值的时候我是在scrollview的这个代理方法中打印的:

    
    - (void)scrollViewDidScroll:(UIScrollView *)scrollView {
        
        NSLog(@"contentInset:{%f,%f,%f,%f}", self.tableView.contentInset.top,self.tableView.contentInset.right,self.tableView.contentInset.bottom,self.tableView.contentInset.left);
        
        NSLog(@"contentOffset:{%f,%f}", self.tableView.contentOffset.x, self.tableView.contentOffset.y);
        
        NSLog(@"contentSize:{%f,%f}", self.tableView.contentSize.height, self.tableView.contentSize.width);
    }
    

    *contentInset: top=64,即是navBar的高度,说明是从这儿开始显示tableview而不是从(0,0,0,0)开始显示的。

    *contentOffset: y = -64。当前显示区域顶点相对于frame的偏移量。即是-64,可以理解成从(0,0)的位置向下拉了64像素,上面我们说到过,(向屏幕内拉,偏移量是负值。向屏幕外推,偏移量是正数)。

    *contentSize 是tableView的滑动区域。宽度就是屏幕的宽度,高度是cell.Height乘以cell.Count

    解释完这个概念接下来就相对比较好理解了,接下来说一下效果实现的原理。

    Object-C实现NavigationController的titleView的动态缩放

    demo运行图片如下图:

    PageBlurTestGif.gif
    1 ===>

    首先创建一个topBkView用来替代系统的titleView.然后声明一个全局的_topImageView放在topBkView上.在这里我是这样做的:

    //MARK:-createScaleHeaderView
    - (void)createScaleHeaderView {
        
        UIView *topBkView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 60, 30)];
        topBkView.backgroundColor = [UIColor clearColor];
        _topImageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 60, 60)];
        _topImageView.backgroundColor = [UIColor whiteColor];
        _topImageView.layer.cornerRadius = _topImageView.bounds.size.width/2;
        _topImageView.layer.masksToBounds = YES;
        _topImageView.image = [UIImage imageNamed:@"head"];
        [topBkView addSubview:_topImageView];
        self.navigationItem.titleView = topBkView;
    }
    

    topImageViewheight设为topBkView的2倍,为的是显示出头像在导航栏上山下各一半的效果.

    2 ===>

    实现动态缩放的思路主要体现在监听ScrollView滑动的代理事件中:

    //MARK:-滑动代理
    - (void)scrollViewDidScroll:(UIScrollView *)scrollView {
        
        CGFloat contentSet = scrollView.contentOffset.y + _tableView.contentInset.top;
    
        if (contentSet >= 0 && contentSet <= 30) {
            _topImageView.transform = CGAffineTransformMakeScale(1 - contentSet/60, 1-contentSet/60);
            _topImageView.y = 0;
        } else if (contentSet > 30) {
            _topImageView.transform = CGAffineTransformMakeScale(0.5, 0.5);
            _topImageView.y = 0;
        } else if (contentSet < 0 ) {
            _topImageView.transform = CGAffineTransformMakeScale(1, 1);
            _topImageView.y = 0;
        }
        
    }
    
    

    在这里声明了一个变量contentSet这是scrollView.contentOffset.y_tableView.contentInset.top的和,初始值是0,当tableView向上滑动的时候contentSet为正值并增大,这里我们判断,当contentSet >= 0 && contentSet <= 30时,我们控制_topImageView的缩放量,另外还有两个情况的判断已经在代码中写出来了,还要说明的一点是在tableView滚动监听并且改变_topImageView大小的过程中,要始终保持_topImageView.y = 0;要不然_topImageView会随着大小的变化乱动。

    Swift实现NavigationController的titleView的动态缩放

    效果如图:


    PageBlurTestGif.gif

    这个比较好玩,是我之前看的一个大牛做的一个效果,后来闲着没事儿用swift写了一遍。这几个功能也是咱们平时比较常见的。这一块我比较懒,没有进行tableView的复用,每一个功能直接copy的tableview。哈哈。

    1 titleView动态缩放===>

    先看第一个界面,就是和简书那个titleView动态缩放比较相似的效果,只是多了一个下拉放大功能,原理是相同的。

    
    //滑动代理方法
        func scrollViewDidScroll(scrollView: UIScrollView) {
            
            let offsetY:CGFloat = scrollView.contentOffset.y + (tableView?.contentInset.top)!
            print("offsetY = %f contentOffset.y = %f contentInset.top = %f", offsetY, scrollView.contentOffset.y, tableView?.contentInset.top)
            if offsetY < 0 && offsetY >= -150 {
                topImageView?.transform = CGAffineTransformMakeScale(1 - offsetY/300, 1 - offsetY/300)
            } else if (offsetY >= 0 && offsetY <= 150) {
                topImageView?.transform = CGAffineTransformMakeScale(1 - offsetY/300, 1 - offsetY/300)
            } else if (offsetY > 150) {
                topImageView?.transform = CGAffineTransformMakeScale(0.45, 0.45)
            } else if(offsetY < -150) {
                topImageView?.transform = CGAffineTransformMakeScale(1.5, 1.5)
            }
            var frame:CGRect = (topImageView?.frame)!
            frame.origin.y = 5;
            topImageView?.frame = frame
            
        }
    
    2 滑动隐藏navBar===>

    panTranslationY这个变量是监听tableView在scrollView中滑动状态的。

    // translationInView :translation in the coordinate system of the specified view
    

    意思就是你用手指触动tableView上下滑动时所反映出来的一个数值,向下拉动为正值,向上拖动为负值。通过这个值的变动进行navBar的隐藏或显示。

    func scrollViewDidScroll(scrollView: UIScrollView) {
            
            let offsetY:CGFloat = scrollView.contentOffset.y + (tableView?.contentInset.top)!
            let panTranslationY = scrollView.panGestureRecognizer.translationInView(tableView).y
            
            if offsetY > 0 {
                if panTranslationY > 0 {
                    //下滑趋势 显示
                    [self.navigationController?.setNavigationBarHidden(false, animated: true)]
                } else {
                    //上滑趋势 隐藏
                    [self.navigationController?.setNavigationBarHidden(true, animated: true)]
                }
            } else {
                [self.navigationController?.setNavigationBarHidden(false, animated: true)]
            }
        }
    
    3 view动态缩放===>

    先放一张图大致理解这个效果实现的套路:

    截图3.png
    首先在创建tableview的时候需要设置tableview的contentInset,目的是给上部的view留出空间。而且不影响tableview的正常使用。在这里设置topContentInset = 100
    func createTableView() -> () {
            
            if (tableView == nil) {
                tableView = UITableView(frame: UIScreen .mainScreen().bounds, style: .Plain)
                tableView?.contentInset = UIEdgeInsetsMake(topContentInset, 0, 0, 0)
                tableView?.delegate = self
                tableView?.dataSource = self
                tableView?.backgroundColor = UIColor.clearColor()
                tableView?.separatorStyle = .SingleLine
                self.view.addSubview(tableView!)
            }
        }
    

    然后创建头部背景视图。我们将这个topImageView 插到tableView下方。这样的话topImageViewtableview是不在同一层级的不会相互影响,并且我们在初始化tableView的时候已经设置好了tableViewcontentInset了,给topImageView的显示留出了空间。

    //MARK:-创建顶部背景视图
        func createScaleImageView() -> Void {
            
            topImageView = UIImageView(frame: CGRectMake(0, 0, UIScreen .mainScreen().bounds.width, UIScreen .mainScreen().bounds.width*435.5/313.0))
            topImageView?.backgroundColor = UIColor.whiteColor()
            topImageView?.image = UIImage(named: "backImage")
            self.view.insertSubview(topImageView!, belowSubview: tableView!)
        }
    

    接下来创建头像视图,headBkView:UIView的高度也是topContentInset = 100背景颜色设置为透明。将tableView?.tableHeaderView = headBkView,因为我们需要头像跟随tableview的滑动而移动。这样我们也给topImageView留出了充足的显示空间。

    //MARK:-创建头像视图
        func createHeadView() -> Void {
            
    //        topContentInset = 136;//136+64 = 200
            let headBkView:UIView = UIView(frame: CGRectMake(0, 0, UIScreen .mainScreen().bounds.width, topContentInset))
            headBkView.backgroundColor = UIColor.clearColor()
            tableView?.tableHeaderView = headBkView
            
            let headImageView = UIImageView()
            headImageView.bounds = CGRectMake(0, 0, 64, 64)
            headImageView.center = CGPointMake(UIScreen .mainScreen().bounds.width/2.0, (topContentInset - 64)/2.0)
            headImageView.backgroundColor = UIColor.whiteColor()
            headImageView.layer.cornerRadius = headImageView.bounds.size.width / 2.0
            headImageView.layer.masksToBounds = true
            headImageView.image = UIImage(named: "head")
            headBkView.addSubview(headImageView)
        }
    

    最后就是实现滑动时topImageView的缩放了,在这里面我有对navBar的透明度的设置,真正起到改变topImageView缩放效果的就是这一句

    else if offsetY < 0 { topImageView?.transform = CGAffineTransformMakeScale(1 + offsetY/(-500), 1 + offsetY/(-500)) }
    
    //MARK:-滑动代理
        func scrollViewDidScroll(scrollView: UIScrollView) {
            
            let offsetY = scrollView.contentOffset.y + (tableView?.contentInset.top)!
            
            print("\(offsetY)")
            
            if offsetY > topContentInset && offsetY <= topContentInset*2 {
                
                statusBarStyleControl = true
                self.navigationController?.navigationBar.setBackgroundImage(UIImage(), forBarMetrics: UIBarMetrics.Default)
                self.navigationController?.navigationBar.shadowImage = UIImage()
                self.navigationController?.navigationBar.translucent = true
                
            }
            else if (offsetY <= topContentInset && offsetY >= 0) {
                
                statusBarStyleControl = false
                if (self.respondsToSelector(#selector(UIViewController.setNeedsStatusBarAppearanceUpdate))) {
                    self.setNeedsStatusBarAppearanceUpdate()
                }
                self.navigationController?.navigationBar.setBackgroundImage(UIImage(), forBarMetrics: UIBarMetrics.Default)
                self.navigationController?.navigationBar.shadowImage = UIImage()
                self.navigationController?.navigationBar.translucent = true
            }
            else if offsetY > topContentInset * 2 {
    
                self.navigationController?.navigationBar.setBackgroundImage(UIImage(), forBarMetrics: UIBarMetrics.Default)
                self.navigationController?.navigationBar.shadowImage = UIImage()
                self.navigationController?.navigationBar.translucent = true
                
            }
            else if offsetY < 0 {
                topImageView?.transform = CGAffineTransformMakeScale(1 + offsetY/(-500), 1 + offsetY/(-500))
            }
            var frame:CGRect = (topImageView?.frame)!
            frame.origin.y = 0
            topImageView?.frame = frame
            
        }
    

    END

    这几个小功能的大体思路就大概是这样的,如果有不正确的地方欢迎批评指正。最后放上代码链接:
    https://github.com/irembeu/NavBarTitleViewScaleDemo.git

    相关文章

      网友评论

      • 谢谢生活:push到下个页面返回后,titleView图片就就能显示一半了??这个怎么回事啊
        谢谢生活:@fierceFlame 解决不了,后面我贴了个大的创可贴在上面:当控制器显示的时候从新添加View。
        b452caeea2ac:兄弟 你解决了吗
      • 任梦RM:在线等!作者看到求方案1
      • 任梦RM:iOS 11 上会被截掉一半,作者怎么解决的?
        任梦RM:@劉光軍_Shine 我找不到原因!
        任梦RM:@劉光軍_Shine 不是X的原因,只要是11的系统就会显不全!
        劉光軍_MVP:@任梦RM 我还没有考虑到iOS11的适配,上面截取一部分是因为safearea的原因吧,具体的你网上找一下关于iPhonex的适配吧
      • 480a52903ce5:写的很详细,大赞
      • 45826397f73f:很不错的效果,学习一下,已关注!
      • 一号线:刚进去的时候,导航条上的icon只会显示一半,会等1s才全部显示出来。
      • nuannuan_nuan:额,对于scrollView类型的视图防止在控制器的自带视图上,感觉要做好多处理。刚就发现一个问题,对于scrollView的contentInset的设置,最好放在viewDidLayoutSubviews方法中处理最好,否则可能会出现,contentInset设置后,追加64.
      • Rookie_Neo:简书是三个tableview的滑动 而不是一个 这是怎么做的呢
        劉光軍_MVP:@清雪飘香 兄弟 你这个思路有点儿妙:+1::+1:
        清雪飘香:@劉光軍_ 是三个。 然后每个tableview 对应一个headview ,再用一个UIImageView 截屏了headView 的内容,hidden = YES,,滑动的时候,head view也在滚动,不过当前的image view.hidden = NO. 实际上实现的思路就是平常的思路。这里有个UIImageView 去迷惑了我们的视线。
        劉光軍_MVP:@Rookie_Neo 这个我也没搞明白:joy::joy:用tableview尝试了一下 感觉思路不对
      • 小城东风:很棒的思路
      • 遛狗的猫:正好最近也在做一个模仿简书的Demo,这个思路挺有帮助的
      • 麦子Alen:很有意思的效果
      • Cyandev:我就很好奇为什么要打码...
        Cyandev:@劉光軍_ 哦哦
        劉光軍_MVP:@Cyandev 向首页投稿的时候审核的人给我说的不能出现微博链接:smile:
      • 啃手高手:这个好,用简书第一天就感觉导航上的头像缩放有意思!:+1:🏻:+1:🏻:+1:🏻厉害:+1:🏻

      本文标题:iOS 实现NavigationController的title

      本文链接:https://www.haomeiwen.com/subject/wbdxdttx.html