iOS开发7-分页栏与选取器

目标

学会使用分页栏和选取器(选取器视图picker view)。
这里写图片描述

内容

这一篇将构建一个完整的分页栏应用程序,它包含5个不同的分页和5个不同的内容视图。

这里写图片描述
选取器视图(picker view,简称选取器)是带有能够旋转的刻度盘的控件。
选取器可以配置为显示一个或多个刻度盘。默认情况下,选取器显示文本列表,但是它也能够显示图像。

1.Pickers应用程序

这里构建的Pickers应用程序包括一个分页栏。构建该应用程序时,我们将更改默认的分页栏,使它包含5个分页,向每个分页项添加一个图标,然后创建一系列内容视图,并将每个视图连接到一个分页。
应用程序的内容视图将有5种不同的选取器。

1)日期选取器:将要构建的第一个内容视图包含一个日期选取器,这是最容易实现的选取器类型。该视图还将有一个按钮,单击该按钮时,视图将弹出一个警告视图,用于显示选取的日期。
这里写图片描述
2)单滚轮选取器:第二个分页中的选取器包含一组值。此选取器的实现比日期选取器稍微难一些。
这里写图片描述
3)多滚轮选取器:第三个分页中,我们将创建带有两个滚轮的选取器。
这里写图片描述
4)包含依赖滚轮的选取器:这里也是创建带有两个滚轮的选取器,但这次,右侧的滚轮显示的数值会根据左侧选定的值变化而变化。本例中,左侧为州名,右侧为这个州的邮政编码。
这里写图片描述
5)包含图像的自定义选取器:第五个视图,你将了解如何将图像添加到选取器中,这里编写了一个小游戏进行演示,就是小时候常玩的老虎机。
这里写图片描述

2.委托和数据源

在开始构建应用程序之前,先看一下为什么选取器比之前使用的控件都复杂。要使用选取器,仅仅从控件库中拖出来并放在内容视图上然后进行配置是不行滴(日期选取器除外)。除此之外,还需要为选取器提供选取器委托和选取器数据源。

之前的文章中介绍过程序委托和操作表单委托,这里的使用方法与它们一样。选取器将一些工作分配给它的委托。其中最重要的是,确定要为每个滚轮中的每一行绘制的实际内容。选取器要求委托在特定滚轮上的特定位置绘制一个字符串或一个视图。
选取器从委托获取数据。

除了委托外,选取器还需要包含一个数据源。这个程序中,数据源这个名称可能有点词不达意。选取器通过数据源获知滚轮数量和每个滚轮中的行数。数据源的工作原理与委托类似,它的方法会在预告指定的某些时刻被调用。如果未指定数据源和委托,选取器就无法工作,甚至无法绘制出来。

在很多情况下,数据源和委托是同一个对象,该对象也就是包含选取器视图的视图控制器,这个例程就是采用的这种方法。每个内容面板的视图控制器就是其选取器的数据源和委托。

数据源从名字上听,似乎应该是模型的一部分,但是实际上,它是控制器的一部分。这里的数据源通常并不是用于保存数据的对象。

3.创建应用程序

开始创建应用程序,创建一个新项目,同样选择 Single View Application模板,然后一路走下去。这里就不多说了。

3.1.创建视图控制器

上篇创建了根控制器(“根视图控制器”的简称)来管理切换应用程序其他视图的过程。这次依旧需要使用根控制器来控制视图的切换,但是不需要自己创建,因为苹果已经提供了一个非常不错的类来管理分页栏视图,所以我们直接使用UITabBarController实例作为根控制器。

首先,在Xcode中分别为每个视图创建对应的控制器,也就是创建5个新控制器类,根控制器会控制这5个视图控制器之间的切换。
在项目导航面板中展开项目文件夹。点击项目文件夹,按Command + N 或者单击右键或者选择File>New>File…。
这里写图片描述
选择Cocoa Touch然后点Next继续:
这里写图片描述
Class输入DataViewController,父类SubClass选择UIViewController。
这里写图片描述
第一个视图的控制器就创建好了:
这里写图片描述
接下来删除创建时自带的默认ViewController控制器,并按DateViewController控制器的创建方法 ,依次创建SingleViewController、DoubleViewController、DependentViewController、CustomViewController.
这里写图片描述
到这里五个视图控制器就创建完成了。

3.2.添加视图

我们在界面构建器中创建根视图控制器,这它是一个UITabBarController实例。
删除默认的内容视图。
这里写图片描述

3.3.创建分页栏控制器

1)添加分页栏控制器
在控件库中选中Tab Bar Controller 拖放到编辑区,并更改好尺寸。
这里写图片描述

这里写图片描述

这时还要将视图设置为起始视图,也就是程序视图的开始点:
选中Tab Bar Controller然后在属性检查器中选中View Controller下的Is Initial View Controller。
如果没有设置起始视图,程序运行时会出现下面的错误:
这里写图片描述

这里写图片描述
起始视图设置成功后,会发现Tab Bar Controller 左边中间出现了一个指向它的大箭头哦:
这里写图片描述
好了,接回上来的话。
这里拖动发现一次拖出了三个完整的视图控制器以及视图,它们通过曲线连接起来了。
实际上这里不仅仅是一个分页栏控制器,还有两个已经关联好的子控制器。
这个分页栏控制器将成为根控制器。根控制器控制着用户在程序运行时看到的第一个视图。同时负责在其他视图之间进行切换。因为要把每个视图分别连接到分页栏上的一个分页,所以分页栏控件是根控制器的合理选择。
2)添加另外3个控制器
可以看到,上面拖放出来的有一个分页栏控制器(作根控制器)和2个视图,所以我们还要加入3个视图,并关联到分页栏控制器上。
首先,从控件对象库中拖出普通的View Controller到编辑区。接下来按住Control键从分页栏控制器拖动到新视图控制器上然后松开鼠标,并在弹出的面板中选择view controller。这样就会告诉分页栏控制器它还有一个新的子控制器需要管理,分页栏也会立刻出现一个新的分页。
同样的方法,再增加两个View Controller视图到编辑区,并关联到分页栏控制器上。
这里写图片描述
3)修改5个视图控制器的图标和名称
5个视图都有了,并与分页栏控制器建立好了关联,接下来就是更改每个子视图的图标和名称了。
在每个子视图控制器底部都显示了一个矩形图标,一个类似 Item1的名字。这里就是我们要更改的图标和名称的地方了。
在第一个视图控制器中,选中Item1,打开属性检查器后可以在Bar Item部分用来设置标题的Title文本框,和设置图标的Image选择框。
项目图片的添加还是和原来一样,这里就不多说了。
这里写图片描述
这里写图片描述
依次为五个视图设置好对应的图标和标题:
1)Item1 Title:Date Image:clockicon (根据你的图标命名而定)
2)Item2 Title:Single Image:Singleicon
3)Item2 Title:Double Image:doubleicon
4)Item3 Title:Dependent Image:dependenticon
5)Item4 Title:Custom Image:toolicon
这里写图片描述
4)为视图控制器绑定对应的类
选中编辑区中的视图,并在身份检查器中更改Custom Class下的Class。
这里写图片描述
依次为其余4个绑定好。
绑定完成后,我们运行一下试试。
这里写图片描述
表示一切OK,可以进行下一步了。

3.4.实现日期选取器

1)视图设计
在第一个Date视图中,添加Date Picker,并设置这个Date Picker的参数:
这里写图片描述
这里写图片描述
接下来,从控件库中看拖出一个Button按钮,并放在视图正中间。双击按钮,将其标题设置为Select。
这里写图片描述
2)连接输出接口到选取器上
选取器
按住control键从选取器拖到辅助编辑器的源代码@interface和@end之间,确保弹出窗口中的Connection值为Outlet,并在Name文本框中输入名字datePicker,然后按下return以创建输出接口并关联到选取器上。
这里写图片描述
按钮

按住control键从按钮拖到辅助编辑器的源代码中,看到Insert Action这样的气泡提示就可以松开了,给新的操作方法取名buttonPressed并按return键进行关联。
这里写图片描述
3)代码编写
编写代码按钮操作方法:

- (IBAction)buttonPressed:(id)sender
{
    NSDate *selected = [self.datePicker date];
    NSString *message = [[NSString alloc]initWithFormat:@"The data and time you seleected is :%@",selected];
    UIAlertView *alert = [[UIAlertView alloc]
                          initWithTitle:@"Date and Time Selected"
                          message:message
                          delegate:nil
                          cancelButtonTitle:@"That's so true"
                          otherButtonTitles:nil];
    [alert show];
}

然后,向viewDidload:方法中添加几行代码,来实现每次加载这个视图时,选取器都会重置为当前的日期和时间。

- (void)viewDidLoad {
    [super viewDidLoad];
    //确保每次加载都是当前的时间
    NSDate *now = [NSDate date];
    [self.datePicker setDate:now animated:YES];
}

DateViewController.m文件全部代码:

#import "DateViewController.h"

@interface DateViewController ()

@property (weak,nonatomic) IBOutlet UIDatePicker *datePicker;

@end

@implementation DateViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    //确保每次加载都是当前的时间
    NSDate *now = [NSDate date];
    [self.datePicker setDate:now animated:YES];
}

- (IBAction)buttonPressed:(id)sender
{
    NSDate *selected = [self.datePicker date];
    NSString *message = [[NSString alloc]initWithFormat:@"The data and time you seleected is :%@",selected];
    UIAlertView *alert = [[UIAlertView alloc]
                          initWithTitle:@"Date and Time Selected"
                          message:message
                          delegate:nil
                          cancelButtonTitle:@"That's so true"
                          otherButtonTitles:nil];
    [alert show];
}
@end

运行结果:
这里写图片描述

3.5.实现单滚轮选取器

这个例子中,要创建一个NSArray来保存显示在选取器中的值。
选取器本身不会保存任何数据。它们调用其数据源和委托上的方法来获取需要显示的数据。选取器不会关心底层数据位于何处。它在需要时才会请求数据,数据源和委托(实际上,他们常是同一对象)将通过相互协作来提供该数据。因此,数据可以来自一个静态列表,比如本节中使用的数据,也可以从一个文件或URL载入,甚至通过动态组合或计算得到。

因为选取器会向它的控制器请求数据,我们必须确保控制器正确地实现了方法。其中一环是在控制器的接口声明将要实现的两个协议。
在SingleViewController.h文件中,添加如下代码以遵循协议:

#import <UIKit/UIKit.h>

@interface SingleViewController : UIViewController<UIPickerViewDataSource,UIPickerViewDelegate>

@end

1)视图设计
从库中拖放Picker View到第二个分页视图上
这里写图片描述

和之前一样,按住control键从选取器拖到SingleViewController.m的顶部@interface部分,以创建一个名为singlePicker的输出接口。
@interface SingleViewController ()
@property (weak, nonatomic) IBOutlet UIPickerView *singlePicker;
@end
同样,把一个Button拖放到这个视图上,双击该按钮,将其标题设置为Select,按下return键提交更改。并拖取到SingleViewController.m中创建操作方法buttonPressed.

- (IBAction)buttonPressed:(id)sender
{  
}

将选取器视图上的DataSource和Delegate关联到视图控制器上:
选中Single选取器,按住control键从选取器拖到视图的顶部小黄点上,在弹出窗中选择dataSource,再同样操作一次,选择delegate。这就完成了关联。
这里写图片描述
关联完成后,dataSource和delegate前都出现了小圆点:
这里写图片描述
2)将控制器实现为数据源和委托
在SingleViewController.m的@interface部分添加以下属性变量,这样我们就有一个指向某个数组的指针,它包含了几个著名的电影角色名字:

@interface SingleViewController ()
@property (weak, nonatomic) IBOutlet UIPickerView *singlePicker;
@property (strong,nonatomic)          NSArray     *characterNames;
@end

3)代码编写
接下来在viewDidLoad方法中添加这些初始化代码来设定角色数组的内容:

- (void)viewDidLoad {
    [super viewDidLoad];
    //设置显示的数据
    self.characterNames = @[@"Luke",@"Leia",@"Han",@"Chewbacca",
                            @"Artoo",@"Threepio",@"Lando"];
}

然后将下列代码添加到buttonPressed方法中:

- (IBAction)buttonPressed:(id)sender
{   //询问选中的是哪一行
    NSInteger row = [self.singlePicker selectedRowInComponent:0];
    //取出选中行的字符串
    NSString *selected = self.characterNames[row];

    NSString *title = [[NSString alloc]initWithFormat:@"You selected %@!",selected];
    //在弹出的警告窗中显示选中的数据。
    UIAlertView *alert = [[UIAlertView alloc]initWithTitle:title
                                                   message:@"Thank you for choosing."
                                                  delegate:nil
                                         cancelButtonTitle:@"You're Welcome"
                                         otherButtonTitles: nil];
    [alert show];
}

最后,将以下代码插入到文件后:

#pragma mark - Picker Data Source Methods
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView
{
    return 1;
}

- (NSInteger)pickerView:(UIPickerView *)pickerView
numberOfRowsInComponent:(NSInteger)component
{
    return [self.characterNames count];
}

以上这两个方法都来自UIPickerViewDataSource协议,所有选取器(除日期选取器)都必须实现这两个方法,所以你猜也猜得到,之后的选取器还会见到这两个方法 。

- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView
{
    return 1;
}

选取器会包含多个旋转滚轮,这就是选取器会询问应该显示几个滚轮的原因。这次只想显示一个列表,因此返回1。
注意,这里有一个UIPickerView作为参数传到这个方法中。这个参数指向触发这个方法的选取器视图,这样一来,同一个数据源就能够控制多个选取器。本例中,只有一个选取器,所以可以直接忽略这个参数,因为已经知道哪个选取器在调用这个方法。
选取器使用第二个数据源方法给定的选取器滚轮应该包含多少行数据:

- (NSInteger)pickerView:(UIPickerView *)pickerView
numberOfRowsInComponent:(NSInteger)component
{
    return [self.characterNames count];
}

这里直接返回数组中的对象数量就可。

在两个数据源方法后,实现了一个委托访。与数据源不同,所有委托方法都是可选的。可选(optional)这个词带有一定的欺骗性,因为实际上可以一个委托方法都不实现。但是,如果想在选择器中显示文本以外的内容,必须实现另一个方法,这会在自定义选取器时会讲解。

#pragma mark - Picker Delegate Methods
- (NSString *)pickerView:(UIPickerView *)pickerView
             titleForRow:(NSInteger)row
            forComponent:(NSInteger)component
{
    return self.characterNames[row];
}

在这个方法中,选取器要求提供指定滚轮中指定行的数据。参数提供了一个指向正在请求数据的选取器的指针,以及它请求的滚轮和行。由于这个视图中只有一个选取器,且该选取器只有一个滚轮,因此可以忽略除row参数之外的其他参数,使用row参数作为索引返回数据数组中相应的元素。

#pragma  是什么

从技术角度来说,任何以#pragma开头的代码都是一条编译器指令。而这里,#pragma指令算是写给Xcode看的,与编译器无关,它告诉Xode要在编辑面板顶部的方法和函数弹出菜单中插入一条分隔线。
主要为了代码导航,方便代码查找和阅读。

SingleViewController.m全部代码如下:

#import "SingleViewController.h"

@interface SingleViewController ()

@property (weak,nonatomic)   IBOutlet UIPickerView    *singlePicker;
@property (strong,nonatomic)          NSArray         *characterNames;

@end

@implementation SingleViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    //设置显示的数据
    self.characterNames = @[@"Luke",@"Leia",@"Han",@"Chewbacca",
                            @"Artoo",@"Threepio",@"Lando"];
}
//按钮响应的方法
- (IBAction)buttonPressed:(id)sender
{   //询问选中的是哪一行
    NSInteger row = [self.singlePicker selectedRowInComponent:0];
    //取出选中行的字符串
    NSString *selected = self.characterNames[row];

    NSString *title = [[NSString alloc]initWithFormat:@"You selected %@!",selected];
    //在弹出的警告窗中显示选中的数据。
    UIAlertView *alert = [[UIAlertView alloc]initWithTitle:title
                                                   message:@"Thank you for choosing."
                                                  delegate:nil
                                         cancelButtonTitle:@"You're Welcome"
                                         otherButtonTitles: nil];
    [alert show];
}
//编译器方指令,方便导航
#pragma mark - Picker Data Source Methods
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView
{
    return 1;
}

- (NSInteger)pickerView:(UIPickerView *)pickerView
numberOfRowsInComponent:(NSInteger)component
{
    return [self.characterNames count];
}

#pragma mark - Picker Delegate Methods
- (NSString *)pickerView:(UIPickerView *)pickerView
             titleForRow:(NSInteger)row
            forComponent:(NSInteger)component
{
    return self.characterNames[row];
}

@end

运行结果:
这里写图片描述

3.6.实现多滚轮选取器

这个选取器带有两个相互独立的滚轮,左侧为三分治馅料列表,右侧为面包类型。
1)视图设计
详细操作参考上一个,操作步骤相同。
在视图中添加一个选取器和一个按钮,将按钮名称改为Select,然后创建必要的关联。
(1)创建一个名为doublePicker输出接口并将视图控制器关联到选取器。
(2)使用关联检查器,将选取器视图上的DataSource和Delegate关联到视图控制器。
(3)使用关联检查器,将按钮的Touch Up Inside事件关联到视图控制器上名为buttonPressed的新操作方法。

2)代码编写

#import "DoubleViewController.h"

#define kFillingComponent       0           //左侧滚轮,三明治馅料
#define kBreadComponent       1           //右侧滚轮,面包类型

@interface DoubleViewController ()

@property (weak,    nonatomic)IBOutlet UIPickerView *doublePicker;
//创建两个对应的数组指针
@property (strong,  nonatomic)         NSArray      *fillingTypes;
@property (strong,  nonatomic)         NSArray      *breadTypes;

@end

@implementation DoubleViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    //滚轮列表的内容数组
    self.fillingTypes = @[@"Ham",@"Turkey",@"Peanut Butter",@"Tuna Salad",
                          @"Chicken Salad",@"Roast Beef",@"Vegemite"];
    self.breadTypes   = @[@"White",@"Whole Wheat",@"Rye",@"Sourdough",
                          @"Seven Grain"];
}

- (IBAction)buttonPressed:(id)sender
{
    //获取当前选中行的行号
    NSInteger fillingRow = [self.doublePicker selectedRowInComponent:kFillingComponent];
    NSInteger breadRow   = [self.doublePicker selectedRowInComponent:kBreadComponent];
    //取出当前行的内容
    NSString *filling = self.fillingTypes[fillingRow];
    NSString *bread   = self.breadTypes[breadRow];
    //组合成消息字符串
    NSString *message = [[NSString alloc]initWithFormat:@"Your %@ on %@ bread will be right up.",filling,bread];
    //弹出警告窗
    UIAlertView *alert = [[UIAlertView alloc]initWithTitle:@"Thank you for your order"
                                                   message:message
                                                  delegate:nil
                                         cancelButtonTitle:@"Great!"
                                         otherButtonTitles:nil];
    [alert show];
}

#pragma mark - Picker Data Source Methods
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView
{   //2个滚轮
    return 2;
}

- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component
{   //对应滚轮,返回对应的内容行数
    if (component == kBreadComponent) {
        return [self.breadTypes count];
    }else{
        return [self.fillingTypes count];
    }
}

#pragma mark - Picker Delegate Methods
- (NSString *)pickerView:(UIPickerView *)pickerView
             titleForRow:(NSInteger)row
            forComponent:(NSInteger)component
{   //为对应行显示相应的内容
    if (component == kBreadComponent) {
        return self.breadTypes[row];
    }else{
        return self.fillingTypes[row];
    }
}
@end 

运行结果:
这里写图片描述

3.7. 实现依赖滚轮的选取器

这里将创建两个有依赖关系带两个滚轮的选取器,左侧为州名,右侧为左侧州所有的邮编号。左侧变动,则右侧的内容也跟着变动。
和上一个视图一样,这里声明两个数组,分别对应一个滚轮。还需要实现一个NSDictionary字典,在这个字典中,每个州都有一个对应NSArray,来存放对应的邮编号。随后实现一个委托方法,该方法将在选取器的选定项改变时通知我们。如果左侧的值改变,我们将从字典中提取正确的数组
1)视图设计
和上一个视图一样:
(1)在这个内容视图上拖放Picker View选取器和Button按钮,改按钮的名为Select。
(2)将选取器的delegate和dataSource输出接口都关联到视图控制器上。
(3)将dependentPicker的属性变量关联到选取器,一个空的buttonPressed:方法关联到按钮上。
2)代码编写
在编写代码之前,需要创建显示的数据。到目前为止,都是在代码中创建了数组来存放字符串数据。但这个例子就不适用了,因为要输入的数据太多,所以这里使用了更合适的方法来解决这个问题。这里将使用一个属性列表载入数据。
这里复制一个属性列表到项目中。


#import "DependentViewController.h"

#define kStateComponent 0
#define kZipComponent   1

@interface DependentViewController ()

@property (weak,  nonatomic)IBOutlet    UIPickerView *dependentPicker;

@property (strong,nonatomic)            NSDictionary *stateZips;
@property (strong,nonatomic)            NSArray      *states;
@property (strong,nonatomic)            NSArray      *zips;

@end

@implementation DependentViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    //从归档文件中读出数据,保存进数组:zips
    NSBundle *bundle = [NSBundle mainBundle];//获取项目中Resouce文件夹的资源
    NSURL    *plistURL = [bundle URLForResource:@"statedictionary"
                                  withExtension:@"plist"];//取出statedictionary.plist的路径

    self.stateZips = [NSDictionary dictionaryWithContentsOfURL:plistURL];//将属性文档中的内容存入stateZips字典中

    NSArray *allStates = [self.stateZips allKeys];//取出关键字,存入allStates数组中。
    NSArray *sortedStates = [allStates sortedArrayUsingSelector:@selector(compare:)];
    self.states = sortedStates;

    NSString *selectedState = self.states[0];
    self.zips = self.stateZips[selectedState];
}

- (IBAction)buttonPressed:(id)sender
{
    NSInteger stateRow = [self.dependentPicker selectedRowInComponent:kStateComponent];
    NSInteger zipRow   = [self.dependentPicker selectedRowInComponent:kZipComponent];

    NSString *state = self.states[stateRow];
    NSString *zip   = self.zips[zipRow];

    NSString *title   = [[NSString alloc]initWithFormat:@"You seleted zip code %@.",zip];
    NSString *message = [[NSString alloc]initWithFormat:@"%@ is in %@",zip,state];

    UIAlertView *alert = [[UIAlertView alloc]initWithTitle:title
                                                   message:message
                                                  delegate:nil
                                         cancelButtonTitle:@"OK"
                                         otherButtonTitles:nil];
    [alert show];
}

#pragma mark - Picker Data Source Methods
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView
{
    return 2;
}

- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component
{
    if (component == kStateComponent) {
        return [self.states count];
    }else{
        return [self.zips count];
    }
}

#pragma mark - Picker Delegate Methods
- (NSString *)pickerView:(UIPickerView *)pickerView
             titleForRow:(NSInteger)row
            forComponent:(NSInteger)component
{
    if (component == kStateComponent) {
        return self.states[row];
    }else{
        return self.zips[row];
    }
}

- (void)pickerView:(UIPickerView *)pickerView
      didSelectRow:(NSInteger)row
       inComponent:(NSInteger)component
{
    if (component == kStateComponent) {
        NSString *selectedState = self.states[row];
        self.zips = self.stateZips[selectedState];
        [self.dependentPicker reloadComponent:kZipComponent];
        [self.dependentPicker selectRow:0
                            inComponent:kZipComponent
                               animated:YES];
    }
}

- (CGFloat)pickerView:(UIPickerView *)pickerView
    widthForComponent:(NSInteger)component
{
    if (component == kZipComponent) {
        return 90;
    }else{
        return 200;
    }
}
@end

在上面的viewDidLoad方法中,首先获取应用程序包的引用 。
NSBundle *bundle = [NSBundle mainBundle];
那么什么是包呢?包(bundle)只是一种特定类型的文件夹,其中的内容遵循特定的结构。应用程序和框架都是包这个调用返回的包对象表示我们的应用程序。
NSBundle的一个主要作用是获取添加到项目的Resources文件夹中的资源。构建应用程序时,这些文件将被复制到应用程序的包中。我们之前已经在项目中添加过图像等资源,但是到现在为止,只是在界面构建器中使用。如果想在代码中使用这些资源,通常需要使用NSBundle。
我们将使用包获取所需资源的路径:

NSURL    *plistURL = [bundle URLForResource:@"statedictionary"
                                  withExtension:@"plist"];

这将返回一个URL,内容是statedictionary.plist文件的位置。然后可以根据这个URL创建一个NSDictionary对象。之后,属性列表的所有内容将被载入到新创建的NSDictionary对象中,然后将这个对象分配给stateZips。

self.stateZips = [NSDictionary dictionaryWithContentsOfURL:plistURL];

刚才载入的字典使用州名作为键,键对应的值是一个NSArray,其中包含所选州的邮政编码,为了填充左侧滚轮的数组,我们从字典获取一个包含所有键的列表,并将这个列表赋给states数组。在赋值前,先对这个列表字母顺序进行排序。

    NSArray *allStates = [self.stateZips allKeys];//取出关键字,存入allStates数组中。
    NSArray *sortedStates = [allStates sortedArrayUsingSelector:@selector(compare:)];
    self.states = sortedStates;

除非特别指定初始时选中的值,否则选取器将从选择的第一行开始(行0)。为了获取与states数组中的第一行相对应的zips数组,我们从states数组提取索引为0的对象。这将返回启动时默认选择的州名。然后使用这个州句提取该州对应的邮政编码数组,将这个数组赋值给zips数组,zips数组用于向右侧滚轮提供数据。

    NSString *selectedState = self.states[0];
    self.zips = self.stateZips[selectedState];

两个数据源方法实际都与上一个版本相同。在合适的数组中返回行数。
实现的第一个委托方法也是与上一个版本相同。
而第二个委托方法是全新的:

- (void)pickerView:(UIPickerView *)pickerView
      didSelectRow:(NSInteger)row
       inComponent:(NSInteger)component
{
    if (component == kStateComponent) {
        NSString *selectedState = self.states[row];
        self.zips = self.stateZips[selectedState];
        [self.dependentPicker reloadComponent:kZipComponent];
        [self.dependentPicker selectRow:0
                            inComponent:kZipComponent
                               animated:YES];
    }
}

只要选择器发生变化,就会调用这个方法在这个方法中检查component参数就可以知道发生变化的是否是左侧的滚轮。
如果确实是左侧的滚轮发生了变化 ,就提取新选择的项对应的数组,并将它赋给zips数组。然后,将右侧滚轮设置为选中第一行,并重新加载右侧滚轮。

最后发现两个滚轮的宽度是一样的,然州名无法全部显示,所以这里要调整一下两个滚轮的宽度:

- (CGFloat)pickerView:(UIPickerView *)pickerView
    widthForComponent:(NSInteger)component
{
    if (component == kZipComponent) {
        return 90;
    }else{
        return 200;
    }
}

运行测试一下。

3.8.实现图像滚轮的选取器

这里将创建一个老虎机的游戏。说简单点就是个图片滚轮,再加上随机数。
1)视图设计
构建视图还是和之前一样的:
这里写图片描述
(1)在这个内容视图上拖放Picker View选取器和Button按钮,改按钮的名为spin。
(2)将选取器的delegate和dataSource输出接口都关联到视图控制器上。
(3)将dependentPicker的属性变量关联到选取器;将一个空的spin:方法关联到spin按钮上;winLabel的输出接口关联到视图的Label控件上。
最后的输出接口如下:

@property (weak,  nonatomic) IBOutlet UIPickerView *picker;
@property (weak,  nonatomic) IBOutlet UILabel      *winLabel;
@property (weak,  nonatomic) IBOutlet UIButton     *button;

(4)选中选取器,打开属性面板取消选择View设置底部的User Interaction Enable复选框,这样用户就不能够手动更改刻度盘作弊了。
最后的连接检查面板:
这里写图片描述
2)添加图像资源
现在需要添加将在游戏中使用的图像。添加到项目图像文件中的6组图像文件(seven.png、bar.png、crown.png、cherry.png、lemon.png、apple.png和所有图像的@2X版本)。和之前对分页栏图标进行的处理一样,将所有这些文件拖动到Xcode的Images.xcasset文件夹中。
这里写图片描述
3)实现控制器
在CustomViewController.m文件中的viewDidload方法中,实现初始化图片和随机数:

- (void)viewDidLoad {
    [super viewDidLoad];
    //载入6个不同的图像,提供一个随机数生成器
    self.images = @[[UIImage imageNamed:@"seven"],
                    [UIImage imageNamed:@"bar"],
                    [UIImage imageNamed:@"crown"],
                    [UIImage imageNamed:@"cherry"],
                    [UIImage imageNamed:@"lemon"],
                    [UIImage imageNamed:@"apple"]];
    srandom(time(NULL));
}

在CustomViewController.m文件中,实现之前写的spin:方法:

- (IBAction)spin:(id)sender
{
    BOOL win = NO;          //判断一行中是否出现了3个连续相同行的标志
    int numInRow = 1;           //跟踪到目前为止一行中连续相同的值出现的次数
    int lastVal = -1;               //记录前一个滚轮的值,以便与当前值进行比较
//通过循环将所有5个滚轮的当前行设置为一个新的随机行。根据images数组的元素个数来选取随机数。
    for (int i = 0; i < 5; i++) {
        int newValue = random() % [self.images count];
        //将新值与上一个值进行比较,如果匹配,则将numInRow加1.如不匹配,则重置为1.
    //然后将新值赋给lastVal.这样,就可以在下一次循环中用它进行比较。
        if (newValue == lastVal) {
            numInRow ++;
        }else{
            numInRow = 1;
        }
        lastVal = newValue;
 //将相应的滚轮设置为新值,通知这个滚轮使用动画效果进行改变,并通知选取器重新加载这个滚轮
        [self.picker selectRow:newValue inComponent:i animated:YES];
        [self.picker reloadComponent:i];
//每次循环的最后一件事就是,查看一行中是否出现了3个连续相同的图像,如果是,win=YES.
        if (numInRow >= 3) {
            win = YES;
        }
    }

spin方法将在用户点击spin按钮时触发。
然后自然就是滚轮的数据源方法和代理方法:

#pragma mark - Picker Data Source Methods
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView
{
    return 5;
}

- (NSInteger)pickerView:(UIPickerView *)pickerView
numberOfRowsInComponent:(NSInteger)component
{
    return [self.images count];
}

#pragma mark - Picker Delegate Methods
- (UIView *)pickerView:(UIPickerView *)pickerView
            viewForRow:(NSInteger)row
          forComponent:(NSInteger)component
           reusingView:(UIView *)view
{
    UIImage *image = self.images[row];
    UIImageView *imageView = [[UIImageView alloc]initWithImage:image];
    return imageView;
}

4)细节优化
这个小游戏构建方法很简单,但还有很多地方要改进一下:
没有声音
刻度盘还没有停止,游戏就告诉我们已经获胜了,这感觉不科学。
因此针对这两个细节,我们进行改进。
为项目添加两个声音文件:crunch.wav和win.wav。将这两文件添加到项目的文件夹中。这两个声音在用户单击spin按钮和获胜时播放。
要使用声音,就需要访问iOS Audio Toolbox中的类。 在CustomViewController.m文件中插入这样一行代码:

#import <AudioToolbox/AudioToolbox.h>

需要一个实例变量来保存声音的引用。

@implementation CustomViewController{
    SystemSoundID winSoundID;
    SystemSoundID crunchSoundID;
}

还需要在控制器类添加两个方法。

- (void)showButton
{
    self.button.hidden = NO;        //显示按钮
}

- (void)playWinSound
{
    if (winSoundID == 0 ) {     //判断声音是否加载完成
        NSURL *soundURL = [[NSBundle mainBundle]URLForResource:@"win" withExtension:@"wav"];
        AudioServicesCreateSystemSoundID((__bridge CFURLRef)soundURL, &winSoundID);
    }
    AudioServicesPlaySystemSound(winSoundID);   //播放声音
    self.winLabel.text = @"WINNING !";
    [self performSelector:@selector(showButton) withObject:nil afterDelay:1.5];
}

第一个方法用于显示按钮,用户单击按钮之后将其隐藏,因为如果滚轮还在旋转,就不应该让它们在停止之前再次开始旋转。

第二个方法将在用户获胜时调用。首先,检查声音是否已经加载完成。实例变量会初始化为0,而有效的声音标识符不会是0,所以通过比较实例变量的值是否为0就能知道声音是否已经加载完成。
然后,分页设置为“WINNING!”,并调用showButton方法,但这里是通过performSelector: withObject:afterDelay:方法来调用showButton方法。这是一个非常方便的方法,所有对象都可以调用它。它支持在未来的某个时间对方法进行调用。

还需要对spin:方法进行一些更改。需要编写代码来播放声音,并在玩家获胜之后调用playerWon方法。

- (IBAction)spin:(id)sender
{
    BOOL win = NO;
    int numInRow = 1;
    int lastVal = -1;
    for (int i = 0; i < 5; i++) {
        int newValue = random() % [self.images count];

        if (newValue == lastVal) {
            numInRow ++;
        }else{
            numInRow = 1;
        }
        lastVal = newValue;

        [self.picker selectRow:newValue inComponent:i animated:YES];
        [self.picker reloadComponent:i];
        if (numInRow >= 3) {
            win = YES;
        }
    }

    if (crunchSoundID == 0) {
        NSString *path = [[NSBundle mainBundle]pathForResource:@"crunch" ofType:@"wav"];
        NSURL *soundURL = [NSURL fileURLWithPath:path];
        AudioServicesCreateSystemSoundID((__bridge CFURLRef)soundURL, &crunchSoundID);
    }
    AudioServicesPlaySystemSound(crunchSoundID);

    if (win) {
        [self performSelector:@selector(playWinSound) withObject:nil afterDelay:0.5];
    }else{
        [self performSelector:@selector(showButton) withObject:nil afterDelay:0.5];
    }

    self.button.hidden = YES;
    self.winLabel.text = @"";
}

5)全部代码

#import "CustomViewController.h"
#import <AudioToolbox/AudioToolbox.h>

@interface CustomViewController ()

@property (strong,nonatomic)          NSArray      *images;

@property (weak,  nonatomic) IBOutlet UIPickerView *picker;
@property (weak,  nonatomic) IBOutlet UILabel      *winLabel;
@property (weak,  nonatomic) IBOutlet UIButton     *button;

@end

@implementation CustomViewController{
    SystemSoundID winSoundID;
    SystemSoundID crunchSoundID;
}

- (void)viewDidLoad {
    [super viewDidLoad];

    self.images = @[[UIImage imageNamed:@"seven"],
                    [UIImage imageNamed:@"bar"],
                    [UIImage imageNamed:@"crown"],
                    [UIImage imageNamed:@"cherry"],
                    [UIImage imageNamed:@"lemon"],
                    [UIImage imageNamed:@"apple"]];
    srandom(time(NULL));
}

- (IBAction)spin:(id)sender
{
    BOOL win = NO;
    int numInRow = 1;
    int lastVal = -1;
    for (int i = 0; i < 5; i++) {
        int newValue = random() % [self.images count];

        if (newValue == lastVal) {
            numInRow ++;
        }else{
            numInRow = 1;
        }
        lastVal = newValue;

        [self.picker selectRow:newValue inComponent:i animated:YES];
        [self.picker reloadComponent:i];
        if (numInRow >= 3) {
            win = YES;
        }
    }

    if (crunchSoundID == 0) {
        NSString *path = [[NSBundle mainBundle]pathForResource:@"crunch" ofType:@"wav"];
        NSURL *soundURL = [NSURL fileURLWithPath:path];
        AudioServicesCreateSystemSoundID((__bridge CFURLRef)soundURL, &crunchSoundID);
    }
    AudioServicesPlaySystemSound(crunchSoundID);

    if (win) {
        [self performSelector:@selector(playWinSound) withObject:nil afterDelay:0.5];
    }else{
        [self performSelector:@selector(showButton) withObject:nil afterDelay:0.5];
    }

    self.button.hidden = YES;
    self.winLabel.text = @"";
}

- (void)playWinSound
{
    if (winSoundID == 0 ) {
        NSURL *soundURL = [[NSBundle mainBundle]URLForResource:@"win" withExtension:@"wav"];
        AudioServicesCreateSystemSoundID((__bridge CFURLRef)soundURL, &winSoundID);
    }
    AudioServicesPlaySystemSound(winSoundID);
    self.winLabel.text = @"WINNING !";
    [self performSelector:@selector(showButton) withObject:nil afterDelay:1.5];
}

- (void)showButton
{
    self.button.hidden = NO;
}

#pragma mark - Picker Data Source Methods
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView
{
    return 5;
}

- (NSInteger)pickerView:(UIPickerView *)pickerView
numberOfRowsInComponent:(NSInteger)component
{
    return [self.images count];
}

#pragma mark - Picker Delegate Methods
- (UIView *)pickerView:(UIPickerView *)pickerView
            viewForRow:(NSInteger)row
          forComponent:(NSInteger)component
           reusingView:(UIView *)view
{
    UIImage *image = self.images[row];
    UIImageView *imageView = [[UIImageView alloc]initWithImage:image];
    return imageView;
}

- (CGFloat)pickerView:(UIPickerView *)pickerView
rowHeightForComponent:(NSInteger)component
{
    return 70;
}
@end

小结

已经掌握了分页栏应用程序和选取器的基础知识。
并构建了一个非常完整的分页栏应用程序。
学习了如何在各种不同配置下使用选取器。
还介绍了选择器的数据源和委托。
最后,还介绍了如何载入图像和声音。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值