MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Objective-C 在 iOS 导航栏与标签栏管理中的实践

2022-12-242.7k 阅读

一、iOS 导航栏基础

在 iOS 开发中,导航栏(UINavigationBar)是一个非常重要的用户界面元素,它为用户提供了一种在应用程序不同层次结构间导航的便捷方式。

1.1 导航栏的结构组成

导航栏通常位于屏幕顶部,由三部分组成:左侧的返回按钮区域(通常用于返回上一级页面)、中间的标题区域以及右侧的额外操作按钮区域。在 Objective - C 中,UINavigationBar 类负责管理导航栏的外观和行为。

例如,在一个简单的视图控制器中,我们可以这样初始化一个导航栏:

#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    UINavigationBar *navigationBar = [[UINavigationBar alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, 44)];
    [self.view addSubview:navigationBar];
}

@end

上述代码创建了一个基本的导航栏,并将其添加到视图控制器的视图中。不过,这样创建的导航栏并没有实际的功能,只是一个外观展示。

1.2 导航栏的外观设置

  1. 背景颜色设置 可以通过设置 barTintColor 属性来改变导航栏的背景颜色。例如:
navigationBar.barTintColor = [UIColor blueColor];

这会将导航栏的背景颜色设置为蓝色。

  1. 标题样式设置 对于导航栏中间的标题,可以通过 titleTextAttributes 属性来设置其样式,包括字体、颜色等。比如:
NSDictionary *attributes = @{NSForegroundColorAttributeName : [UIColor whiteColor],
                             NSFontAttributeName : [UIFont boldSystemFontOfSize:18]};
navigationBar.titleTextAttributes = attributes;

上述代码将标题文本颜色设置为白色,字体设置为加粗的系统字体,字号为 18。

  1. 按钮样式设置 导航栏上的按钮,无论是返回按钮还是自定义的右侧按钮,都可以设置其样式。对于返回按钮,可以通过 appearance 代理来全局设置其样式。例如:
UINavigationBarAppearance *appearance = [UINavigationBarAppearance new];
appearance.backButtonAppearance.normal.titleTextAttributes = @{NSForegroundColorAttributeName : [UIColor redColor]};
[UINavigationBar appearance].standardAppearance = appearance;

这会将所有导航栏返回按钮的标题颜色设置为红色。

二、使用 UINavigationController 管理导航栏

UINavigationController 是 iOS 中专门用于管理导航栏和视图控制器层级关系的类。它采用栈(Stack)的方式来管理视图控制器,用户可以通过导航栏上的按钮在不同的视图控制器之间进行导航。

2.1 创建 UINavigationController

在应用程序的入口,通常会创建一个 UINavigationController 作为根视图控制器。例如,在 AppDelegate.m 文件中:

#import "AppDelegate.h"
#import "ViewController.h"

@interface AppDelegate ()

@end

@implementation AppDelegate


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    ViewController *rootViewController = [[ViewController alloc] init];
    UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:rootViewController];
    self.window = [[UIWindow alloc] initWithFrame:UIScreen.mainScreen.bounds];
    self.window.rootViewController = navigationController;
    [self.window makeKeyAndVisible];
    return YES;
}

@end

上述代码创建了一个以 ViewController 为根视图控制器的 UINavigationController,并将其设置为应用程序窗口的根视图控制器。

2.2 视图控制器的入栈与出栈

  1. 入栈操作(Push) 当用户需要从当前视图控制器跳转到下一级视图控制器时,使用 pushViewController:animated: 方法。例如,在 ViewController.m 中:
#import "ViewController.h"
#import "SecondViewController.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    UIButton *pushButton = [UIButton buttonWithType:UIButtonTypeSystem];
    pushButton.frame = CGRectMake(100, 100, 200, 50);
    [pushButton setTitle:@"Push to Second View" forState:UIControlStateNormal];
    [pushButton addTarget:self action:@selector(pushToSecondView) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:pushButton];
}

- (void)pushToSecondView {
    SecondViewController *secondViewController = [[SecondViewController alloc] init];
    [self.navigationController pushViewController:secondViewController animated:YES];
}

@end

上述代码在 ViewController 中添加了一个按钮,点击按钮会将 SecondViewController 压入导航栈,同时导航栏会自动显示返回按钮。

  1. 出栈操作(Pop) 当用户想要返回上一级视图控制器时,可以使用 popViewControllerAnimated: 方法。在 SecondViewController.m 中:
#import "SecondViewController.h"

@interface SecondViewController ()

@end

@implementation SecondViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    UIButton *popButton = [UIButton buttonWithType:UIButtonTypeSystem];
    popButton.frame = CGRectMake(100, 100, 200, 50);
    [popButton setTitle:@"Pop to First View" forState:UIControlStateNormal];
    [popButton addTarget:self action:@selector(popToFirstView) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:popButton];
}

- (void)popToFirstView {
    [self.navigationController popViewControllerAnimated:YES];
}

@end

点击这个按钮会将 SecondViewController 从导航栈中弹出,返回到上一级视图控制器 ViewController

2.3 导航栏的动态更新

在不同的视图控制器中,导航栏的标题和按钮等属性可以根据需求动态更新。例如,在 SecondViewController.m 中更新导航栏标题:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.navigationItem.title = @"Second View";
}

这样,当 SecondViewController 被压入导航栈时,导航栏的标题会更新为 “Second View”。

同样,也可以动态添加右侧按钮。比如在 SecondViewController.m 中添加一个分享按钮:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    UIBarButtonItem *shareButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAction target:self action:@selector(shareAction)];
    self.navigationItem.rightBarButtonItem = shareButton;
}

- (void)shareAction {
    // 分享逻辑代码
    NSLog(@"Share action triggered");
}

上述代码在 SecondViewController 的导航栏右侧添加了一个分享按钮,点击按钮会触发分享逻辑。

三、iOS 标签栏基础

标签栏(UITabBar)是 iOS 应用中常见的底部导航组件,它允许用户在不同的主要功能模块之间快速切换。

3.1 标签栏的结构组成

UITabBar 位于屏幕底部,通常由多个标签(UITabBarItem)组成。每个标签都包含一个图标和一个标题,用户通过点击标签来切换到对应的视图控制器。

例如,创建一个简单的标签栏:

UITabBar *tabBar = [[UITabBar alloc] initWithFrame:CGRectMake(0, self.view.bounds.size.height - 49, self.view.bounds.size.width, 49)];
[tabBar setItems:@[
                  [[UITabBarItem alloc] initWithTitle:@"Home" image:[UIImage systemImageNamed:@"house"] tag:0],
                  [[UITabBarItem alloc] initWithTitle:@"Profile" image:[UIImage systemImageNamed:@"person"] tag:1]
                  ]];
[tabBar addTarget:self action:@selector(tabBarItemTapped:) forControlEvents:UIControlEventValueChanged];
[self.view addSubview:tabBar];

上述代码创建了一个包含两个标签的标签栏,分别是 “Home” 和 “Profile”,并为标签栏添加了点击事件处理方法。

3.2 标签栏的外观设置

  1. 背景颜色设置 通过设置 barTintColor 属性来改变标签栏的背景颜色,例如:
tabBar.barTintColor = [UIColor grayColor];

这会将标签栏的背景颜色设置为灰色。

  1. 标签样式设置 可以通过 itemAppearance 来设置标签的样式。比如设置选中状态下的标签颜色:
UITabBarAppearance *tabBarAppearance = [UITabBarAppearance new];
tabBarAppearance.stackedLayoutAppearance.selected.titleTextAttributes = @{NSForegroundColorAttributeName : [UIColor blueColor]};
tabBar.standardAppearance = tabBarAppearance;

上述代码将选中状态下的标签标题颜色设置为蓝色。

四、使用 UITabBarController 管理标签栏

UITabBarController 用于管理标签栏和多个视图控制器之间的关系,它可以方便地实现应用程序不同模块之间的切换。

4.1 创建 UITabBarController

通常在应用程序入口创建 UITabBarController。例如,在 AppDelegate.m 中:

#import "AppDelegate.h"
#import "HomeViewController.h"
#import "ProfileViewController.h"

@interface AppDelegate ()

@end

@implementation AppDelegate


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    HomeViewController *homeViewController = [[HomeViewController alloc] init];
    UINavigationController *homeNavigationController = [[UINavigationController alloc] initWithRootViewController:homeViewController];
    homeNavigationController.tabBarItem = [[UITabBarItem alloc] initWithTitle:@"Home" image:[UIImage systemImageNamed:@"house"] tag:0];
    
    ProfileViewController *profileViewController = [[ProfileViewController alloc] init];
    UINavigationController *profileNavigationController = [[UINavigationController alloc] initWithRootViewController:profileViewController];
    profileNavigationController.tabBarItem = [[UITabBarItem alloc] initWithTitle:@"Profile" image:[UIImage systemImageNamed:@"person"] tag:1];
    
    UITabBarController *tabBarController = [[UITabBarController alloc] init];
    tabBarController.viewControllers = @[homeNavigationController, profileNavigationController];
    
    self.window = [[UIWindow alloc] initWithFrame:UIScreen.mainScreen.bounds];
    self.window.rootViewController = tabBarController;
    [self.window makeKeyAndVisible];
    return YES;
}

@end

上述代码创建了一个 UITabBarController,包含两个导航控制器,每个导航控制器分别对应一个视图控制器。每个导航控制器的 tabBarItem 用于设置标签栏的显示内容。

4.2 标签栏切换事件处理

UITabBarController 提供了 delegate 协议来处理标签栏切换的相关事件。例如,在 AppDelegate.m 中设置代理并实现代理方法:

#import "AppDelegate.h"
#import "HomeViewController.h"
#import "ProfileViewController.h"

@interface AppDelegate () <UITabBarControllerDelegate>

@end

@implementation AppDelegate


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // 视图控制器初始化代码...
    
    tabBarController.delegate = self;
    
    self.window = [[UIWindow alloc] initWithFrame:UIScreen.mainScreen.bounds];
    self.window.rootViewController = tabBarController;
    [self.window makeKeyAndVisible];
    return YES;
}

- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController {
    NSInteger selectedIndex = [tabBarController.viewControllers indexOfObject:viewController];
    if (selectedIndex == 0) {
        NSLog(@"Home tab selected");
    } else if (selectedIndex == 1) {
        NSLog(@"Profile tab selected");
    }
}

@end

上述代码实现了 tabBarController:didSelectViewController: 方法,当用户切换标签栏时,会根据选中的视图控制器打印相应的日志。

五、导航栏与标签栏的协同工作

在实际应用中,导航栏和标签栏通常会协同工作,以提供更完整和流畅的用户体验。

5.1 嵌套使用导航栏和标签栏

一种常见的结构是在标签栏的每个标签对应的视图控制器中再嵌套导航栏。例如,在上面的例子中,HomeViewControllerProfileViewController 都被包含在 UINavigationController 中。这样,用户在切换标签栏时,可以在每个标签对应的模块内进行导航操作。

HomeViewController 中,如果需要进行页面跳转,可以像之前在单一导航栏场景中一样使用 pushViewController:animated: 方法。例如:

#import "HomeViewController.h"
#import "DetailViewController.h"

@interface HomeViewController ()

@end

@implementation HomeViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    UIButton *pushButton = [UIButton buttonWithType:UIButtonTypeSystem];
    pushButton.frame = CGRectMake(100, 100, 200, 50);
    [pushButton setTitle:@"Push to Detail" forState:UIControlStateNormal];
    [pushButton addTarget:self action:@selector(pushToDetail) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:pushButton];
}

- (void)pushToDetail {
    DetailViewController *detailViewController = [[DetailViewController alloc] init];
    [self.navigationController pushViewController:detailViewController animated:YES];
}

@end

这样,在 “Home” 标签对应的模块内,用户可以通过点击按钮进行页面跳转,导航栏会自动管理页面层级。

5.2 同步更新导航栏和标签栏状态

有时候,需要根据导航栏的页面层级或其他操作来同步更新标签栏的状态。例如,在某个特定页面需要隐藏标签栏。可以在视图控制器的 viewWillAppear: 方法中进行判断和操作:

#import "DetailViewController.h"

@interface DetailViewController ()

@end

@implementation DetailViewController

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    if (self.shouldHideTabBar) {
        self.tabBarController.tabBar.hidden = YES;
    } else {
        self.tabBarController.tabBar.hidden = NO;
    }
}

@end

上述代码根据 shouldHideTabBar 变量的值来决定是否隐藏标签栏。这个变量可以根据具体业务逻辑在视图控制器的其他地方进行设置。

同时,也可以根据标签栏的切换来更新导航栏的标题等属性。例如,在 AppDelegate.mtabBarController:didSelectViewController: 方法中:

- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController {
    NSInteger selectedIndex = [tabBarController.viewControllers indexOfObject:viewController];
    if (selectedIndex == 0) {
        UINavigationController *navigationController = (UINavigationController *)viewController;
        navigationController.navigationBar.topItem.title = @"Home Page";
    } else if (selectedIndex == 1) {
        UINavigationController *navigationController = (UINavigationController *)viewController;
        navigationController.navigationBar.topItem.title = @"Profile Page";
    }
}

上述代码根据标签栏的选中项,更新对应的导航栏标题。

通过合理地设计和协同导航栏与标签栏的工作,可以为 iOS 应用用户提供清晰、便捷的导航体验,提高应用的易用性和用户满意度。在实际开发中,需要根据具体的业务需求和用户体验设计,灵活运用导航栏和标签栏的各种特性,以打造出高质量的 iOS 应用程序。