盼秋风,徐徐而来,吹尽炎热,捎来些许凉意。
望秋水,秋雨来临,散尽暑气,带来一夜好眠。
来说一下导航控制器。导航一般分三种:平铺导航、标签导航、树形结构导航。
平时我们大家经常见的使用导航的样式如下:
平铺导航:应用界面(基于分屏导航),电子书(基于分页导航)
标签导航:苹果的Photos等。
树形结构导航:苹果的Settings等。
现在我们通过一个国家列表Demo来介绍实现这三种导航模式。
首先先创建了一个plist文件,存储要使用的测试数据,如下:
鉴于我们之前用的都是故事板,下面我们就继续使用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。这二者自此关联。
④创建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。
- (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数据后,如图:
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,如图:
选择新连接的线,点击属性检查器,将Identifier设为CountryDetail(有多个Segue时便于在代码中区分),如图:
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回到当页);
- 点击该Controller,打开属性检查器,将Under Top Bars 和Under Bottom Bars勾选去掉,这样ScrollView不会显示在导航栏和Tab栏下面。
(不主要)设置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一下吧: