Objective-C之平铺导航、标签导航、树形结构导航

时间:2022-12-05 16:02:02

盼秋风,徐徐而来,吹尽炎热,捎来些许凉意。

望秋水,秋雨来临,散尽暑气,带来一夜好眠。

来说一下导航控制器。导航一般分三种:平铺导航、标签导航、树形结构导航。

平时我们大家经常见的使用导航的样式如下:

平铺导航:应用界面(基于分屏导航),电子书(基于分页导航)

Objective-C之平铺导航、标签导航、树形结构导航  Objective-C之平铺导航、标签导航、树形结构导航

标签导航:苹果的Photos等。

Objective-C之平铺导航、标签导航、树形结构导航

树形结构导航:苹果的Settings等。

Objective-C之平铺导航、标签导航、树形结构导航

现在我们通过一个国家列表Demo来介绍实现这三种导航模式。

首先先创建了一个plist文件,存储要使用的测试数据,如下:

Objective-C之平铺导航、标签导航、树形结构导航

鉴于我们之前用的都是故事板,下面我们就继续使用Storyboard来组合。

我想实现的demo如下:(标签导航)tab为各个洲,每个tab中显示的是该洲的国家列表;(树形结构导航)点击某国家,进入详细页面;(平铺导航)详细页面采用分屏模式,第一页显示首都和国旗,第二页显示地图。(flag和map图来源于*)

1.标签导航:

①创建一个Single View Application工程,打开Main.storyboard,将View Controller Scene删除,重新从对象列表中拖拽一个Tab Bar Controller,我们看到它由三部分组成,左边是Tab Bar Controller,右边两个是Tab关联的页面,由于这里是使用动态创建tab,所以右边两个View Controller删掉。

②ViewController类的继承类修改为UITabBarController,这里添加了一个字典成员变量continentList来保存plist文件中的数据。

③重新打开故事板,将①中的Tab Bar Controller Scene的类改为ViewController。这二者自此关联。Objective-C之平铺导航、标签导航、树形结构导航


④创建Tab对应Controller:

我们要显示国家列表,应该使用TableViewController,鉴于在此我们是要展示树形结构导航(需要使用Navigation Controller,而Navigation Controller会自带一个TableViewController),所以我在这直接拖拽了一个Navigation Controller。

该Navigation Controller的StoryboardID设为ContinentNavigation(便于代码获取)。自带的Table View Controller设置StoryboardID为Continent。

注:如果使用故事板固定设定tab,按住Ctrl从Tab Bar Controller拖到Navigation Controller选择view controllers即关联上。在此我们动态通过代码关联。

⑤创建Tab对应的Controller关联类:

创建ContinentViewController类,继承自UITableViewController类。创建数组成员变量countries来保存当前tab的数据。由上一层设置。

将④中的Table View Controller的Class设为刚创建的ContinentViewController类,方式同③不再截图喽。

⑥为Tab Bar Controller动态添加viewcontroller。直接上代码了哈,说明看注释吧:

- (void)viewDidLoad {
[super viewDidLoad];
//获取Country.plist中数据赋给_continentList
NSString *resourcePath = [[NSBundle mainBundle] pathForResource:@"Country" ofType:@"plist"];
_continentList = [NSDictionary dictionaryWithContentsOfFile:resourcePath];
//创建动态数组,这里是故事板中ContinentNavigation的导航控制器数组,根据plist中数据赋值
NSMutableArray *continents = [[NSMutableArray alloc] init];
// [_continentList allKeys]在此相当于所有洲的名字,有多少个洲我们就创建多少个Tab,Tab的个数取决于viewControllers中控制器的个数,tab bar item显示内容取决于控制器的Title。
for (NSString* continentName in [_continentList allKeys]) {
// 获取ID为ContinentNavigation的导航控制器
UINavigationController *navigation = [[UIStoryboard storyboardWithName:@"Main" bundle:nil] instantiateViewControllerWithIdentifier:@"ContinentNavigation"];
// 获取导航控制器绑定的rootViewController,即ContinentViewController实例,设置数据源。
ContinentViewController *continent = (ContinentViewController *)navigation.topViewController;
continent.title = continentName;
continent.countries = [_continentList objectForKey:continentName];
// 这个不用说了吧,将设置好的UINavigationController实例添加到动态数组中。注:是navigation,而不是continent,否则树形结构导航跳转会出问题。
[continents addObject:navigation];
}

//赋值,注:Objective-C中,NSArray是不能追加删除的,NSMutableArray继承自NSArray,是可修改的动态数组。
self.viewControllers = continents;
}

Run一下,Tab出现了,Tab显示内容和每个Tab的Title名是一样的,多于5个Tab时,第五个Tab会显示More,点击More会以列表形式显示其它Tab。

Objective-C之平铺导航、标签导航、树形结构导航  Objective-C之平铺导航、标签导航、树形结构导航


- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
#warning Potentially incomplete method implementation.
// Return the number of sections.
return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
#warning Incomplete method implementation.
// Return the number of rows in the section.
return [_countries count];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell" forIndexPath:indexPath];
cell.textLabel.text = [[_countries objectAtIndex:indexPath.row] objectForKey:@"name"];

return cell;
}

如上在代码中设置好TableView数据后,如图:

Objective-C之平铺导航、标签导航、树形结构导航

2.树形结构导航:

其实在第一步中部分结构已经搭好了,Navigation Controller和Table View Controller,现在继续延伸,点击某个国家进入详情页面。

①创建子Controller并关联类:

打开Main.storyboard,从对象列表中拖拽一个View Controller;

File→New→File...→Cocoa Touch Class创建,命名为DetailViewController,继承自UIViewController;创建字典成员变量details来保存数据源。

再打开Main.stroyboard,点击之前拖拽的View Controller,绑定Class为DetailViewController。

②在TableViewController和ViewController间建立连接:

Main.storyboard:按住Ctrl键,从Contient Scene(Table View Controller)的Cell拖到Detail Scene(View Controller)选择Accessory Action 的push,如图:

Objective-C之平铺导航、标签导航、树形结构导航



选择新连接的线,点击属性检查器,将Identifier设为CountryDetail(有多个Segue时便于在代码中区分),如图:

Objective-C之平铺导航、标签导航、树形结构导航



ContinentViewController.m:实现prepareForSegue:sender:方法,点击Cell时会调用

-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
// 判断segue
if ([segue.identifier isEqualToString:@"CountryDetail"]) {
// 获取目标ViewController
DetailViewController *detail = segue.destinationViewController;
// 设置目标ViewController
detail.details = [_countries objectAtIndex:[[self.tableView indexPathForSelectedRow] row]];
detail.title = [detail.details objectForKey:@"name"];
}
}

Run一下,点击某国家,看看是否会跳转到下一页面。

3.平铺导航:

这里我们介绍平铺导航中的分屏导航,利用的是UIPageController和UIScrollView。这里我把显示内容分为了两页,所以又另外拖拽了两个ViewController,分别创建了对应的类Detail1ViewController和Detail2ViewController,绑定好,不再赘述。

拖拽一个Scroll View和一个Page Controller到Detail Scene上。

  • 点击PageController,打开属性检查器,Pages设为2,设置原点颜色(Tint Color)和当前页颜色(Current Page);
  • 点击ScrollView,打开属性检查器,选择Scrolling Enabled(可通过滑动翻页)和Paging Enabled(以页为单位滚动,如果小于1/2回到当页);

Objective-C之平铺导航、标签导航、树形结构导航

  • 点击该Controller,打开属性检查器,将Under Top Bars 和Under Bottom Bars勾选去掉,这样ScrollView不会显示在导航栏和Tab栏下面。

Objective-C之平铺导航、标签导航、树形结构导航

(不主要)设置Detail1 Scene和Detail2 Scene,这里我分别拖拽了需要的控件上去:Detail1拖拽了三个Label和一个Image View,其中一label和Image View在Detail1ViewController中设置输出口,用于设置首都名和国旗图片。Detail2拖拽了一个Label和一个Image View,其中Image View在Detail2ViewController中设置输出口,用于设置地图图片。

DetailViewController实现UIScrollViewDelegate协议

打开DetailViewController.m通过代码进行设置:(注释即说明)


- (void)viewDidLoad {
[super viewDidLoad];

// 设置scroll view的相对于view的位置和尺寸,尺寸不能大于可显示大小,否则会出现上下滚动的状况。
CGRect frame = CGRectMake(self.view.frame.origin.x, self.view.frame.origin.y, self.view.frame.size.width,
[UIScreen mainScreen].applicationFrame.size.height -
self.tabBarController.tabBar.frame.size.height -
self.navigationController.navigationBar.frame.size.height );

self.scroll.frame = frame;

// scroll view所显示内容的尺寸,高度和其一样,宽度为其宽度*page数
self.scroll.contentSize = CGSizeMake(self.view.frame.size.width * 2, self.scroll.frame.size.height);

// 获取Detail1ViewController和Detail2ViewController的实例,设置数据源
UIStoryboard *mainStoryboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
Detail1ViewController *detail1 = [mainStoryboard instantiateViewControllerWithIdentifier:@"Detail1"];
Detail2ViewController *detail2 = [mainStoryboard instantiateViewControllerWithIdentifier:@"Detail2"];
detail1.labelCapital = [_details objectForKey:@"capital"];
detail1.image = [UIImage imageNamed:[_details objectForKey:@"flag"]];
detail2.image = [UIImage imageNamed:[_details objectForKey:@"map"]];

// 将detail1和detail2的view赋给成员变量,便于控制,设置其分别在scroll view的位置,并添加到scroll view中,使用addSubview。
_page1 = detail1.view;
_page2 = detail2.view;

_page1.frame = CGRectMake(0.0f, 0.0f, self.view.frame.size.width, self.scroll.frame.size.height);
_page2.frame = CGRectMake(self.view.frame.size.width, 0.0f,
self.view.frame.size.width, self.scroll.frame.size.height);

[self.scroll addSubview:_page1];
[self.scroll addSubview:_page2];
}

Run一下,可以滚动翻页了,诶,你会发现Page Controller点击没反应,也没有随翻页而变化,和平时看到的不同啊。下面我们关联一下:

给PageController绑定事件,具体怎么绑定不用说啦,点击PageController拖拽到DetailViewController.h,你懂的。在此我命名为pageChanged:

- (IBAction)pageChanged:(id)sender {
// 获取当前页
NSInteger index = self.pageControl.currentPage;
// 将scroll view滚动到对应位置。
[self.scroll scrollRectToVisible:CGRectMake(self.view.frame.size.width * index, 0.0f, self.view.frame.size.width,self.scroll.frame.size.height) animated:YES];
}

这样点击分屏控件时就会有反应了。

通过手势滚动view时设置分屏控制器的当前页,使用的是scrollViewDidScroll:方法,滚动时会触发该方法:

-(void)scrollViewDidScroll:(UIScrollView *)scrollView{
NSInteger index = scrollView.contentOffset.x / self.view.frame.size.width;
self.pageControl.currentPage = index;
}

嘿嘿,到此导航模式就结束了,Detail1和Detail2显示代码如下(不主要):

Detail1ViewController:

- (void)viewDidLoad {
[super viewDidLoad];

self.capital.text = _labelCapital;
self.imgFlag.image = _image;
self.imgFlag.contentMode = UIViewContentModeTopLeft;
}

Detail2ViewController:

- (void)viewDidLoad {
[super viewDidLoad];

self.imgMap.image = _image;
self.imgMap.contentMode = UIViewContentModeTopLeft;
}

Run一下吧:

Objective-C之平铺导航、标签导航、树形结构导航Objective-C之平铺导航、标签导航、树形结构导航Objective-C之平铺导航、标签导航、树形结构导航