UINavigationController学习笔记

2020-02-18  本文已影响0人  寻心_0a46

UINavigationController

UINavigationController导航控制器,派生自UIViewController,是一种容器视图控制器,它定义了一种基于堆栈的方案,用于导航分层内容。

导航控制器是在导航界面中管理一个或多个子视图控制器的容器视图控制器。在这种类型的接口中,一次只能看到一个子视图控制器。在视图控制器中选择一个项目会使用动画在屏幕上推送一个新的视图控制器,从而隐藏以前的视图控制器。点击界面顶部导航栏中的“后退”按钮,将删除顶部控制器,从而显示下面的视图控制器。

导航控制器对象使用称为导航堆栈的有序数组管理其子视图控制器。数组中的第一个视图控制器是根视图控制器,表示堆栈的底部。数组中的最后一个视图控制器是堆栈中最上面的项,表示当前显示的视图控制器。可以使用segues或此类的方法从堆栈中添加和移除视图控制器。用户还可以使用导航栏中的“后退”按钮或使用左边缘滑动手势删除最顶部的视图控制器。

常用属性

@property(nullable, nonatomic,readonly,strong) UINavigationController *navigationController;

属性描述 :视图控制器层次结构中最近的父控制器,即导航控制器。如果视图控制器或其父控制器之一是导航控制器的子控制器,则此属性包含所属的导航控制器。如果视图控制器未嵌入导航控制器,则此属性为nil。

@property(nullable, nonatomic,readonly,strong) UINavigationController *navigationController;

@property(nonatomic,readonly,strong) UINavigationItem *navigationItem;

属性描述 : 用于表示父级导航栏中的视图控制器的导航项。这是UINavigationItem的唯一实例,创建该实例是为了在视图控制器被推送到导航控制器上时表示视图控制器。第一次访问属性时,将创建UINavigationItem对象。因此,如果不使用导航控制器来显示视图控制器,则不应访问此属性。为了确保导航项已配置,您可以重写此属性并添加代码以在首次访问时创建条形按钮项,或者在视图控制器的初始化代码中创建项。

避免在导航项中创建条形按钮项与创建视图控制器的视图相关联。视图控制器的导航项可以独立于视图控制器的视图进行检索。例如,当将两个视图控制器推到导航堆栈上时,最上面的视图控制器变为可见,但是可以检索另一个视图控制器的导航项以显示其后退按钮。

默认行为是创建一个导航项,显示视图控制器的标题。

@property(nonatomic,readonly,strong) UINavigationItem *navigationItem;

@property(nonatomic,readonly) UINavigationBar *navigationBar;

属性描述 : 导航控制器管理的导航栏。允许使用UINavigationBar类的方法和属性自定义导航栏的外观,但决不能更改其frame、bounds或alpha值或直接修改其视图层次结构。要显示或隐藏导航栏,应始终通过导航控制器更改其navigationBarHidden属性或调用setNavigationBarHidden:animated:方法来显示或隐藏导航栏。

@property(nonatomic,readonly) UINavigationBar *navigationBar; 

@property(nonatomic) BOOL hidesBottomBarWhenPushed API_UNAVAILABLE(tvOS);

属性描述 : 一个布尔值,指示将视图控制器推到导航控制器上时是否隐藏屏幕底部的工具栏。作为导航控制器的子级添加的视图控制器可以在屏幕底部显示可选工具栏。最顶部视图控制器上此属性的值决定工具栏是否可见。如果此属性的值为“YES”,则工具栏将隐藏。如果此属性的值为“NO”,则工具栏可见。

@property(nonatomic) BOOL hidesBottomBarWhenPushed API_UNAVAILABLE(tvOS);

@property(nonatomic,copy) NSArray<__kindof UIViewController *> *viewControllers;

属性描述 : 当前在导航堆栈上的视图控制器。根视图控制器位于数组的索引0处,要返回的视图控制器位于索引n-2的位置,而顶部控制器位于索引n-1的位置,其中n是数组中的项数。为该属性分配新的视图控制器数组等效于调用将动画参数设置为NO的setViewControllers:animated:方法。

@property(nonatomic,copy) NSArray<__kindof UIViewController *> *viewControllers;

例如获取当前控制器的上一个控制器:

- (UIViewController *)backViewController{
    NSInteger myIndex = [self.navigationController.viewControllers indexOfObject:self];
    if( myIndex !=0&& myIndex !=NSNotFound) {
        return [self.navigationController.viewControllers objectAtIndex:myIndex-1];
    }else{
        return nil;
    }
}

@property(nullable, nonatomic, readonly) UIGestureRecognizer *interactivePopGestureRecognizer API_AVAILABLE(ios(7.0)) API_UNAVAILABLE(tvOS);

属性描述 : 负责从导航堆栈弹出顶视图控制器的手势识别器。导航控制器在其视图上安装此手势识别器,并使用它将最上面的视图控制器弹出导航堆栈。可以使用此属性检索手势识别器,并将其与用户界面中其他手势识别器的行为关联。将手势识别器连接在一起时,确保它们同时识别它们的手势,以确保手势识别器有机会处理事件。

@property(nullable, nonatomic, readonly) UIGestureRecognizer *interactivePopGestureRecognizer API_AVAILABLE(ios(7.0)) API_UNAVAILABLE(tvOS);

常用函数

- (void)presentViewController:(UIViewController *)viewControllerToPresent animated: (BOOL)flag completion:(void (^ __nullable)(void))completion API_AVAILABLE(ios(5.0));

函数描述 : 模态地呈现一个视图控制器。在水平规则环境中,视图控制器以modalPresentationStyle属性指定的样式显示。在水平紧凑的环境中,视图控制器默认为全屏显示。如果将自适应委托与viewControllerToPresent中与对象关联的表示控制器关联,则可以动态修改表示样式。

调用此方法的对象不一定是处理表示的对象。每种表示风格都有不同的行为规则。例如,一个全屏显示必须由一个本身覆盖整个屏幕的视图控制器来完成。如果当前的视图控制器不能满足一个请求,它将请求转发到视图控制器层次结构中最近的父控制器,然后父控制器可以处理或转发请求。

在显示视图控制器之前,此方法根据表示样式调整显示的视图控制器的视图大小。对于大多数表示样式,生成的视图随后使用显示视图控制器的modAltTransitionStyle属性中的转换样式在屏幕上设置动画。对于自定义表示,视图使用被呈现的视图控制器的转换委托在屏幕上进行动画。对于当前上下文表示,视图可以使用当前视图控制器的转换样式在屏幕上进行动画。
在viewDidAppear:方法在被呈现的视图控制器上被调用之后,会调用完成处理程序。

参数 :

viewControllerToPresent : 要在当前视图控制器的内容上显示的视图控制器。

flag : YES设置演示的动画;NO,没有演示的动画。

completion : 演示结束后要执行的块。此块没有返回值,也不接受任何参数。可以为该参数指定nil。

- (void)presentViewController:(UIViewController *)viewControllerToPresent animated: (BOOL)flag completion:(void (^ __nullable)(void))completion API_AVAILABLE(ios(5.0));

- (void)dismissViewControllerAnimated: (BOOL)flag completion: (void (^ __nullable)(void))completion API_AVAILABLE(ios(5.0));

函数描述 : 解除由视图控制器模态呈现的视图控制器。呈现视图控制器负责解除其呈现的视图控制器。如果在呈现的视图控制器本身上调用此方法,UIKit将要求呈现的视图控制器处理解除。

如果连续呈现多个视图控制器,从而构建呈现的视图控制器堆栈,则在堆栈中较低的视图控制器上调用此方法将解除其直接子视图控制器和堆栈中该子视图控制器之上的所有视图控制器。当这种情况发生时,只有最上面的视图以动画的方式被解除;任何中间视图控制器都只是从堆栈中移除。最上面的视图使用其模式转换样式被解除,它可能与堆栈中其他视图控制器使用的样式不同。

如果要保留对视图控制器的presented view controller的引用,请在调用此方法之前获取presentedViewController属性中的值。在所呈现的视图控制器上调用viewDidDiscape:方法后,将调用完成处理程序。

参数 :

flag : 通过YES设置过渡的动画。

completion :解除视图控制器后要执行的块。此块没有返回值,不接受任何参数。可以为此参数指定nil。

- (void)dismissViewControllerAnimated: (BOOL)flag completion: (void (^ __nullable)(void))completion API_AVAILABLE(ios(5.0));

- (instancetype)initWithRootViewController:(UIViewController *)rootViewController NS_DESIGNATED_INITIALIZER;

函数描述 : 初始化导航控制器并将根视图控制器推送到导航堆栈上。每个导航堆栈必须至少有一个视图控制器作为根。

参数:

rootViewController:位于导航堆栈底部的视图控制器。此对象不能是UITabBarController类的实例。

返回值:

初始化的导航控制器对象,如果初始化对象时出现问题,则返回nil。

- (instancetype)initWithRootViewController:(UIViewController *)rootViewController NS_DESIGNATED_INITIALIZER; 

例如在窗口中设置第一个控制器时:

    self.window = [[UIWindow alloc]initWithFrame:[UIScreen mainScreen].bounds];
    self.window.backgroundColor = [UIColor whiteColor];
    ViewController *viewController = [[ViewController alloc]init];
    UINavigationController * navigationController = [[UINavigationController alloc]initWithRootViewController:viewController];
    self.window.rootViewController = navigationController;
    [self.window makeKeyAndVisible];

- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated;

函数描述 : 将视图控制器推到接收器堆栈上并更新显示。viewController参数中的对象成为导航堆栈上的顶视图控制器。如果已设置动画的参数为“是”,则视图将设置动画到相应位置;否则,视图将仅显示在其最终位置。

参数:

viewController :要推到堆栈上的视图控制器。此对象不能是选项卡栏控制器。如果视图控制器已经在导航堆栈上,则此方法引发异常。

animated:指定“YES”设置过渡的动画,如果不希望设置过渡的动画,则指定“NO”。如果在启动时设置导航控制器,则可以指定“NO”。

- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated;

在开发中,经常有这样的情景,从一个控制器A模态到另一个控制器B,再从B控制器push到另一个C控制器,但是按照通常的方法,模态到B控制器之后,就push不到C控制器了,这个是因为B控制器不是导航控制器的根控制器或子控制器。只有当前控制器在导航控制器栈中才可以使用push到导航其它视图,所你必须把B控制器加入到导航控制器中,才能用来push 其他视图。

//在A的控制器里模态到B的控制器:
UINavigationController* navigationController = [[UINavigationController alloc] initWithRootViewController:A];
[self presentViewController:navigationController animated:YES completion:nil];
//再从B控制器push 到C控制器
[self.navigationController pushViewController:C animated:YES];

- (nullable UIViewController *)popViewControllerAnimated:(BOOL)animated;

函数描述 : 从导航堆栈弹出顶端视图控制器并更新显示。此方法从堆栈中移除顶部视图控制器,并使堆栈的新顶部成为活动视图控制器。如果堆栈顶部的视图控制器是根视图控制器,则此方法不执行任何操作。换句话说,不能弹出堆栈上的最后一个项。除了在堆栈顶部显示与新视图控制器关联的视图外,此方法还相应地更新导航栏和工具栏。

参数 :

animated : 将此值设置为YES可设置转换的动画。如果要在显示导航控制器的视图之前设置该控制器,请设置NO。

返回值 : 从堆栈中弹出的视图控制器。

- (nullable UIViewController *)popViewControllerAnimated:(BOOL)animated; 

例如在推出控制器后延迟1秒弹出控制器 :

[self.navigationController performSelector:@selector(popViewControllerAnimated:) withObject:@(YES) afterDelay:1.0];

- (nullable NSArray<__kindof UIViewController *> *)popToViewController:(UIViewController *)viewController animated:(BOOL)animated;

函数描述 : 弹出视图控制器,直到指定的视图控制器位于导航堆栈的顶部。

参数 :

viewController : 要位于堆栈顶部的视图控制器。此视图控制器当前必须位于导航堆栈上。

animated : 将此值设置为YES可设置转换的动画。如果要在显示导航控制器的视图之前设置该控制器,请设置NO。

返回值 : 包含从堆栈中弹出的视图控制器的数组。

- (nullable NSArray<__kindof UIViewController *> *)popToViewController:(UIViewController *)viewController animated:(BOOL)animated; 

弹到指定的控制器,通常会判断该控制器是否在控制器栈中,例如 :

    UIViewController *targetViewController = nil;
    for (UIViewController *viewController in self.navigationController.viewControllers) {
        if ([viewController isMemberOfClass:NSClassFromString(RefundListViewController)]) {
            targetViewController = viewController;
            break;
        }
    }
    if (targetViewController) {
        [self.navigationController popToViewController:targetViewController animated:YES];
    }

- (nullable NSArray<__kindof UIViewController *> *)popToRootViewControllerAnimated:(BOOL)animated;

函数描述 : 弹出堆栈上除根视图控制器之外的所有视图控制器并更新显示。

参数 :

animated :将此值设置为YES可设置转换的动画。如果要在显示导航控制器的视图之前设置该控制器,请设置NO。

返回值 :从堆栈中弹出的项的视图控制器数组。

- (nullable NSArray<__kindof UIViewController *> *)popToRootViewControllerAnimated:(BOOL)animated; 

没啥说的,就这样调用,例如:

[self.navigationController popToRootViewControllerAnimated:NO];

- (void)setNavigationBarHidden:(BOOL)hidden animated:(BOOL)animated;

函数描述 : 设置导航栏是否隐藏。对于动画过渡,动画的持续时间由UINavigationControllerHideShowBarDuration常量中的值指定。

参数 :

hidden : 指定“YES”隐藏导航栏,或指定“NO”显示导航栏。

animated : 如果要设置可见性更改的动画,请指定“YES”;如果要立即显示导航栏,请指定“NO”。

- (void)setNavigationBarHidden:(BOOL)hidden animated:(BOOL)animated; 

UINavigationBar 导航控制器管理的导航栏

UINavigationBar派生自UIView,UINavigationBar对象是一个栏,通常显示在窗口的顶部,包含用于在屏幕层次结构中导航的按钮。主要组件是一个左(后退)按钮、一个中间标题和一个可选的右按钮。可以将导航栏作为独立对象使用,也可以将其与导航控制器对象结合使用。

导航栏最常用于导航控制器中。UINavigationController对象创建、显示和管理其关联的导航栏,并使用添加的视图控制器的属性来控制导航栏中显示的内容。

要在使用导航控制器时控制导航栏,需要执行以下步骤:

1.在界面生成器或代码中创建导航控制器。
2.使用UINavigationController对象上的navigation bar属性配置导航栏的外观。
3.通过在推送到导航控制器堆栈上的每个UIViewController上设置title和navigationItem属性来控制导航栏的内容。

也可以使用独立的导航栏,而不使用导航控制器。要向界面添加导航栏,需要执行以下步骤:

1.设置自动布局规则以控制导航栏在界面中的位置。
2.创建根导航项以提供初始标题。
3.配置委托对象以处理用户与导航栏的交互。
4.自定义导航栏的外观。
5.配置应用程序,以便在用户浏览分层屏幕时推送和弹出相关导航项。

UINavigationBar常用属性

@property(nullable,nonatomic,copy) NSDictionary<NSAttributedStringKey, id> *titleTextAttributes API_AVAILABLE(ios(5.0)) UI_APPEARANCE_SELECTOR;

属性描述 : 显示栏标题文本的属性。可以使用NSAttributedStringKey中描述的文本属性键在文本属性字典中指定标题的字体、文本颜色、文本阴影颜色和文本阴影偏移量。

@property(nullable,nonatomic,copy) NSDictionary<NSAttributedStringKey, id> *titleTextAttributes API_AVAILABLE(ios(5.0)) UI_APPEARANCE_SELECTOR;

@property(nullable, nonatomic,strong) UIColor *barTintColor API_AVAILABLE(ios(7.0)) UI_APPEARANCE_SELECTOR;

属性描述 : 应用于导航栏背景的半透明色。默认情况下,此颜色是半透明的,除非将“ translucent”属性设置为“NO”。

@property(nullable, nonatomic,strong) UIColor *barTintColor API_AVAILABLE(ios(7.0)) UI_APPEARANCE_SELECTOR;

@property(nonatomic,assign,getter=isTranslucent) BOOL translucent API_AVAILABLE(ios(3.0)) UI_APPEARANCE_SELECTOR;

属性描述 : 一个布尔值,指示导航栏是否半透明,当导航栏是半透明的时,将视图控制器的edgesForExtendedLayout和extendedLayoutIncludesOpaqueBars属性配置为在导航栏下显示内容。
如果导航栏没有自定义背景图像,或者背景图像的任何像素的alpha值小于1.0,则此属性的默认值为YES。如果背景图像是完全不透明的,则此属性的默认值为NO。如果将此属性设置为YES,并且自定义背景图像是完全不透明的,则UIKit将对图像应用小于1.0的系统定义不透明度。如果将此属性设置为NO,并且背景图像不透明,则UIKit将添加不透明背景。

@property(nonatomic,assign,getter=isTranslucent) BOOL translucent API_AVAILABLE(ios(3.0)) UI_APPEARANCE_SELECTOR;

@property(nullable, nonatomic,strong) UIImage *shadowImage API_AVAILABLE(ios(6.0)) UI_APPEARANCE_SELECTOR;

属性描述 : 用于导航栏的阴影图像。默认值为nil,与默认阴影图像相对应。非nil时,此属性表示要显示的自定义阴影图像,而不是默认值。要显示自定义阴影图像,还必须使用set background image:forBarMetrics:方法设置自定义背景图像。如果使用默认背景图像,则无论此属性的值如何,都将使用默认阴影图像。

@property(nullable, nonatomic,strong) UIImage *shadowImage API_AVAILABLE(ios(6.0)) UI_APPEARANCE_SELECTOR;

例如设置一个透明的导航栏代码片段:

- (void)setNavigationBarAttributes {
    
    self.navigationController.navigationBar.barTintColor = [UIColor colorWithRed:54.0f/255.0f green:53.0f/255.0f blue:58.0f/255.0f alpha:0.5f];
    self.navigationController.navigationBar.titleTextAttributes = @{NSForegroundColorAttributeName: [UIColor whiteColor]};
    //去掉透明后导航栏下边的黑边
    self.navigationController.navigationBar.shadowImage = [[UIImage alloc] init];
    //设置导航栏背景图片为一个空的image,这样就透明了
    [self.navigationController.navigationBar setBackgroundImage:[[UIImage alloc] init] forBarMetrics:UIBarMetricsDefault];
    self.navigationController.navigationBar.translucent = YES;
}
截屏2020-02-16下午12.08.13.png

UINavigationBar常用函数

- (void)setBackgroundImage:(nullable UIImage *)backgroundImage forBarMetrics:(UIBarMetrics)barMetrics API_AVAILABLE(ios(5.0)) UI_APPEARANCE_SELECTOR;

函数描述 : 设置给定导航栏的背景图像。

参数 :

backgroundImage : 用于barMetrics的背景图像。

barMetrics : 一个导航栏指示常数。

- (void)setBackgroundImage:(nullable UIImage *)backgroundImage forBarMetrics:(UIBarMetrics)barMetrics API_AVAILABLE(ios(5.0)) UI_APPEARANCE_SELECTOR;

UINavigationItem

UINavigationItem派生自NSObject,当关联的视图控制器可见时,导航栏将显示的项。构建导航界面时,推送到导航堆栈上的每个视图控制器都必须有一个UINavigationItem对象,该对象包含要在导航栏中显示的按钮和视图。管理UINavigationController对象使用最上面两个视图控制器的导航项向导航栏填充内容。

导航项始终反映与其关联的视图控制器的信息。当视图控制器位于导航堆栈的顶部时,导航项必须提供要显示的标题。此外,该项可能包含要显示在导航栏右侧(或尾部)的其他按钮。可以使用leftbarbuttonems属性指定要在工具栏左侧(或前导)显示的按钮和视图,但导航控制器仅在有可用空间时才显示这些按钮。

导航项的backBarButtonElem属性反映当前视图控制器正好位于最顶部视图控制器下方时要显示的后退按钮。换句话说,当当前视图控制器位于最上面时,不使用后退按钮。

为导航项指定按钮时,必须使用UIBarButtonItem对象。如果要在导航栏中显示自定义视图,则必须在将这些视图添加到导航项之前将其包装在UIBarButtonItem对象中。

UINavigationItem常用属性

@property(nullable, nonatomic,copy) NSString *title;

属性描述 : 导航栏中显示的导航项标题,默认值为nil。当接收者位于导航项堆栈上,并且位于顶部的第二个位置时,换句话说,它的视图控制器管理用户将导航回的视图,此属性中的值用于最顶部导航栏上的back按钮。如果此属性的值为nil,则系统使用字符串“Back”作为Back按钮的文本。在ios11及以后版本中,标题的大小和位置由导航栏的preferslargetitle属性和导航项的largeTitleDisplayMode属性决定。

@property(nullable, nonatomic,copy)   NSString        *title;  

例如给导航栏设置一个标题:

- (void)viewDidLoad {
    
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor redColor];
    self.navigationItem.title = @"测试代码控制器";

}
截屏2020-02-16下午5.19.32.png

@property(nullable, nonatomic,strong) UIView *titleView;

属性描述 :当接收器是顶部项目时,显示在导航栏中心的自定义视图。如果此属性值为nil,则当接收者为顶部项目时,导航项目的标题将显示在导航栏的中心。如果将此属性设置为自定义视图,则将显示它而不是标题。自定义视图可以包含按钮。使用UIButton类中的buttonWithType:方法,以导航栏的样式将按钮添加到自定义视图中。自定义标题视图以导航栏为中心,可以根据需要调整大小。默认值为nil。

@property(nullable, nonatomic,strong) UIView          *titleView; 

@property(nullable, nonatomic,strong) UIBarButtonItem *leftBarButtonItem;

属性描述 : 当接收器是顶部导航项时,显示在导航栏左侧(或前缘)的自定义栏按钮项。此属性的内容始终引用leftBarButtonItems数组中的第一个栏按钮项。将新值赋给此属性将用新值替换leftBarButtonItems数组中的第一项。将此属性设置为nil将删除数组中的第一项。如果bar按钮项已经在数组中,则将其从当前位置移动到数组的前面。在从右向左的用户界面中,左侧栏按钮项的位置将自动翻转。

@property(nullable, nonatomic,strong) UIBarButtonItem *leftBarButtonItem;

@property(nullable, nonatomic,strong) UIBarButtonItem *rightBarButtonItem;

属性描述 : 当接收器是顶部导航项时,显示在导航栏右(或后缘)的自定义栏按钮项。此属性的内容始终引用rightBarButtonItems数组中的第一个栏按钮项。将新值赋给此属性将用新值替换rightBarButtonItems数组中的第一项。将此属性设置为nil将删除数组中的第一项。如果bar按钮项已经在数组中,则将其从当前位置移动到数组的前面。在从右向左的用户界面中,右栏按钮项的位置将自动翻转。

@property(nullable, nonatomic,strong) UIBarButtonItem *rightBarButtonItem;

@property(nullable,nonatomic,copy) NSArray<UIBarButtonItem *> *leftBarButtonItems API_AVAILABLE(ios(5.0));

属性描述 : 当接收器是顶部导航项时,显示在导航栏左侧(或前导)的自定义栏按钮项数组。此数组可以包含0个或多个要显示在导航栏左侧(或前导)的导航栏按钮项。项目可以包括固定宽度和灵活的宽度空间。如果leftItemsSupplementBackButton属性为YES,则项目将显示在“后退”按钮的右侧(或尾部),否则项目将替换“后退”按钮并从栏的左侧(或前缘)开始。项目按在数组中显示的相同顺序从左到右显示。在从右到左的用户界面中,项目将自动翻转。

如果没有足够的空间显示数组中的所有项,则不会显示与标题视图(如果存在)或栏右侧按钮重叠的项。数组中的第一项也可以使用leftBarButtonItem属性设置。

@property(nullable,nonatomic,copy) NSArray<UIBarButtonItem *> *leftBarButtonItems API_AVAILABLE(ios(5.0));

@property(nullable,nonatomic,copy) NSArray<UIBarButtonItem *> *rightBarButtonItems API_AVAILABLE(ios(5.0));

属性描述 : 当接收器是顶部导航项时,显示在导航栏右侧(或尾部)的自定义栏按钮项数组。此数组可以包含0个或多个要显示在导航栏右侧(或尾随)的导航栏按钮项。项目按在数组中显示的相同顺序从右到左显示。因此,数组中的第一个项是最右边的项,其他项被添加到前一个项的左边。在从右到左的用户界面中,项目将自动翻转。

如果没有足够的空间显示数组中的所有项,则不会显示与标题视图(如果存在)或栏左侧按钮重叠的项。数组中的第一项也可以使用rightBarButtonItem属性设置。

@property(nullable,nonatomic,copy) NSArray<UIBarButtonItem *> *rightBarButtonItems API_AVAILABLE(ios(5.0));

Push到下级界面后再移除当前控制器的代码片段

NSMutableArray *controllerArray = [viewController.navigationController.viewControllers mutableCopy];
for (UIViewController *newViewController in controllerArray) {
    if (newViewController == viewController) {
        [controllerArray removeObject:newViewController];
        break;
    }
}
[viewController.navigationController setViewControllers:controllerArray animated:NO];

测试代码片段

//
//  YSCUITextField.h

#import <UIKit/UIKit.h>

@interface YSCUITextField : UITextField

@end

//
//  YSCUITextField.m

#import "YSCUITextField.h"

@implementation YSCUITextField

/**
 返回接收器左侧覆盖视图的绘制矩形。不应该直接调用这个方法。如果想将左侧覆盖视图放置在不同的位置,可以覆盖此方法并返回新矩形。注意,在从右到左的用户界面中,绘制矩形保持不变。
 
 参数:
 bounds : 接收器的边框。
 返回值 : 要在其中绘制左覆盖视图的矩形。
 */

- (CGRect)leftViewRectForBounds:(CGRect)bounds {
    CGRect iconRect = [super leftViewRectForBounds:bounds];
    iconRect.origin.x += 10;
    return iconRect;
}

/**
 返回文本字段的文本的绘制矩形。不应该直接调用这个方法。如果想为文本自定义绘图矩形,可以覆盖此方法并返回一个不同的矩形。此方法的默认实现返回一个矩形,该矩形派生自控件的原始边界,但不包括接收方的边框或覆盖视图占用的区域。
 
 参数:
 bounds : 接收器的边框。
 返回值 : 用于标签文本的计算绘图矩形。
 */

- (CGRect)textRectForBounds:(CGRect)bounds {
    bounds.size.width += 35.0;
    return CGRectInset(bounds, 35, 0);
}

/**
 返回文本字段的占位符文本的绘制矩形。不应该直接调用这个方法。如果想为占位符文本自定义绘图矩形,可以覆盖此方法并返回一个不同的矩形。如果占位符字符串为空或nil,则不调用此方法。
 
 参数:
 bounds : 接收器的边框。
 返回值 : 占位符文本的计算绘图矩形。
 */

- (CGRect)placeholderRectForBounds:(CGRect)bounds {
    return CGRectInset(bounds, 35, 0);
}

/**
 返回可编辑文本显示的矩形。不应直接调用此方法。如果要为文本提供不同的编辑矩形,可以重写此方法并返回该矩形。默认情况下,此方法返回文本字段中不被任何覆盖视图占用的区域。
 
 参数 :
 bounds : 接收器的边框。
 返回值 : 可编辑文本显示的矩形。
 
 */

- (CGRect)editingRectForBounds:(CGRect)bounds {
    bounds.size.width += 35.0;
    return CGRectInset(bounds, 35, 0);
}

/**
 返回接收器右覆盖视图的绘图位置。不应直接调用此方法。如果要将右覆盖视图放置在其他位置,可以重写此方法并返回新矩形。请注意,在从右到左的用户界面中,绘图矩形保持不变。
 
 参数:
 bounds : 接收器的边框。
 返回值 : 在其中绘制右覆盖视图的矩形。
 */

- (CGRect)rightViewRectForBounds:(CGRect)bounds {
    CGRect textRect = [super rightViewRectForBounds:bounds];
    textRect.origin.x -= 10 * 0.5;
    return textRect;
}


@end

//
//  TestNavigationController.h


#import <UIKit/UIKit.h>


@interface TestNavigationController : UIViewController

@end
//
//  TestNavigationController.m


#import "TestNavigationController.h"
#import "YSCUITextField.h"

@interface TestNavigationController ()<UITextFieldDelegate>

@property (nonatomic, strong) YSCUITextField *searchTextField;
@property (nonatomic, strong) UIColor *backBarTintColor;
@property (nonatomic, copy) NSDictionary *backTitleAttributes;
@property (nonatomic, strong) UIImage *backShadowImage;
@property (nonatomic, assign) BOOL isHaveDian;//是否是小数

@end

@implementation TestNavigationController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor redColor];
}

- (void)viewWillAppear:(BOOL)animated{
    [super viewWillAppear:animated];
    //备份导航栏属性
    [self backupNavigationBarAttributes];
    //设置导航栏透明
    [self setNavigationBarAttributes];
}

- (void)viewDidAppear:(BOOL)animated{
    [super viewDidAppear:animated];
    //设置导航项左侧返回按钮
    [self setNavigationLeftBlockItem];
    //设置导航栏右侧按钮
    [self setNavigationRightBlockItem];
    //设置导航栏搜索视图
    [self setNavigationItemTitleView];
}

- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
    //重置导航栏属性
    [self resetNavigationBarAttributes];
    //将导航栏左侧返回按钮重置为黑色图标
    UIButton *leftButton = self.navigationItem.leftBarButtonItem.customView;
    [leftButton setImage:[UIImage imageNamed:@"btn_back_dark"] forState:UIControlStateNormal];
    [leftButton setImage:[UIImage imageNamed:@"btn_back_dark"] forState:UIControlStateHighlighted];
}


///---------------------------------------- UINavigationController代码测试区 -------------------------------

///备份导航栏属性
- (void)backupNavigationBarAttributes {
    _backBarTintColor = self.navigationController.navigationBar.barTintColor;
    _backTitleAttributes = self.navigationController.navigationBar.titleTextAttributes;
    _backShadowImage = self.navigationController.navigationBar.shadowImage;
}

///重置导航栏属性
- (void)resetNavigationBarAttributes {
    //栏标题文本的属性
    self.navigationController.navigationBar.titleTextAttributes = self.backTitleAttributes;
    //导航栏背景的色调颜色
    self.navigationController.navigationBar.barTintColor = self.backBarTintColor;
    //导航栏的阴影图像
    self.navigationController.navigationBar.shadowImage = self.backShadowImage;
    //导航栏是否半透明
    self.navigationController.navigationBar.translucent = NO;
}

///设置导航项左侧返回按钮
- (void)setNavigationLeftBlockItem{
    [((UIButton *)self.navigationItem.leftBarButtonItem.customView) setImageEdgeInsets:UIEdgeInsetsMake(0, -10, 0, 0)];
    [((UIButton *)self.navigationItem.leftBarButtonItem.customView) setImage:[UIImage imageNamed:@"btn_back_white"] forState:UIControlStateNormal];
    [((UIButton *)self.navigationItem.leftBarButtonItem.customView) setImage:[UIImage imageNamed:@"btn_back_white"] forState:UIControlStateHighlighted];
}

///设置导航栏透明
- (void)setNavigationBarAttributes {
    
    self.navigationController.navigationBar.barTintColor = [UIColor colorWithRed:54.0f/255.0f green:53.0f/255.0f blue:58.0f/255.0f alpha:0.5f];
    self.navigationController.navigationBar.titleTextAttributes = @{NSForegroundColorAttributeName: [UIColor whiteColor]};
    //去掉透明后导航栏下边的黑边
    self.navigationController.navigationBar.shadowImage = [[UIImage alloc] init];
    //设置导航栏背景图片为一个空的image,这样就透明了
    [self.navigationController.navigationBar setBackgroundImage:[[UIImage alloc] init] forBarMetrics:UIBarMetricsDefault];
    self.navigationController.navigationBar.translucent = YES;
}

///设置导航栏搜索视图
- (void)setNavigationItemTitleView{
    //初始化文本框
    self.searchTextField = [[YSCUITextField alloc] initWithFrame:CGRectMake(0, 0, SCREEN_WIDTH, 33.0)];
    //设置文本输入框样式
    self.searchTextField.clearButtonMode = UITextFieldViewModeWhileEditing;
    self.searchTextField.backgroundColor = [UIColor whiteColor];
    self.searchTextField.layer.cornerRadius = 16.5;
    self.searchTextField.leftViewMode = UITextFieldViewModeAlways;
    self.searchTextField.font = [UIFont systemFontOfSize:13];
    self.searchTextField.textColor = [UIColor blackColor];
    self.searchTextField.returnKeyType = UIReturnKeySearch;
    self.searchTextField.delegate = self;
    self.searchTextField.attributedPlaceholder = [[NSAttributedString alloc] initWithString:@"输入搜索内容" attributes:@{NSForegroundColorAttributeName: [UIColor blackColor], NSFontAttributeName: [UIFont systemFontOfSize:13]}];
    //初始化放大镜按钮视图
    UIButton *searchButton = [UIButton buttonWithType:UIButtonTypeCustom];
    [searchButton setImage:[UIImage imageNamed:@"ic_search_black"] forState:UIControlStateNormal];
    searchButton.contentMode = UIViewContentModeScaleAspectFit;
    searchButton.frame = CGRectMake (7.5, 7.5, 18.0, 18.0);
    //放大镜图标视图添加点击事件
    UITapGestureRecognizer *searchImageViewTapGestureRecognizer = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(searchImageViewTouchUpInside)];
    [searchButton addGestureRecognizer:searchImageViewTapGestureRecognizer];
    //初始化文本清空按钮
    UIButton *clearButton = [UIButton buttonWithType:UIButtonTypeCustom];
    clearButton.frame = CGRectMake(0.0, 7, 19.0, 19.0);
    [clearButton setImage:[UIImage imageNamed:@"btn_clear_content_cricled"] forState:UIControlStateNormal];
    clearButton.imageEdgeInsets = UIEdgeInsetsMake(2.5, 2.5, 2.5, 2.5);
    [clearButton addTarget:self action:@selector(clearSearchText:) forControlEvents:UIControlEventTouchUpInside];
    //添加清空按钮到文本输入框右侧
    self.searchTextField.rightView = clearButton;
    //添加放大镜图标视图到文本输入框左侧
    self.searchTextField.leftView = searchButton;
    //将文本输入框作为导航栏视图
    self.navigationItem.titleView = self.searchTextField;
}

///设置导航栏右侧按钮
- (void)setNavigationRightBlockItem{
    
    //初始化更多按钮
    UIButton *moreButton = [UIButton buttonWithType:UIButtonTypeCustom];
    //设置按钮矩形框架
    moreButton.frame = CGRectMake(0, 0, 25.0, 25.0);
    //添加点击事件
    [moreButton addTarget:self action:@selector(navigationBarButtonItemOnclick:) forControlEvents:UIControlEventTouchUpInside];
    //设置图片内间距
    moreButton.imageEdgeInsets = UIEdgeInsetsMake(0, 0, 0, -6.0);
    //设置...图标
    [moreButton setImage:[UIImage imageNamed:@"btn_more_white"] forState:UIControlStateNormal];
    //初始化条形按钮项
    UIBarButtonItem *moreBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:moreButton];
    //添加标签
    moreButton.tag = 3000;
    
    //初始化扫描按钮
    UIButton *scanButton = [UIButton buttonWithType:UIButtonTypeCustom];
    //设置按钮矩形框架
    scanButton.frame = CGRectMake(0, 0, 25.0, 25.0);
    //添加点击事件
    [scanButton addTarget:self action:@selector(navigationBarButtonItemOnclick:) forControlEvents:UIControlEventTouchUpInside];
    //设置图片内间距
    scanButton.imageEdgeInsets = UIEdgeInsetsMake(0, 0, 0, -6.0);
    //设置扫描图标
    [scanButton setImage:[UIImage imageNamed:@"btn_scan_white"] forState:UIControlStateNormal];
    //初始化条形按钮项
    UIBarButtonItem *scanBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:scanButton];
    //添加标签
    scanButton.tag = 3001;
    //将条形按钮项数组添加到导航栏右侧按钮
    NSArray *rightBarButtonItems = @[moreBarButtonItem, scanBarButtonItem];
    self.navigationItem.rightBarButtonItems = rightBarButtonItems;
}

///清空输入的搜索内容
- (void)clearSearchText:(UIButton *)sender {
    self.searchTextField.text = nil;
}

///点击视图文本输入框以外内容时文本框放弃第一响应对象
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    if ([self.searchTextField isFirstResponder]) {
        [self.searchTextField resignFirstResponder];
    }
}

///导航栏右侧按钮点击事件
- (void)navigationBarButtonItemOnclick:(UIButton *)sender {
    if(sender.tag == 3000){
        NSLog(@"点击了更多按钮");
    }else if(sender.tag == 3001){
        NSLog(@"点击了扫描按钮");
    }
}

///放大镜图标视图点击事件
- (void)searchImageViewTouchUpInside{
    NSLog(@"放大镜点击了");
}

#pragma mark - Text field delegate

- (BOOL)textFieldShouldReturn:(UITextField *)textField {
    
    if (textField.isFirstResponder) {
        [textField resignFirstResponder];
    }
    NSLog(@"搜索%@",textField.text);
    return YES;
}

- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string{
    
    NSString *text = [textField.text stringByAppendingString:string] ? : string;

    if (text.floatValue > 50.0) {
        [self.view makeToast:@"最多可使用的余额50.0元"];
        return NO;
    }
    //#####验证输入的内容
    if ([textField.text containsString:@"."]) {
        self.isHaveDian = YES;
    }else{
        self.isHaveDian = NO;
    }
    
    if (string.length > 0) {
        
        //当前输入的字符
        unichar single = [string characterAtIndex:0];
        
        //验证是否是数字
        NSString *phoneRegex = @"^[0-9]+(\\.[0-9]{0,2})?$";
        NSPredicate *phoneTest = [NSPredicate predicateWithFormat:@"SELF MATCHES %@",phoneRegex];
        if(single != '.' && ![phoneTest evaluateWithObject:string]){
            [self.view makeToast:@"请输入数字格式"];
            return NO;
        }
        
        [NSString stringWithFormat:@"single = %c",single];
        // 只能有一个小数点
        if (self.isHaveDian && single == '.') {
            [self.view makeToast:@"最多只能输入一个小数点"];
            return NO;
        }
        // 如果第一位是.则前面加上0.
        if ((textField.text.length == 0) && (single == '.')) {
            textField.text = @"0";
        }
        // 如果第一位是0则后面必须输入点,否则不能输入。
        if ([textField.text hasPrefix:@"0"]) {
            if (textField.text.length > 1) {
                NSString *secondStr = [textField.text substringWithRange:NSMakeRange(1, 1)];
                if (![secondStr isEqualToString:@"."]) {
                    [self.view makeToast:@"第二个字符需要是小数点"];
                    return NO;
                }
            }else{
                if (![string isEqualToString:@"."]) {
                    [self.view makeToast:@"第二个字符需要是小数点"];
                    return NO;
                }
            }
        }
        // 小数点后最多能输入两位
        if (self.isHaveDian) {
            NSRange ran = [textField.text rangeOfString:@"."];
            // 由于range.location是NSUInteger类型的,所以这里不能通过(range.location - ran.location)>2来判断
            if (range.location > ran.location) {
                if ([textField.text pathExtension].length > 1) {
                    [self.view makeToast:@"小数点后最多有两位小数"];
                    return NO;
                }
            }
        }
        
    }
    return YES;
}

///------------------------------------ UINavigationController代码测试区结束 --------------------------------


@end

效果如图 :

Jietu20200218-212943.gif

Bug记录

问题如下 : 在一个有导航栏的控制器push到另一个有导航栏的控制器,在另一个控制器中隐藏了导航栏,然后滑动屏幕返回上一控制器,控制器导航栏消失了,但在控制器滑动返回前,明明在viewWillDisappear:函数中设置了导航栏恢复的代码,但却没有起作用,代码如下:

- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
    self.disapper = YES;
    self.navigationController.navigationBar.translucent = NO;
    [self.navigationController setNavigationBarHidden:NO animated:YES];
    if ([self.searchTextField isFirstResponder]) {
        [self.searchTextField resignFirstResponder];
    }
}

Jietu20200911-163945.gif

通过对比发现,在隐藏导航栏时使用的是self.navigationController.navigationBar.hidden = YES;来隐藏的,索性将恢复的代码也改为self.navigationController.navigationBar.hidden = NO;进行修改,问题解决,代码如下:

- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
    self.disapper = YES;
    self.navigationController.navigationBar.translucent = NO;
    self.navigationController.navigationBar.hidden = NO;
    if ([self.searchTextField isFirstResponder]) {
        [self.searchTextField resignFirstResponder];
    }
}
Jietu20200911-165757.gif
上一篇下一篇

猜你喜欢

热点阅读