澳门新葡亰娱乐网站-www.142net-欢迎您

澳门新葡亰娱乐网站是因为你还没有找到一条正确的致富之路,www.142net是将所有的游戏都汇集在一起的官方平台,因为澳门新葡亰娱乐网站这个网站当中有着大量的游戏攻略,托IP定位技术,传达终端直接到达的精准传播方式。

透明导航栏,记一次导航栏平滑过渡的实现

来源:http://www.bhtsgq.com 作者:计算机知识 人气:70 发布时间:2020-04-07
摘要:如自己在传送门:iOS导航栏切换分界面时隐瞒和出示中所说,将来众多App的私有大旨模块都以不保留导航栏的,会一向使导航栏透明,譬如做的很好的QQ个人消息分界面: 前言 品类中有

如自己在传送门:iOS导航栏切换分界面时隐瞒和出示中所说,将来众多App的私有大旨模块都以不保留导航栏的,会一向使导航栏透明,譬如做的很好的QQ个人消息分界面:

前言

品类中有时供给一些导航栏看起来是晶莹剔透的,但地点的item按键依然存在的,那么要怎么比较灵通又简约的落实啊?今日就来做个尝试,供大家参谋, 这里介绍是导航栏的测滑,UIScrollView的滑动导航栏渐变也是同样的道理,监听滑动改换光滑度就可以。

乘胜技能的迭代,今后对App的效应必要更高,那么在这里篇文章里面我们一起研讨一下怎么样在调控器做跳转的时候对导航栏做平滑过渡的转场

在iOS 11在先的洋洋导航栏透明方法无论用了,原因是11导航栏做了改换,详细表达请看大神随笔,戳个链接:iOS NavigationBar 导航栏背景颜色设置 iOS11 适配

图片 1image.png

导航栏透明

最简单易行的导航栏透明只要这几行代码就可以兑现

navigationController?.navigationBar.setBackgroundImage, for: .default)navigationController?.navigationBar.shadowImage = UIImage()

只要要吊销透明,也很简单

navigationController?.navigationBar.setBackgroundImage(nil, for: .default)navigationController?.navigationBar.shadowImage = nil

再有种手动发更换发光度的法子

extension UINavigationController{ func setBackgroundAlpha(alpha:CGFloat){ if let barBackgroundView = navigationBar.subviews.first{ if #available(iOS 11.0, *){ if navigationBar.isTranslucent{ for view in barBackgroundView.subviews { view.alpha = alpha } }else{ barBackgroundView.alpha = alpha } } else { barBackgroundView.alpha = alpha } } }}

此地能够给调节器加多属性方便间接改造

private var navBarAlpha: Void?extension UIViewController{ // navigationBar _UIBarBackground alpha var navBarBackgroundAlpha:CGFloat { get { guard let barBackgroundAlpha = objc_getAssociatedObject(self, &navBarAlpha) as? CGFloat else { return 1.0 } return barBackgroundAlpha } set { objc_setAssociatedObject(self, &navBarAlpha, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) navigationController?.setBackgroundAlpha(alpha: newValue) } }}

姣好之后看起开是那样子的

图片 2image

好了,透明的不二等秘书技介绍完了,未来始发怎样科学的施用(runtime魔法卡塔尔

构思:

  • 第一要拿到到导航栏里的子控件来设置其反射率(达成光滑度变化卡塔尔
  • 为富有调整器加多几个导航栏光滑度属性,用于记录当前调整器的导航栏光滑度(记录发光度值卡塔尔国
  • 由此监听手势滑动来获取源和目标调控器,计算从源到目标调控器的反射率变化,来改换导航栏的反射率(完结平滑对接卡塔尔国

图片 3

bardemo


在translucent属性设置为NO之后会情不自禁难题,把translucent设置为YES,之后加上代码:

何以说QQ做的很好吧?既然有透明的导航栏也可能有不透明的导航栏,那自然会在分界面切换之间存在一个连片的经过,而以此进度,QQ做的专门好,在从透明导航栏界面重回到不透明导航栏分界面时,导航栏的透明度是三个循规蹈矩的过渡效果,以致会有一种毛玻璃的功用,感兴趣的能够张开手提式有线电话机QQ到个体分界面看一看,效果非常的赞。

runtime管理UIViewController的导航栏透明

倘若简单的管理正是在急需透明的UIViewController中加多正如代码就能够:

override func viewWillAppear(_ animated: Bool) { super.viewWillAppear //在这里处理透明 navigationController?.navigationBar.setBackgroundImage, for: .default) navigationController?.navigationBar.shadowImage = UIImage()}override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear navigationController?.navigationBar.setBackgroundImage(nil, for: .default) navigationController?.navigationBar.shadowImage = nil}

每趟只要都要那样写估量要疯,所以大家用runtime交换方法的贯彻来合併实行拍卖, 这里须求知道swift下怎么举办swizzle method的不二法门, 详细请看本身的另一篇小说

swift下利用runtime交流方法的落到实处

交换viewwillappear的实现

extension UIViewController{ func navigationBarTransparency() { navigationController?.navigationBar.setBackgroundImage, for: .default) navigationController?.navigationBar.shadowImage = UIImage() } func navigationBarUnTransparency() { navigationController?.navigationBar.setBackgroundImage(nil, for: .default) navigationController?.navigationBar.shadowImage = nil } public static func hook(){ ///此方法的实现见 “swift下使用runtime交换方法的实现” 文章 DispatchQueue.once(token: onceToken) { let needSwizzleSelectorArr = [ #selector(viewWillAppear ] for selector in needSwizzleSelectorArr { let str = ("prefix_"   selector.description) if let originalMethod = class_getInstanceMethod(self, selector), let swizzledMethod = class_getInstanceMethod(self, Selector { method_exchangeImplementations(originalMethod, swizzledMethod) } } } } @objc private func prefix_viewWillAppear(_ animated: Bool) { //调用系统的 prefix_viewWillAppear updateNavAlpha() } //抽出需要透明的控制器方法 /// 需要导航栏透明的控制器 /// /// - Returns: bool fileprivate func needOpacity() -> Bool{ return self.isKind(of: OneViewController.self) || self.isKind(of: TwoViewController.self) } private func updateNavAlpha() { if needOpacity(){ navBarBackgroundAlpha = 0.0 }else{ navBarBackgroundAlpha = 1.0 } }}

诸如此比控制各个需求透明的调整器就比较便利了。

1、完毕折射率变化

要想完结折射率变化,得先拿走到导航栏里的子控件,然后设置其阿尔法值。不过怎么取得呢? 首先构思选拔KVC,通过导航栏的'valueForKey:'方法来获取子控件对象,不过在不一致的连串上,导航栏里的子控件结构排布也是何啻天壤, 意味着key值并不是一定,通过key值拿子控件对象的方法在差异的体系上就相当轻便抛格外。

图片 4

左:iOS10 右:iOS9

考虑到那或多或少,作者动用了最直接的诀要:遍历导航栏的全数子控件,获得首个子控件给其安装反射率。透明导航栏,记一次导航栏平滑过渡的实现。如此这般不但不须要再去构思系统的标题了,同一时常间也能满意带颜色的导航栏只怕是带背景图的导航栏发光度的更动。

- (void)xa_changeNavBarAlpha:(CGFloat)navBarAlpha{
    NSMutableArray *barSubviews = [NSMutableArray array];
    //将导航栏的子控件添加到数组当中,取首个子控件设置透明度(防止导航栏上存在非导航栏自带的控件)
    for (UIView * view in self.navigationBar.subviews) {
        if(![view isMemberOfClass:[UIView class]]){
            [barSubviews addObject:view];
        }
    }
    UIView *barBackgroundView = [barSubviews firstObject];
    barBackgroundView.alpha   = navBarAlpha;
}
-(void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    [self.navigationController.navigationBar setBackgroundImage:[[UIImage alloc] init] forBarMetrics:UIBarMetricsDefault];
    [self.navigationController.navigationBar setShadowImage:[[UIImage alloc] init]];

}

- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
    [self.navigationController.navigationBar  setBackgroundImage:nil forBarMetrics:UIBarMetricsDefault];
    [self.navigationController.navigationBar setShadowImage:nil];
}

而广大App的做法实在比非常粗大糙,形似于自己在传送门:iOS导航栏切换分界面时隐讳和出示中的做法,须求导航栏透明时,直接将导航栏蒙蔽起来。直接隐敝起来的情趣是,整个导航栏就用持续了,也正是说,标题、再次回到按键等都急需和煦去做,那是一个相比较麻烦的地点,此外,在有无导航栏的分界面间切换时,进程是比较生硬的,导航栏不是渐变现身的。倘诺说这个都可以选择,那最大的三个标题,也是自个儿在此篇小说里关系的,假使恰巧处于用UITabbarConatroller切换分界面,那么导航栏会有三个往上缩回的全速动漫,这件事实上就特别不美观了,当然大家能够由此将掩没导航栏的卡通片去掉来完成对Tabbar切换友好的功效:

监听手势的滑动退换导航栏的光滑度

此处只要调换导航调节的 _updateInteractiveTransition:就能够促成

extension UINavigationController{ public static func injectHook() guard self === UINavigationController.self else { return } DispatchQueue.once(token: "com.moglo.hallo.UINavigationController") { swizzle() } } private static func swizzle(){ let needSwizzleSelectorArr = [ NSSelectorFromString("_updateInteractiveTransition:") ] for selector in needSwizzleSelectorArr { let str = ("prefix_"   selector.description) if let originalMethod = class_getInstanceMethod(self, selector), let swizzledMethod = class_getInstanceMethod(self, Selector { method_exchangeImplementations(originalMethod, swizzledMethod) } } } @objc private func prefix_updateInteractiveTransition(_ percentComplete: CGFloat){ prefix_updateInteractiveTransition(percentComplete) if let topVc = topViewController{ let fromAlpha = topVc.transitionCoordinator?.viewController(forKey: .from)?.navBarBackgroundAlpha ?? 1.0 let toAlpha = topVc.transitionCoordinator?.viewController(forKey: .to)?.navBarBackgroundAlpha ?? 0.0 let nowAlpha = fromAlpha   (toAlpha - fromAlpha) * percentComplete// Console.debug("----------fromAlpha:(fromAlpha) -- toAlpha: -- nowAlpha:") setBackgroundAlpha(alpha: nowAlpha) } } }

那个时候用手滑动大概会发掘这么的标题

图片 5问题

这是由于调控器未有在导航栏下诱致到的功效,其实就是性质extendedLayoutIncludesOpaqueBars开火只要在无需透明的调整器中设置

extendedLayoutIncludesOpaqueBars = true

改建下方才的措施

private func updateNavAlpha() { if needOpacity(){ navBarBackgroundAlpha = 0.0 }else{ extendedLayoutIncludesOpaqueBars = true navBarBackgroundAlpha = 1.0 }}

到那边差相当的少能兑现完全要求了,看起来粗粗是以此样子

图片 6image

2、记录光滑度

各样调整器都应有有温馨的导航栏光滑度且当折射率产生变化后大家都应该把值保存下去,以利于后一次的利用,这里我们就给UIViewController增多一个分拣并加叁个navBarAlpha的质量,那样我们就足以从来通过调节器去设置导航栏的发光度啦~

- (CGFloat)xa_navBarAlpha{
    return [objc_getAssociatedObject(self, _cmd)floatValue] ;
}

- (void)setXa_navBarAlpha:(CGFloat)xa_navBarAlpha{
    if(xa_navBarAlpha > 1){
        xa_navBarAlpha = 1;
    }
    if(xa_navBarAlpha < 0){
        xa_navBarAlpha = 0;
    }
    objc_setAssociatedObject(self, @selector(xa_navBarAlpha), @(xa_navBarAlpha), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    [self.navigationController xa_changeNavBarAlpha:xa_navBarAlpha];
}

目前以为效果幸亏,应该还足以优化,等闲下来继续钻探一下。

[self.navigationController setNavigationBarHidden:NO animated:NO];

修补小劣势

因为是在viewwillappear中设置导航栏的反射率,所以push到下三个调整器的时候中间的跳变是相比较鲜明的,轻巧的做法正是, 做个卡通让发光度的更动不是直接跳变

UIView.animate(withDuration: 0.25, animations: { self.navgationBarAlpha = 0.0}) {  in }

若果那样做,导航栏的底层黑线的隐敝方式不能够像网络直接感觉的写法

navigationBar.clipsToBounds = alpha == 0

由此那边介绍正确的潜伏格局

extension UINavigationController{ var hairLineView: UIImageView? { get { return objc_getAssociatedObject(self, &hairLine) as? UIImageView } set { objc_setAssociatedObject(self, &hairLine, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } ///查找分割线 public func findHairLine(view: UIView) -> UIImageView?{ if view.isKind(of: UIImageView.self) && view.frame.size.height <= 1.0{ return view as? UIImageView } for subView in view.subviews { let imageView = findHairLine(view: subView) if let imageView = imageView{ return imageView } } return nil } public func setHairLine(hidden: Bool){ if let hairLineView = hairLineView{ hairLineView.isHidden = hidden }else{ hairLineView = findHairLine(view: navigationBar) hairLineView?.isHidden = hidden } } }

在上述的setBackgroundAlpha变成

func setBackgroundAlpha(alpha:CGFloat){ if let barBackgroundView = navigationBar.subviews.first{ if #available(iOS 11.0, *){ if navigationBar.isTranslucent{ for view in barBackgroundView.subviews { view.alpha = alpha } }else{ barBackgroundView.alpha = alpha } } else { barBackgroundView.alpha = alpha } } let hidden = alpha == 0 setHairLine(hidden: hidden)}

在手势滑动进程中恐怕产生的小跳变化解

extension UINavgationController: UInavigationControllerDelegate{ public func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) { if let topVC = topViewController, let coor = topVC.transitionCoordinator, coor.initiallyInteractive { if #available(iOS 10.0, *) { coor.notifyWhenInteractionChanges({[weak self]  in self?.dealInteractionChanges }) } else { coor.notifyWhenInteractionEnds({[weak self]  in self?.dealInteractionChanges }) } } } private func dealInteractionChanges(_ context: UIViewControllerTransitionCoordinatorContext){ if context.isCancelled{ let cancelDuration = context.transitionDuration * TimeInterval(context.percentComplete) let fromVc = context.viewController(forKey: .from) let nowAlpha = fromVc?.navBarBackgroundAlpha ?? 1.0 UIView.animate(withDuration: cancelDuration) {[weak self] in self?.setBackgroundAlpha(alpha: nowAlpha) } }else{ let finishDuration = context.transitionDuration * TimeInterval(1 - context.percentComplete) UIView.animate(withDuration: finishDuration) {[weak self] in let nowAlpha = context.viewController(forKey: .to)?.navBarBackgroundAlpha ?? 1.0 self?.setBackgroundAlpha(alpha: nowAlpha) } } } }

此处是最后的效率

图片 7最后效果

3、达成平滑过渡

未来留存的标题就是本人在某些页面设置了导航栏的反射率,back回上多少个分界面,导航栏的晶莹度值仍然为上个分界面包车型地铁

图片 8

navbug

此间做衔接有二种状态,一种是手势滑动back回上三个分界面,还大概有一种情形是直接点击了back开关回到上三个界面包车型地铁。依据这二种境况我们独家做一下处理。

只是那样一来你在UINavigationController种类下切换分界面时由于尚未了动漫片,那边的效果与利益又会变得非常糟糕。那五个矛盾还没有想到能够疏通的一手,除非在业务上就不展现Tabbar了,但始终不是长久之计。

总结

这是团结的叁遍尝试导航栏掩瞒的总计,记录下踩过的坑,最终发个写的可比完全且相比较牛的方案的链接

相比较不错的导航栏透明

3.1、手势滑动back

这种场馆咱们需求去监听导航调节器的手势滑动,导航调整器有个方式'_updateInteractiveTransition:',该办法能够监听手势滑动以至当前转场的速度,大家得以因而swizzing来调换方法完成,来接班'_updateInteractiveTransition:'艺术调用的监听

  (void)load{
    //交换导航控制器的手势进度转场方法,来监听手势滑动的进度
    SEL originalSEL =  NSSelectorFromString(@"_updateInteractiveTransition:");
    SEL swizzledSEL =  NSSelectorFromString(@"xa_updateInteractiveTransition:");
    Method originalMethod = class_getInstanceMethod(self,  originalSEL);
    Method swizzledMethod = class_getInstanceMethod(self,  swizzledSEL);
    BOOL success = class_addMethod(self, originalSEL, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
    if(success){
        class_replaceMethod(self, swizzledSEL, method_getImplementation(originalMethod),  method_getTypeEncoding(originalMethod));
    }else{
        method_exchangeImplementations(originalMethod, swizzledMethod);
    }
}

接下来经过转场的上下文音讯,获得源和目标的调整器navBarAlpha值,再依据percentComplete转场进度参数总计并安装导航栏发光度值,这样就大功告成了手势滑动的back

- (void)xa_updateInteractiveTransition:(CGFloat)percentComplete{
    [self xa_updateInteractiveTransition:percentComplete];
    UIViewController *topVC = self.topViewController;
    if(topVC){
        //通过transitionCoordinator拿到转场的两个控制器上下文信息
        id <UIViewControllerTransitionCoordinator> coordinator =  topVC.transitionCoordinator;
        if(coordinator != nil){
            //拿到源控制器和目的控制器的透明度(每个控制器都单独保存了一份)
            CGFloat fromVCAlpha  = [coordinator viewControllerForKey:UITransitionContextFromViewControllerKey].xa_navBarAlpha;
            CGFloat toVCAlpha    = [coordinator viewControllerForKey:UITransitionContextToViewControllerKey].xa_navBarAlpha;
            //再通过源,目的控制器的导航条透明度和转场的进度(percentComplete)计算转场时导航条的透明度
            CGFloat newAlpha     = fromVCAlpha   ((toVCAlpha - fromVCAlpha ) * percentComplete);
            //这里不要直接去修改控制器navBarAlpha属性,会影响目的控制器的navBarAlpha的数值
            [self xa_changeNavBarAlpha:newAlpha];
        }
    }
}

同失常候,大家就算说QQ做的很好,但也长期以来有一部分不足,多把玩一下导航栏过渡的历程就能够发觉,假使策动从透明导航栏重临时又调控不反回了,依然停留在导航栏透明的分界面,此时导航栏即使会回到透明,但会有三个导航栏闪现一下的小缺欠。

3.2、按键点击back

当点击buttonItem back调控器的时候大家能够在'viewWillAppear:'的时候设置回当前调控器的发光度值,所以大家一致要换来'viewWillAppear:'形式的兑现,那么每当调整器要显得的时候,大家连年要将它重新载入参数回当前调控器应有的晶莹度值。

此处别的还索要做四个逻辑判别:

  • 一个是决断手势是还是不是正在滑动。假使是YES表示近期的景色是手势滑动back的图景则无需处理。
  • 其余二个逻辑是推断当前调整器是不是设置过navBarAlpha的属性值。假诺有设置过,那么每一回调整器要显得的时候都要将导航栏折射率设置成调控器储存的透明值。反之,大家给这一个调整器设置叁个暗中同意的发光度值
- (void)xa_viewWillAppear:(BOOL)animated{
    [self xa_viewWillAppear:animated];

    //当前控制器父控制器是导航控制器并且不是通过手势滑动显示的
    if([self.parentViewController isKindOfClass:[UINavigationController class]] &&
       (!self.navigationController.xa_isGrTransitioning)){
        //如果在控制器初始化的时候用户设置过导航栏的值,那么我们直接设置该导航栏应有的透明度值,没有设置过的话默认透明度给1
        if(self.xa_didSetBarAlpha){
            [self.navigationController xa_changeNavBarAlpha:self.xa_navBarAlpha];
        }else{
            self.xa_navBarAlpha = 1;
        }
    }
}

现行反革命主题素材早就讲罢了,基于这个难题,我们和睦来品尝完结一种越来越好的平整过渡效果,不自定义导航栏,直接利用系统原生的导航栏,使用Category和Runtime的本事,到达那一个成效:

4、细节优化与调治

在手势滑动的时候,假使滑动到了大要上就放手了,那么导航栏就大概自行完毕只怕裁撤重回操作了,招致剩下的导航栏的折射率将不恐怕测算,能够观望firstViewController的导航栏反射率并不是是1

图片 9

bug

对此那或多或少来讲,大家得以加上手势滑动的并行的状态,固然当前的滑行的进度中暂停,那么决断是撤废操作依然成功操作,然后再形成剩余的卡通效果。
首先我们要去监听导航调整器的'popViewControllerAnimated:'的形式调用

  (void)load{
    //交换导航控制器的popViewControllerAnimated:方法,来监听什么时候当前控制被back
    SEL popOriginalSEL =  @selector(popViewControllerAnimated:);
    SEL popSwizzledSEL =  NSSelectorFromString(@"xa_popViewControllerAnimated:");
    Method popOriginalMethod = class_getInstanceMethod(self,  popOriginalSEL);
    Method popSwizzledMethod = class_getInstanceMethod(self,  popSwizzledSEL);
    BOOL popSuccess = class_addMethod(self, popOriginalSEL, method_getImplementation(popSwizzledMethod), method_getTypeEncoding(popSwizzledMethod));
    if(popSuccess){
        class_replaceMethod(self, popSwizzledSEL, method_getImplementation(popOriginalMethod),  method_getTypeEncoding(popOriginalMethod));
    }else{
        method_exchangeImplementations(popOriginalMethod, popSwizzledMethod);
    }

}

下一场再经过转场和谐器对象监听手势滑动人机联作的转移

- (UIViewController *)xa_popViewControllerAnimated:(BOOL)animated{
    UIViewController *popVc =  [self xa_popViewControllerAnimated:animated];
    if(self.viewControllers.count <= 0){
        return popVc;
    }
    UIViewController *topVC = [self.viewControllers lastObject];
    if (topVC != nil) {
        id<UIViewControllerTransitionCoordinator> coordinator = topVC.transitionCoordinator;

        //监听手势返回的交互改变,如手势滑动过程当中松手就会回调block
        if (coordinator != nil) {
            if([[UIDevice currentDevice].systemVersion intValue]  >= 10){//适配iOS10
                [coordinator notifyWhenInteractionChangesUsingBlock:^(id<UIViewControllerTransitionCoordinatorContext> context){
                    [self dealNavBarChangeAction:context];
                }];
            }else{
                [coordinator notifyWhenInteractionEndsUsingBlock:^(id<UIViewControllerTransitionCoordinatorContext>  _Nonnull context) {
                    [self dealNavBarChangeAction:context];
                }];
            }
        }
    }
    return popVc;
}

末段大家通过转场的上下文音信依附操作(机动实现恐怕回到操作State of Qatar来获取剩余的动漫时间长度,并产生剩余的卡通片

- (void)dealNavBarChangeAction:(id<UIViewControllerTransitionCoordinatorContext>)context {
    if ([context isCancelled]) {// 取消了(还在当前页面)
        //根据剩余的进度来计算动画时长xa_changeNavBarAlpha
        CGFloat animdDuration = [context transitionDuration] * [context percentComplete];
        CGFloat fromVCAlpha   = [context viewControllerForKey:UITransitionContextFromViewControllerKey].xa_navBarAlpha;
        [UIView animateWithDuration:animdDuration animations:^{
            [self xa_changeNavBarAlpha:fromVCAlpha];
        }];

    } else {// 自动完成(pop到上一个界面了)

        CGFloat animdDuration = [context transitionDuration] * (1 -  [context percentComplete]);
        CGFloat toVCAlpha     = [context viewControllerForKey:UITransitionContextToViewControllerKey].xa_navBarAlpha;
        [UIView animateWithDuration:animdDuration animations:^{
            [self xa_changeNavBarAlpha:toVCAlpha];
        }];
    };
}

图片 1020170322193055722.gif

最后:

最火资讯