最全设置,侧滑失效难题

前段时间有个小伙伴想统一设置app导航栏返回按钮的文字,但是用的方法是[UINavigationBar appearance].backItem.title = @"返回";来问为什么没起作用,其实是概念的不清晰,想当然以为backItem是返回按钮。故从蛮久以前的笔记里整理了这篇文章( [UINavigationBar appearance].backItem其实是nil )

 

@interface UINavigationController : UIViewController@property(nonatomic,readonly) UINavigationBar *navigationBar;@interface UIViewController (UINavigationControllerItem)@property(nonatomic,readonly,strong) UINavigationItem *navigationItem;@interface UINavigationBar : UIView@property(nullable, nonatomic,readonly,strong) UINavigationItem *topItem;@property(nullable, nonatomic,readonly,strong) UINavigationItem *backItem;@property(nullable,nonatomic,copy) NSArray<UINavigationItem *> *items;@interface UINavigationItem : NSObject@property(nullable,nonatomic,strong) UIBarButtonItem *backBarButtonItem@property(nullable,nonatomic,copy) NSArray<UIBarButtonItem *> *leftBarButtonItems;@property(nullable,nonatomic,copy) NSArray<UIBarButtonItem *> *rightBarButtonItems;@property(nullable, nonatomic,strong) UIBarButtonItem *leftBarButtonItem;@property(nullable, nonatomic,strong) UIBarButtonItem *rightBarButtonItem;@interface UIBarButtonItem : UIBarItem@property(nullable, nonatomic) SEL action; @interface UIBarItem : NSObject@property(nullable, nonatomic,copy) NSString *title;@property(nullable, nonatomic,strong) UIImage *image;

  侧滑返回手势是从iOS7开始增加的一个返回操作,经历了两年时间估计iPhone用户大部分都已经忽略了屏幕左上角那个碍眼的back按钮了。之前在网上搜过有关侧滑手势的技术博客,发现大多比较散乱,甚至有很多都是简单的粘贴复制,并不全面。侧滑返回的操作效果与左上角的back按钮是一样的,所以一起放在这里进行探讨。  

图片 162F192F1-9F82-48FE-9783-312E35E74D01.png

    // 导航栏背景色

通过对上述几个类的属性的罗列,我们可以做个总结

  导航栏左上角的back按钮是附着在UINavigationController的UINavigationBar里自带的一个返回按钮,导航栏自带的back按钮的图层结构如下图所示。一个UINavigationController只会有一个UIBackButtonContentView,但是可以有多个leftBarButtonItem、rightBarButtonItem(leftBarButtonItem、rightBarButtonItem就在下图所示的UIButtonBarStackView图层下),其中backButton与leftBarButtonItem之间的关系和区别在后面我们会讲到。

UINavigationBar管理栈上所有控制器的navigationItem(UINavigationItem),保存在items属性中,topItem是栈顶的item,即对当前页面起作用item(当前控制器中获取self.navigationItem)

    self.navigationController.navigationBar.barTintColor = [UIColor orangeColor];

基本介绍
  • UIBarItem一个可以放置在Bar之上的所有小控件类的抽象类,可以设置标题,图片等
  • UIBarButtonItem继承UIBarItem,增加了动作以及目标等button的属性。相当于放在UIToolBar或者UINavigationBar上的特殊的button。
  • UINavigationItem包含了title,prompt,titleView,leftBarButtonItem,rightBarButtonItem,backBarButonItem等当前页面上所有的信息
  • UINavigationBarNavigaitonBar就是导航栏 主要对UINavigationItem进行栈管理 展示导航栏的外观背景
  • UINavigationController包含了viewcontrollers、navigationbar、toolbar

图片 2

例:从A控制器push到了B控制器B.navigationItem == B.navigationController.navigationBar.topItembackItem是前一个控制器的navigationItem即:A.navigationItem == B.navigationController.navigationBar.backItem

    // 设置push出来的controller返回按钮的箭头颜色

关系综述
  • UINavigationController是一个容器类,对ViewController进行栈管理,包含navigationBar。
  • UINavigationBar 即UINavigationController顶部的导航栏,主要负责外观背景的展示,并对navigationItem进行栈管理
  • UINavigationItem是导航栏上显示的具体的元素的一个抽象类,UINavigationController 通过Category的方法为ViewController添加了一个navigationItem,把UINavigationItem交由ViewController管理

// Created on-demand so that a view controller may customize its navigation appearance.

这里引用叶落寒的一段介绍,更加的通俗易懂

通俗地说就是,UINavigationController是个容器,里面可以装很多UIViewController。装这么多UIViewController让用户怎么控制它们呢?总得有个工具吧,这个工具就是UINavigationBar。一个容器就这么一个bar,相当于控制台吧。但是管理那么多UIViewController,控制台上得按钮啊、标题啊,都千篇一律是不是看起来太无聊了。为了解决这个问题,UINavigationController为每个UIViewController生成一个UINavigationItem,通过这个UINavigationItem可以改变控制台“上面”的按钮和标题。如果你不自定义UINavigationItem,UINavigationController会使用默认的;

一 侧滑返回   

  侧滑返回是系统iOS7自带的一种方便用户进行返回操作而推出的一种新功能。在开发过程中,对侧滑返回进行控制非常简单,主要就是启动侧滑手势和禁用侧滑手势。首先,我们来看一下 UINavigationController 的 @property ,可以找到下面这个属性。

@property(nullable, nonatomic, readonly) UIGestureRecognizer *interactivePopGestureRecognizer;

UINavigationItem继承自NSObject,相当于一个模型,封装了当前导航bar上需要显示的所有信息。(包含title,titleView,backBarButtonItem(UIBarButtonItem),leftBarButtonItem(UIBarButtonItem),rightBarButtonItem(UIBarButtonItem)等属性)

    [self.navigationController.navigationBar setTintColor:[UIColor whiteColor]];

1. UIBarButtonItem

//初始化方法- (instancetype)init;- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder;- (instancetype)initWithImage:(nullable UIImage *)image landscapeImagePhone:(nullable UIImage *)landscapeImagePhone style:(UIBarButtonItemStyle)style target:(nullable id)target action:(nullable SEL)action;- (instancetype)initWithTitle:(nullable NSString *)title style:(UIBarButtonItemStyle)style target:(nullable id)target action:(nullable SEL)action;- (instancetype)initWithBarButtonSystemItem:(UIBarButtonSystemItem)systemItem target:(nullable id)target action:(nullable SEL)action;- (instancetype)initWithCustomView:customView;@property(nonatomic) UIBarButtonItemStyle style; //类型@property(nonatomic) CGFloat width;@property(nullable, nonatomic,copy) NSSet<NSString *> *possibleTitles;@property(nullable, nonatomic,strong) __kindof UIView *customView;@property(nullable, nonatomic) SEL action;@property(nullable, nonatomic,weak) id target;@property(nullable, nonatomic,strong) UIColor *tintColor//为任意style的button设置背景图片- setBackgroundImage:(nullable UIImage *)backgroundImage forState:(UIControlState)state barMetrics:(UIBarMetrics)barMetrics- (nullable UIImage *)backgroundImageForState:(UIControlState)state barMetrics:(UIBarMetrics)barMetrics//为特定style的button设置背景图片- setBackgroundImage:(nullable UIImage *)backgroundImage forState:(UIControlState)state style:(UIBarButtonItemStyle)style barMetrics:(UIBarMetrics)barMetrics- (nullable UIImage *)backgroundImageForState:(UIControlState)state style:(UIBarButtonItemStyle)style barMetrics:(UIBarMetrics)barMetrics//设置背景图片垂直方向的偏移量- setBackgroundVerticalPositionAdjustment:adjustment forBarMetrics:(UIBarMetrics)barMetrics- backgroundVerticalPositionAdjustmentForBarMetrics:(UIBarMetrics)barMetrics//设置标题的偏移量- setTitlePositionAdjustment:adjustment forBarMetrics:(UIBarMetrics)barMetrics- (nullable UIImage *)backButtonBackgroundImageForState:(UIControlState)state barMetrics:(UIBarMetrics)barMetrics//设置返回按钮标题偏移量- setBackButtonTitlePositionAdjustment:adjustment forBarMetrics:(UIBarMetrics)barMetrics- backButtonTitlePositionAdjustmentForBarMetrics:(UIBarMetrics)barMetrics//设置返回按钮背景图片在垂直方向上的偏移量- setBackButtonBackgroundVerticalPositionAdjustment:adjustment forBarMetrics:(UIBarMetrics)barMetrics- backButtonBackgroundVerticalPositionAdjustmentForBarMetrics:(UIBarMetrics)barMetrics

typedef NS_ENUM(NSInteger, UIBarButtonSystemItem) { UIBarButtonSystemItemDone,//显示完成 UIBarButtonSystemItemCancel,//显示取消 UIBarButtonSystemItemEdit, //显示编辑 UIBarButtonSystemItemSave, //显示保存 UIBarButtonSystemItemAdd,//显示加号 UIBarButtonSystemItemFlexibleSpace,//什么都不显示,占位一个空间位置 UIBarButtonSystemItemFixedSpace,//和上一个类似 UIBarButtonSystemItemCompose,//显示写入按钮 UIBarButtonSystemItemReply,//显示循环按钮 UIBarButtonSystemItemAction,//显示活动按钮 UIBarButtonSystemItemOrganize,//显示组合按钮 UIBarButtonSystemItemBookmarks,//显示图书按钮 UIBarButtonSystemItemSearch,//显示查找按钮 UIBarButtonSystemItemRefresh,//显示刷新按钮 UIBarButtonSystemItemStop,//显示停止按钮 UIBarButtonSystemItemCamera,//显示相机按钮 UIBarButtonSystemItemTrash,//显示移除按钮 UIBarButtonSystemItemPlay,//显示播放按钮 UIBarButtonSystemItemPause,//显示暂停按钮 UIBarButtonSystemItemRewind,//显示退后按钮 UIBarButtonSystemItemFastForward,//显示前进按钮 UIBarButtonSystemItemUndo,//显示消除按钮 UIBarButtonSystemItemRedo ,//显示重做按钮 UIBarButtonSystemItemPageCurl ,//在tool上有效};

1.1 侧滑开启与关闭

  UINavigationController的interactivePopGestureRecognizer这个属性就是我们的侧滑返回手势,如果你的项目中没有需求要自定义返回按钮(虽然我觉得这并不太可能),那么你所需要的操作就非常简单了,不多说直接上代码。

self.navigationController.interactivePopGestureRecognizer.enabled = YES;  //启用侧滑手势
self.navigationController.interactivePopGestureRecognizer.enabled = NO;   //禁用侧滑手势 

UIBarButtonItem是放在bar上的特殊的button,能处理点击事件

    

2.UINavigationItem

NS_CLASS_AVAILABLE_IOS @interface UINavigationItem : NSObject <NSCoding>//初始化- (instancetype)initWithTitle:(NSString *)title NS_DESIGNATED_INITIALIZER;- (nullable instancetype)initWithCoder:(NSCoder *)coder NS_DESIGNATED_INITIALIZER;//设置导航栏中间的内容标题@property(nullable, nonatomic,copy) NSString *title; //设置导航栏中间的内容视图 @property(nullable, nonatomic,strong) UIView *titleView; //提示@property(nullable,nonatomic,copy) NSString *prompt; //返回@property(nullable,nonatomic,strong) UIBarButtonItem *backBarButtonItem; //是否隐藏返回Button@property(nonatomic,assign) BOOL hidesBackButton;- setHidesBackButton:hidesBackButton animated:animated;//左边数组Item@property(nullable,nonatomic,copy) NSArray<UIBarButtonItem *> *leftBarButtonItems NS_AVAILABLE_IOS;//右边数组Item@property(nullable,nonatomic,copy) NSArray<UIBarButtonItem *> *rightBarButtonItems NS_AVAILABLE_IOS;- setLeftBarButtonItems:(nullable NSArray<UIBarButtonItem *> *)items animated:animated NS_AVAILABLE_IOS;- setRightBarButtonItems:(nullable NSArray<UIBarButtonItem *> *)items animated:animated NS_AVAILABLE_IOS;//通过指定该属性为YES,可以让leftBarButtonItem和backBarButtonItem同时显示,其中leftBarButtonItem显示在backBarButtonItem的右边 默认值为NO@property(nonatomic) BOOL leftItemsSupplementBackButton NS_AVAILABLE_IOS;//左边Item@property(nullable, nonatomic,strong) UIBarButtonItem *leftBarButtonItem;//右边Item@property(nullable, nonatomic,strong) UIBarButtonItem *rightBarButtonItem;- setLeftBarButtonItem:(nullable UIBarButtonItem *)item animated:animated;- setRightBarButtonItem:(nullable UIBarButtonItem *)item animated:animated;@end

prompt 是一个NSString类型描述,注意添加该描述以后NavigationBar的高度会增加30,总的高度会变成74(不管当前方向是Portrait还是Landscape,此模式下navgationbar都使用高度44加上prompt30的方式进行显示)。

如:

self.navigationItem.prompt=@"这是什么?";self.title=@"HAH";

图片 3prompt.png

1.2 侧滑使用注意

侧滑手势在使用中需要注意的一点就是在项目开发中,我们一般是采用的UITabBar  UINavigationController架构,对于每一个UITabBar的item模块,我们都定义一个UINavigationController对该item模块上的viewController进行控制。而在这个模块上,我们有某个或某些viewController需要禁用侧滑手势(一般需要禁用侧滑手势是因为返回或退出当前viewController时需要double confirm,在一些填表的页面比较常见),而其他的viewController则不需要禁用侧滑手势。这时候我们就需要特别小心,因为 self.navigationController.interactivePopGestureRecognizer.enabled = NO; //禁用侧滑手势 是对当前的UINavigationController有效的,所以一旦你在某个界面禁用了侧滑,那么该UINavigationController控制下的所有viewController都会禁用侧滑,这显然是不合理的。提供一个解决方案就是在进入viewController时 - (void)viewDidAppear:(BOOL)animated; 中禁用侧滑手势,然后在离开viewController时 - (void)viewWillDisappear:(BOOL)animated; 开启策划手势。具体代码如下:

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
    // 禁用返回手势
    if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
     self.navigationController.interactivePopGestureRecognizer.enabled = NO;
    }
}

- (void)viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];
    // 开启返回手势
    if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
     self.navigationController.interactivePopGestureRecognizer.enabled = YES;
    }
}

UINavigationItem对象可设置多个UIBarButtonItem对象,例控制器中设置 :self.navigationItem.leftBarButtonItem;self.navigationItem.rightBarButtonItem;

    // 设置push出来的controller返回按钮字体及颜色

3.UINavigationBar

NS_CLASS_AVAILABLE_IOS @interface UINavigationBar : UIView <NSCoding, UIBarPositioning> //UIBarStyleDefault 灰色背景 白色文字 UIBarStyleBlack 纯黑色背景 白色文字@property(nonatomic,assign) UIBarStyle barStyle;@property(nullable,nonatomic,weak) id<UINavigationBarDelegate> delegate;//Translucent设置成透明度,设置成YES会有一种模糊效果@property(nonatomic,assign,getter=isTranslucent) BOOL translucent NS_AVAILABLE_IOS UI_APPEARANCE_SELECTOR; //UINavigationBar上面不只是简单的显示标题,它也将标题进行了堆栈的管理,每一个标题抽象为的对象在iOS系统中是UINavigationItem对象,我们可以通过push与pop操作管理item组。//向栈中添加一个item,上一个item会被推向导航栏的左侧,变为pop按钮,会有一个动画效果- pushNavigationItem:(UINavigationItem *)item animated:animated;//pop一个item- (nullable UINavigationItem *)popNavigationItemAnimated:animated; //当前push到最上层的item@property(nullable, nonatomic,readonly,strong) UINavigationItem *topItem;//仅次于最上层的item,一般式被推向导航栏左侧的item@property(nullable, nonatomic,readonly,strong) UINavigationItem *backItem;//获取堆栈中所有item的数组@property(nullable,nonatomic,copy) NSArray<UINavigationItem *> *items;//设置一组item- setItems:(nullable NSArray<UINavigationItem *> *)items animated:animated; //系统类型按钮文字颜色@property(null_resettable, nonatomic,strong) UIColor *tintColor;//通过barTintColor来设置背景色@property(nullable, nonatomic,strong) UIColor *barTintColor NS_AVAILABLE_IOS UI_APPEARANCE_SELECTOR; //设置工具栏背景和阴影图案- setBackgroundImage:(nullable UIImage *)backgroundImage forBarPosition:(UIBarPosition)barPosition barMetrics:(UIBarMetrics)barMetrics NS_AVAILABLE_IOS UI_APPEARANCE_SELECTOR;- (nullable UIImage *)backgroundImageForBarPosition:(UIBarPosition)barPosition barMetrics:(UIBarMetrics)barMetrics NS_AVAILABLE_IOS UI_APPEARANCE_SELECTOR;//通过背景图片来设置导航栏的外观- setBackgroundImage:(nullable UIImage *)backgroundImage forBarMetrics:(UIBarMetrics)barMetrics NS_AVAILABLE_IOS UI_APPEARANCE_SELECTOR;- (nullable UIImage *)backgroundImageForBarMetrics:(UIBarMetrics)barMetrics NS_AVAILABLE_IOS UI_APPEARANCE_SELECTOR;//背景阴影图片 - 即分割线@property(nullable, nonatomic,strong) UIImage *shadowImage NS_AVAILABLE_IOS UI_APPEARANCE_SELECTOR;//标题的富文本@property(nullable,nonatomic,copy) NSDictionary<NSString *,id> *titleTextAttributes NS_AVAILABLE_IOS UI_APPEARANCE_SELECTOR;//标题垂直偏移- setTitleVerticalPositionAdjustment:adjustment forBarMetrics:(UIBarMetrics)barMetrics NS_AVAILABLE_IOS UI_APPEARANCE_SELECTOR;- titleVerticalPositionAdjustmentForBarMetrics:(UIBarMetrics)barMetrics NS_AVAILABLE_IOS UI_APPEARANCE_SELECTOR;//设置返回按钮的图片@property(nullable,nonatomic,strong) UIImage *backIndicatorImage NS_AVAILABLE_IOS UI_APPEARANCE_SELECTOR;@property(nullable,nonatomic,strong) UIImage *backIndicatorTransitionMaskImage NS_AVAILABLE_IOS UI_APPEARANCE_SELECTOR;@end

1.3 侧滑手势的获取

  如果一个页面上有多个手势,我们要如何去获取策划手势,并对其进行操作呢?其实很简单,侧滑手势是一种UIScreenEdgePanGestureRecognizer,所以我们只需要对当前手势的类别进行判断就可以了。具体代码如下:

//获取侧滑返回手势
- (UIScreenEdgePanGestureRecognizer *)screenEdgePanGestureRecognizer
{
    UIScreenEdgePanGestureRecognizer *screenEdgePanGestureRecognizer = nil;
    if (self.view.gestureRecognizers.count > 0)
    {
        for (UIGestureRecognizer *recognizer in self.view.gestureRecognizers)
        {
            if ([recognizer isKindOfClass:[UIScreenEdgePanGestureRecognizer class]])
            {
                screenEdgePanGestureRecognizer = (UIScreenEdgePanGestureRecognizer *)recognizer;
                break;
            }
        }
    }
    return screenEdgePanGestureRecognizer;
}

当前控制器的navigationItem的backBarButtonItem决定下一个控制器的返回按钮。即设置A.navigationItem.backBarButtonItem,实际上设置的是在B控制器中显示默认的返回按钮

    UIBarButtonItem *backItem=[[UIBarButtonItem alloc]init];

4.UINavigationController

NS_CLASS_AVAILABLE_IOS @interface UINavigationController : UIViewController//UINavigationController初始化,自定义NavigationBar,自定义toolbar- (instancetype)initWithNavigationBarClass:(nullable Class)navigationBarClass toolbarClass:(nullable Class)toolbarClass NS_AVAILABLE_IOS;//UINavigationController初始化,导航控制器的根控制器- (instancetype)initWithRootViewController:(UIViewController *)rootViewController;//压栈:将目标控制器压入栈中- pushViewController:(UIViewController *)viewController animated:animated; //弹栈:将栈顶控制器从栈中弹出- (nullable UIViewController *)popViewControllerAnimated:animated;//弹栈:弹到指定的目标控制器- (nullable NSArray<__kindof UIViewController *> *)popToViewController:(UIViewController *)viewController animated:animated; //弹栈:弹到根控制器- (nullable NSArray<__kindof UIViewController *> *)popToRootViewControllerAnimated:animated;//导航栈的栈顶视图 只读 就是某个导航栈的栈顶视图,和导航息息相关@property(nullable, nonatomic,readonly,strong) UIViewController *topViewController; //当前显示的控制器 只读 visibleViewController和哪个导航栈没有关系,只是当前显示的控制器,也就是说任意一个导航的visibleViewController所返回的值应该是一样的@property(nullable, nonatomic,readonly,strong) UIViewController *visibleViewController;//栈里的视图控制器数组@property(nonatomic,copy) NSArray<__kindof UIViewController *> *viewControllers;//替换栈中的视图控制器数组- setViewControllers:(NSArray<UIViewController *> *)viewControllers animated:animated NS_AVAILABLE_IOS; //是否隐藏导航栏@property(nonatomic,getter=isNavigationBarHidden) BOOL navigationBarHidden;//设置导航栏隐藏 是否有动画- setNavigationBarHidden:hidden animated:animated; //导航栏@property(nonatomic,readonly) UINavigationBar *navigationBar; //toolbar是否隐藏@property(nonatomic,getter=isToolbarHidden) BOOL toolbarHidden NS_AVAILABLE_IOS;//toolbar是否隐藏 是否有动画- setToolbarHidden:hidden animated:animated NS_AVAILABLE_IOS; //toolbar对象@property(null_resettable,nonatomic,readonly) UIToolbar *toolbar NS_AVAILABLE_IOS; //委托@property(nullable, nonatomic, weak) id<UINavigationControllerDelegate> delegate;//边缘侧滑返回手势@property(nullable, nonatomic, readonly) UIGestureRecognizer *interactivePopGestureRecognizer NS_AVAILABLE_IOS;//展示视图控制器- showViewController:(UIViewController *)vc sender:(nullable id)sender NS_AVAILABLE_IOS; // Interpreted as pushViewController:animated://输入键盘出现时将导航栏隐藏 IOS8特性@property (nonatomic, readwrite, assign) BOOL hidesBarsWhenKeyboardAppears NS_AVAILABLE_IOS;//滚动页面时隐藏Bar IOS8特性@property (nonatomic, readwrite, assign) BOOL hidesBarsOnSwipe NS_AVAILABLE_IOS;//获取能够隐藏navigationBar的滑动手势 只读@property (nonatomic, readonly, strong) UIPanGestureRecognizer *barHideOnSwipeGestureRecognizer NS_AVAILABLE_IOS;//当设置为true时,横向方向时隐藏NavigationBar@property (nonatomic, readwrite, assign) BOOL hidesBarsWhenVerticallyCompact NS_AVAILABLE_IOS;//当设置为true时,如果有没处理的点击手势就会隐藏和现实navigationBar @property (nonatomic, readwrite, assign) BOOL hidesBarsOnTap NS_AVAILABLE_IOS;//获取能够隐藏navigationBar的点击手势 只读@property (nonatomic, readonly, assign) UITapGestureRecognizer *barHideOnTapGestureRecognizer NS_AVAILABLE_IOS;@end

@interface UIViewController (UINavigationControllerItem)//导航栏上面用户自定义视图@property(nonatomic,readonly,strong) UINavigationItem *navigationItem; //推送时隐藏bottom@property(nonatomic) BOOL hidesBottomBarWhenPushed;//下级视图的导航控制器@property(nullable, nonatomic,readonly,strong) UINavigationController *navigationController; @end

@interface UIViewController (UINavigationControllerContextualToolbarItems)//属性设置工具条中包含的按钮@property (nullable, nonatomic, strong) NSArray<__kindof UIBarButtonItem *> *toolbarItems NS_AVAILABLE_IOS;- setToolbarItems:(nullable NSArray<UIBarButtonItem *> *)toolbarItems animated:animated NS_AVAILABLE_IOS;@end

1.4 UIScrollView与侧滑手势共存问题

  UIScrollView及其子类自带滑动手势,所以如果一个viewController钟有UIScrollView及其子类的view时,侧滑手势影响用户体验效果,此时用户将无法通过侧滑进行返回。因为侧滑返回手势事实上是由存在已久的UIPanGestureRecognizer来识别并响应的,它直接与UINavigationController的view进行绑定,因此在包含UIScrollView的viewController中存在如下关系:

UIPanGestureRecognizer          ——bind——  UIScrollView

UIScreenEdgePanGestureRecognizer ——bind——  UINavigationController.view

  滑动返回无法触发,说明UIScreenEdgePanGestureRecognizer并没有接收到手势事件,也就是说UIScreenEdgePanGestureRecognizer被UIPanGestureRecognizer屏蔽了。因此,我们为了实现侧滑返回手势,我们需要设置两种手势的共存和先后响应问题,我们可以设置UIScrollView的UIPanGestureRecognizer手势在UIScreenEdgePanGestureRecognizer失效时才识别,具体设置方法如下:

//指定滑动手势在侧滑返回手势失效后响应
[self.tableView.panGestureRecognizer requireGestureRecognizerToFail:[self.navigationController screenEdgePanGestureRecognizer]];

导航栏左侧按钮规则:

例:从A控制器 push到B控制器

  1. 如果B控制器有自定义的leftBarButtonItem,则显示这个leftBarButtonItem;
  2. 如果B控制器没有,看A控制器backBarButtonItem属性是否有自定义项,有则显示;
  3. 12都没有,则默认显示一个返回按钮,文字是A控制器的标题。
方法一:在B控制器中设置leftBarButtonItem。UIBarButtonItem *leftItem = [[UIBarButtonItem alloc]initWithTitle:@"返回" style:UIBarButtonItemStylePlain target:self action:@selector];self.navigationItem.leftBarButtonItem = leftItem;注:如果给控制器添加了leftBarButtonItem,系统侧滑返回手势会失效方法二:在A控制器中设置backBarButtonItem,实际上设置的就是B控制器上默认显示的返回按钮self.navigationItem.backBarButtonItem = [[UIBarButtonItem alloc]initWithTitle:@"返回" style:UIBarButtonItemStylePlain target:nil action:nil];**注:self.navigationItem.backBarButtonItem.title = @"返回”; 直接这样设置无效,title需要item设置到bar之前修改,而backBarButtonItem是系统内置的,所以直接改无效,而且如果没有手动设置backBarButtonItem,获取到的backBarButtonItem也是nil。方法三:利用runtime一次设置全部控制器1、建一个UIViewController分类2、在分类的 load方法里用method swizzling来hook控制器的viewDidLoad方法3、同方法二统一设置 

文字设置为空的做法也可以设置为创建title是空字符串的UIBarButtonItem网上也有做法是设置文字偏移到屏幕外:(据说会有闪屏的可能?)[[UIBarButtonItem appearance] setBackButtonTitlePositionAdjustment:UIOffsetMake forBarMetrics:UIBarMetricsDefault];

UIImage *buttonBackImg = [UIImage imageNamed:@"ic_navbar_back-black_normal”];// 可以设置图片不要渲染// buttonBackImg = [buttonBackImg imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];[[UINavigationBar appearance] setBackIndicatorImage:buttonBackImg];[[UINavigationBar appearance] setBackIndicatorTransitionMaskImage:buttonBackImg];

核心就是自定义一个导航控制器,并作为手势对象的代理和导航控制器的代理,同时处理手势冲突问题

#import “NavViewController.h"@interface NavViewController ()<UINavigationControllerDelegate,UIGestureRecognizerDelegate>@end@implementation NavViewController- viewDidLoad{ [super viewDidLoad]; // Do any additional setup after loading the view. __weak __typeof weakSelf = self; if ([self respondsToSelector:@selector(interactivePopGestureRecognizer)]) { // UIScreenEdgePanGestureRecognizer self.interactivePopGestureRecognizer.delegate = weakSelf; self.delegate = weakSelf; }}- pushViewController:(UIViewController *)viewController animated:animated{ if ([self respondsToSelector:@selector(interactivePopGestureRecognizer)]) self.interactivePopGestureRecognizer.enabled = NO; [super pushViewController:viewController animated:animated];}#pragma mark UINavigationControllerDelegate- navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:animate{ // Enable the gesture again once the new controller is shown if ([self respondsToSelector:@selector(interactivePopGestureRecognizer)]) self.interactivePopGestureRecognizer.enabled = YES;}#pragma mark UINavigationControllerDelegate- gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer { if ([self.childViewControllers count] == 1) { return NO; } return YES;}// ViewController接受多个手势,再处理手势冲突问题。- gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{ return YES;}// 处理手指在滑动的时候,被pop的 ViewController 中的 UIScrollView 会跟着一起滚动问题- gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{ return [gestureRecognizer isKindOfClass:UIScreenEdgePanGestureRecognizer.class];}@end

// 底部阴影[[UINavigationBar appearance] setShadowImage:[[UIImage alloc] init]];// 背景图片[[UINavigationBar appearance] setBackgroundImage:[[UIImage alloc] init] forBarMetrics:UIBarMetricsDefault];//bar背景颜色[[UINavigationBar appearance] setBarTintColor:[UIColor clearColor]];// 文字颜色[[UINavigationBar appearance] setTintColor:[UIColor whiteColor]];// 标题位置偏移[[UINavigationBar appearance] setTitleVerticalPositionAdjustment:10 forBarMetrics:UIBarMetricsDefault];// 标题字体NSShadow *shadow = [[NSShadow alloc] init];shadow.shadowColor = [UIColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:0.8];shadow.shadowOffset = CGSizeMake;[[UINavigationBar appearance] setTitleTextAttributes: [NSDictionary dictionaryWithObjectsAndKeys: [UIFont systemFontOfSize:16], NSFontAttributeName, shadow,NSShadowAttributeName, nil]]; // 返回图标[UINavigationBar appearance].backIndicatorImage =[UINavigationBar appearance].backIndicatorTransitionMaskImage =[[UIImage imageNamed:@"ic_navbar_back-black_normal"] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];

    backItem.title=@"BBBACK";

1.UINavigationBar的背景颜色

-changeNavigationBarBackgroundColor { //背景色 self.navigationBar.barTintColor = [UIColor blueColor]; //title字体 //[self.navigationController.navigationBar setTitleTextAttributes:@{NSForegroundColorAttributeName:[UIColor lightGrayColor],NSFontAttributeName:[UIFont systemFontOfSize:17]}]; //修改UIBarButtonItem 图片 title颜色 self.navigationBar.tintColor = [UIColor greenColor]; //是否半透明 当为YES时 设置的导航栏背景颜色会和实际rgb值有误差 self.navigationBar.translucent = NO; //如果想要半透明效果 颜色没有色差 可以通过设置背景图片的方法 背景图片会覆盖barTintColor //- setBackgroundImage:(nullable UIImage *)backgroundImage forBarMetrics:(UIBarMetrics)barMetrics}

 二 导航栏的back按钮

  在了解导航栏的返回按钮之前,我们先了解一下导航栏管理导航栏上各类控件的UINavigationBar。首先,我们先来看一看官方文档怎么介绍UINavigationBar,A UINavigationBar object is a bar, typically displayed at the top of the window, containing buttons for navigating within a hierarchy of screens. The primary components are a left (back) button, a center title, and an optional right button. You can use a navigation bar as a standalone object or in conjunction with a navigation controller object.组成如下图左边所示。最重要的一部分我用蓝色加粗标出来了,就是说这个UINavigationBar主要是由左右按钮控件、中间标题控件组成。原生的导航条上的返回(back)按钮,一般是显示一个返回箭头 上一页面的标题(或者是 返回箭头 Back),如下图右边所示。

图片 4

    [backItem setTitleTextAttributes:@{NSForegroundColorAttributeName:[UIColor blueColor]} forState:UIControlStateNormal];

2.UINavigationBar底部的shadowImage

Apple官方对shadowImage有这样的介绍:

/* Default is nil. When non-nil, a custom shadow image to show instead of the default shadow image. For a custom shadow to be shown, a custom background image must also be set with -setBackgroundImage:forBarMetrics: (if the default background image is used, the default shadow image will be used).*/

设置shadowImage必须先setBackgroundImage,否则无法实现效果

-changeNavigationBarBottonLine { //设置底部line颜色时需要同时设置backgroundImage即导航栏的背景图片 否则没有效果 [self.navigationBar setBackgroundImage:[UIImage new] forBarMetrics:UIBarMetricsDefault]; [self.navigationBar setShadowImage:[self imageWithColor:[UIColor redColor]]]; //此处设置透明颜色的image,底部line即可隐藏,但此种方法隐藏,没有办法再显示 下面方法通过找到该view 控制其hidden属性 //[self reducibilityHiddenNavogationBarLine];}

找到该imageView设置起Hidden为YES

-reducibilityHiddenNavogationBarLine { UIImageView * imageView = [self findLineImageViewUnder:self.navigationBar]; if (imageView) { imageView.hidden = YES; }}

找到该imageView

-(UIImageView *)findLineImageViewUnder:view { if ([view isKindOfClass:[UIImageView class]] && view.bounds.size.height <= 1.0) { return (UIImageView *)view; } for (UIView * subView in view.subviews) { UIImageView * imageView = [self findLineImageViewUnder:subView]; if (imageView) { return imageView; } } return nil;}

2.1 导航条上的按钮三兄弟

  在前面我们也提到了,在导航栏上有左右按钮和返回按钮,官方称谓是backBarButtonItem、leftBarButtonItem、rightBarButtonItem。他们都属于UINavigationItem的组成部分,都显示在navigationBar上,都属于UIBarButtonItem类,所以我给他们取名为导航条上的按钮三兄弟,哈哈哈。。。

  首先,我们来说一下leftBarButtonItem、rightBarButtonItem,这两个是孪生兄弟,唯一的区别就是在导航条上的位置,顾名思义,leftBarButtonItem在导航条左侧,rightBarButtonItem在导航条右侧。此外,还有一点需要我们注意的是navigationBar上的leftBarButtonItem、rightBarButtonItem可以有多个,用法也非常简单,常见用法就是一般在 - (void)viewDidLoad  中添加按钮,然后添加按钮的点击功能即可。

//添加取消btn
UIBarButtonItem *cancelBtn = [[UIBarButtonItem alloc] initWithTitle:@"取消" style:UIBarButtonItemStylePlain target:self action:@selector(navBtnPress:)] ;
cancelBtn.tag = 1000 ;
self.navigationItem.leftBarButtonItem = cancelBtn ;
//添加完成btn
UIBarButtonItem *createBtn = [[UIBarButtonItem alloc] initWithTitle:@"完成" style:UIBarButtonItemStylePlain target:self action:@selector(navBtnPress:)] ;
createBtn.enabled = NO ;        //刚开始设置为不可选
createBtn.tag = 1001 ;
self.navigationItem.rightBarButtonItem = createBtn ;

/**
 导航栏 取消 完成 按钮的操作
 @param sender <#sender description#>
 */
- (void) navBtnPress:(UIButton *)sender{
    if (sender.tag == 1000) {
        //取消
         NSLog(@"cancel"); 
    } else {
        //完成
        NSLog(@"create");
    }
}

    self.navigationItem.backBarButtonItem = backItem;

3.自定义导航栏的返回按钮

@property(nonatomic,readonly) UINavigationBar *navigationBar;// The navigation bar managed by the controller. Pushing, popping or setting navigation items on a managed navigation bar is not supported.

受controller管理的navigationBar 不支持对navigation items操作,所以此处对返回按钮的操作是在ViewController完成的,以下代码中self表示ViewController

Apple官方对setBackIndicatorImage和setBackIndicatorTransitionMaskImage做了如下解释,必须同时设置才能生效

/* The back indicator image is shown beside the back button. The back indicator transition mask image is used as a mask for content during push and pop transitions Note: These properties must both be set if you want to customize the back indicator image. */

1.自定义文字 图片

-createCustomBackBarItem { //修改图片文字颜色 self.navigationController.navigationBar.tintColor = [UIColor whiteColor]; //替换图片 [self.navigationController.navigationBar setBackIndicatorImage:[UIImage imageNamed:@"erwema"]]; [self.navigationController.navigationBar setBackIndicatorTransitionMaskImage:[UIImage imageNamed:@"erwema"]]; //设置文字 UIBarButtonItem * backBarItem = [[UIBarButtonItem alloc]initWithTitle:@"返回" style:UIBarButtonItemStylePlain target:nil action:nil]; self.navigationItem.backBarButtonItem = backBarItem;}

注意:对backBarButtonItem的修改是在当前viewController前一个页面完成的,在当前页面修改针对下一个viewController的navigationItem生效

2.不显示文字设置Title在Y方向上的偏移量,使其移除屏幕,该方法在第一次进入时会有个文字移动的动画效果,效果不好,不推荐使用

[[UIBarButtonItem appearance] setBackButtonTitlePositionAdjustment:UIOffsetMake forBarMetrics:UIBarMetricsDefault];

3.使用leftBarButtonItem替代backBarButtonItem使用这种方法,不能使用边缘滑动返回手势,且不能同时设置图片和标题

-setLeftBarItemBack{ UIBarButtonItem *leftBarBtnItem = [[UIBarButtonItem alloc]initWithImage:[UIImage imageNamed:@"back"] style:UIBarButtonItemStylePlain target:self action:@selector(clickLeftBarBtnItem:)]; [self.navigationItem setLeftBarButtonItem:leftBarBtnItem animated:YES]; self.navigationItem.leftBarButtonItem.tintColor = NavigationLeftBackColor;}/** * 导航条leftBarBtn事件 */- clickLeftBarBtnItem:(UIBarButtonItem *)sender { [self.navigationController popViewControllerAnimated:YES];}

4.使用CustomView的方法此方法不适于backBarButtonItem,只能用于leftBarButtonItem

注意:1.如果B视图有一个自定义的左侧按钮(leftBarButtonItem),则会显示这个自定义按钮;2.如果B没有自定义按钮,但是A视图的backBarButtonItem属性有自定义项,则显示这个自定义项;3.如果前2条都没有,则默认显示一个后退按钮,后退按钮的标题是A视图的标题;

此处注意:5.0中新增加了一个属性leftItemsSupplementBackButton,通过指定该属性为YES,可以让leftBarButtonItem和backBarButtonItem同时显示,其中leftBarButtonItem显示在backBarButtonItem的右边。

使用3、4方法,边缘返回会失效,此时加上这句代码依然可以实现边缘滑动返回

 self.navigationController.interactivePopGestureRecognizer.delegate = self;

这里推荐使用FDFullscreenPopGesture 全屏滑动手势返回,减少工作量。

2.2 导航条上的backBarButtonItem

  同样的,我们首先从官方文档了解一下backBarButtonItem的描述:When this navigation item is immediately below the top item in the stack, the navigation controller derives the back button for the navigation bar from this navigation item. When this property is nil, the navigation item uses the value in its title property to create an appropriate back button. If you want to specify a custom image or title for the back button, you can assign a custom bar button item (with your custom title or image) to this property instead. When configuring your bar button item, do not assign a custom view to it; the navigation item ignores custom views in the back bar button anyway.说明了backBarButtonItem只能自定义image和title,不能重写target or action,系统会忽略其他的相关设置项。

  Note: If the title of your back button is too long to fit in the available space on the navigation bar, the navigation bar may substitute the string “Back” in place of the button’s original title. The navigation bar does this only if the back button is provided by the previous view controller. If the new top-level view controller has a custom left bar button item—an object in the leftBarButtonItems or leftBarButtonItem property of its navigation item—the navigation bar does not change the button title.这段描述了关于backBarButtonItem的一些特殊点,如果你上一级设置的backBarButtonItem的标题过长(没有设置则默认是上一级标题),那么系统可能会自动用“Back/返回”来代替返回按钮中的标题。此外,如果是自定义的左按钮,则系统不会修改其值。

    

4.navigationBar偶尔显示上一个页面的navigationBar

一般情况下都是正常的。但是在偶然情况下,会出现在进入新界面后,新界面的navigationBar会突然消失,出现的还是上一个界面的 navigationBar。从此以后,navigationBar 全乱了, kill 掉重新进,恢复正常。原因:一般我们会打点调用navigationBarHidden的属性来设置导航栏是否隐藏,这种方法是不带动画效果的。这样偶尔就会导致错乱,这应该是一个系统的bug,所以应尽量使用

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

来设置navigationBar的Hidden属性。

2.3 backBarButtonItem和leftBarButtonItem的区别

  • backBarButtonItem和另外两兄弟是有区别的,比如当前有AController准备push到BController,设置backBarButtonItem的title和image需要在AController内设置,在调用AController Push:B之前进行设置, AController.navigationItem.backBarButtonItem = ....  ,其他两兄弟则是在BController的ViewDidload后设置均可。所以,如果我们一定需要重写返回键的action做一些其他的工作,则需要自定义一leftBarButtonItem,因为系统定义leftBarButtonItem的显示优先级比backBarButtonItem优先级高,当存在leftBarButtonItem时,自动忽略backBarButtonItem,达到重写backBarButtonItem的目的。
  • backBarButtonItem的自定义不会影响系统的侧滑返回手势,而leftBarButtonItem的自定义则会禁用侧滑返回手势。

    UIButton *backButton = [UIButton buttonWithType:UIButtonTypeCustom]; 
    //对按钮的个性化设定
    UIBarButtonItem *barItem = [[UIBarButtonItem alloc] initWithCustomView:backButton]; 
    self.navigationItem.leftBarButtonItem = barItem; //侧滑手势失效
    self.navigationItem.backBarButtonItem = barItem; //不影响侧滑手势
    
  • backBarButtonItem的自定义不能影响返回按钮的标题和图片,不会隐藏最左边的返回箭头backIndicatorImage,而leftBarButtonItem的自定义则会使最左边的返回箭头消失backIndicatorImage。

图片 5

    // 设置当前导航栏左右barbutton

5.修改系统navigationBar的高度

UINavigationBar * navigatioBar = self.navigationController.navigationBar;CGFloat navBarHeight = 100.f;CGRect frame = CGRectMake(0.0f, 0.0f, 320.0f, navBarHeight);[navigatioBar setFrame:frame];

此种方法经测试只有在非rootViewController中才能生效,这样在第一次进入根视图的时候navigationBar并不会变高,不知有没有人知道该如何解决,欢迎赐教。

2.4 各个对象下的backBarButtonItem的区别 

  这一部分参考自:,还有些没弄明白,后面有时间再理一理这一块。

  对于导航栏上的按钮三兄弟,我们在3个类下面都能发现他们,比如当前在一个UIViewController内,输入以下方法都能发现他们。(同leftBarButtonItem | rightBarButtonItem)

self.navigationItem.backBarButtonItem

self.navigationController.navigationItem.backBarButtonItem

self.navigationController.navigationBar.backItem.backBarButtonItem

比如在AController->BController,在A设置了self.navigationItem.backBarButtonItem,经过试验发现,这个backBarButtonItem为BController的self.navigationController.navigationBar.backItem.backBarButtonItem。

UIViewController的属性navigationItem正是被当前UINavigationBar--[UINavigationBar appearance]管理的属性

//The navigation item used to represent the view controller in a parent’s navigation bar. (read-only)
@property(nonatomic, readonly, retain) UINavigationItem *navigationItem;

self.navigationController.navigationItem.backBarButtonItem则是表示当前navigationController的parent的UINavigationBar,一般情况下没有这样的嵌套。

 

    UIBarButtonItem *leftBarbutton = [[UIBarButtonItem alloc] initWithTitle:@"左按钮" style:UIBarButtonItemStylePlain target:self action:@selector(action:)];

6.易混淆知识点

一目了然

self.navigationItem.title = @"my title"; //sets navigation bar title.self.tabBarItem.title = @"my title"; //sets tab bar title.self.title = @"my title"; //sets both of these.
  1. 如果当前VC通过 self.navigationItem.titleView指定了自定义的titleView,系统将会显示指定的titleView,设置self.title、self.navigationItem.title不会改变导航栏的标题。
  1. 如果当前VC没有指定titleView,系统则会根据当前VC的title或者当前VC的navigationItem.title的内容创建一个UILabel并显示。
  2. self.title会重写navigationItem和tabBarItem的title。

navigationItem是UIViewController的一个属性,navigationController继承UIViewController,自然会继承viewControoler的navigationItem属性。此处self.navigationController.navigationItem是应该被忽视的。navigationItem直接由viewController管理。

typedef NS_ENUM(NSInteger, UIBarMetrics) { UIBarMetricsDefault, //横屏 UIBarMetricsCompact,//竖屏 UIBarMetricsDefaultPrompt = 101, //横屏且设置了prompt属性 Applicable only in bars with the prompt property, such as UINavigationBar and UISearchBar UIBarMetricsCompactPrompt, //竖屏且设置了prompt属性};typedef NS_ENUM(NSInteger, UIBarPosition) { UIBarPositionAny = 0, //Bar在任何位置 UIBarPositionBottom = 1, //Bar在底部 UIBarPositionTop = 2, //Bar在顶部 UIBarPositionTopAttached = 3, //Bar在顶部,且他的背景扩展到statusBar的区域} NS_ENUM_AVAILABLE_IOS;

不正之处,还望多多指教!

参考文章你真的了解UINavigationController吗?UINavigationItem UINavigationBar 关系分析

    [leftBarbutton setTintColor:[UIColor blackColor]];

    [leftBarbutton setImage:[UIImage imageNamed:@"icon57.png"]];

    [leftBarbutton setTag:110];

    

    UIBarButtonItem *rightBartutton = [[UIBarButtonItem alloc] initWithTitle:@"右按钮" style:UIBarButtonItemStylePlain target:self action:@selector(action:)];

    [rightBartutton setTag:111];

    [rightBartutton setTintColor:[UIColor whiteColor]];

 

    self.navigationItem.rightBarButtonItem= rightBartutton;

    self.navigationItem.leftBarButtonItem = leftBarbutton;

    

    // 导航栏文字颜色

    //UIView *navView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 50, 30)];

    //[navView setBackgroundColor:[UIColor whiteColor]];

    //[self.navigationItem setTitleView:navView];

    [self.navigationItem setTitle:@"主页"];

    [self.navigationController.navigationBar setTitleTextAttributes:@{NSForegroundColorAttributeName:[UIColor redColor]}];

   

    // 视图坐标从导航栏高度开始

    [self.navigationController.navigationBar setTranslucent:NO];

 

    // 添加一个导航栏

    UINavigationBar *navigationBar = [[UINavigationBar alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 70)];

    UINavigationItem *navigationItem = [[UINavigationItem alloc] initWithTitle:@"首页"];

    [navigationBar pushNavigationItem:navigationItem animated:NO];

    UIBarButtonItem *pushBarButton = [[UIBarButtonItem alloc] initWithTitle:@"左按钮" style:UIBarButtonItemStylePlain target:self action:@selector(push:)];

    [pushBarButton setTintColor:[UIColor blackColor]];

    [pushBarButton setImage:[UIImage imageNamed:@"icon57.png"]];

    navigationItem.rightBarButtonItem = pushBarButton;

    [self.view addSubview:navigationBar];

    // 设置其字体颜色和背景色

    [[UINavigationBar appearance] setTitleTextAttributes:@{NSForegroundColorAttributeName:[UIColor redColor]}];

    [[UINavigationBar appearance] setBarTintColor:[UIColor whiteColor]];

本文由星彩网app下载发布于计算机编程,转载请注明出处:最全设置,侧滑失效难题

TAG标签: 星彩网app下载
Ctrl+D 将本页面保存为书签,全面了解最新资讯,方便快捷。