包含依赖的多滚轮选择器
和单滚轮选择器相似,所以一些简单的布局、关联等操作就略过了,可以参考单滚轮选择器。
目标:
这个选择器将有2个滚轮,左边选择州,右边选择对应州的邮编。
思路:
所谓包含依赖就是说具有联动效果,一个滚轮根据另一个滚轮的变化来变化。
所以我们只要能够监听某一个滚轮的滚动事件,然后动态调整另一个滚轮的绑定数据就能实现“包含依赖的多滚轮选择器”了。
代码讲解:
首先定义1个NSDictionary和2个NSArray,NSDictionary用来加载文件以保存所有数据,2个NSArray则分别作为两个滚轮的数据源。
因为邮编是和州对应的,而且有很多州,所以我们可以用NSDictionary来存储所有的州和邮编,使用州作为key,对应的邮编组装成一个nsarray作为value
这里提供一个plist文件,这个例子中我们将从文件中加载数据。文件下载
应该在界面加载之后(viewDidLoad)就加载文件,获取数据,加载文件的方法如下:
NSBundle *bundle = [NSBundle mainBundle]; NSURL *plistURL = [bundle URLForResource:@"statedictionary" withExtension:@"plist"]; self.stateZips = [NSDictionary dictionaryWithContentsOfURL:plistURL];
NSBundle看以看做是一个目录,而mainBundle就是程序的主目录,然后通过文件名和文件格式获取指定文件的路径,再然后使用dictionaryWithContentsOfURL:方法初始化NSDictionary就可以了。dictionaryWithContentsOfURL:是使用文件的路径加载文件数据到NSDictionary中。
按钮的逻辑与之前的一样,在此略过。
在实现普通多滚轮选择器中的两个数据源方法和1个委托方法的基础上,再实现另一个委托方法,
pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component
这个方法会在滚轮滚动时调用,这样我们就能监听某一个滚轮的值是否改动,从而改动另一个滚轮的数据了。
NSString *selectState = self.states[row]; self.zips = self.stateZips[selectState]; [self.dependentPicker reloadComponent:kZipComponent];
如果是控制州的滚轮变化,就获取选择的州,然后从NSDictionary中获取对应的邮编NSArray,让选择邮编的滚轮重新加载新数据源。
优化:
1、排序
可能从plist文件中的加载到NSDictionary之后州的排序并不是我们想要的,或者想优化用户体验,按照字母(或其他规则)排序。我们应该这么做
NSArray *sortedStates =
[allStates sortedArrayUsingSelector:@selector(compare:)];
2、默认
可以使州默认选中第一个,然后选择对应的邮编
NSString *selectedState = self.states[0]; self.zips = self.stateZips[selectedState];
3、界面宽度
因为州的名字比较长,邮编的宽度是一定的,所以我们可以让控制州的滚轮占据选择器的更多宽度。
要实现这个效果,就必须实现另一个委托方法:
pickerView:(UIPickerView *)pickerView widthForComponent:(NSInteger)component
这个方法会返回CGFloat值,指示某一个滚轮的宽度。
完整代码:
1 // 2 // DependentComponentPickerViewController.m 3 // Pickers 4 // 5 // Created by 张光发 on 15/10/13. 6 // Copyright (c) 2015年 张光发. All rights reserved. 7 // 8 9 #import "DependentComponentPickerViewController.h" 10 #define kStateComponent 0 11 #define kZipComponent 1 12 13 @interface DependentComponentPickerViewController () 14 @property(strong, nonatomic) NSDictionary *stateZips; 15 @property(strong, nonatomic) NSArray *states; 16 @property(strong, nonatomic) NSArray *zips; 17 @property(weak, nonatomic) IBOutlet UIPickerView *dependentPicker; 18 19 @end 20 21 @implementation DependentComponentPickerViewController 22 23 - (void)viewDidLoad { 24 [super viewDidLoad]; 25 // Do any additional setup after loading the view. 26 27 //!!!: 可以认为程序的主目录就是mainBundle 28 NSBundle *bundle = [NSBundle mainBundle]; 29 NSURL *plistURL = 30 [bundle URLForResource:@"statedictionary" withExtension:@"plist"]; 31 self.stateZips = [NSDictionary dictionaryWithContentsOfURL:plistURL]; 32 NSArray *allStates = [self.stateZips allKeys]; 33 //!!!: 这个地方的排序方法是nsarray自带的compare: 34 NSArray *sortedStates = 35 [allStates sortedArrayUsingSelector:@selector(compare:)]; 36 self.states = sortedStates; 37 38 NSString *selectedState = self.states[0]; 39 self.zips = self.stateZips[selectedState]; 40 } 41 42 - (IBAction)buttonPressed:(id)sender { 43 NSInteger stateRow = [self.dependentPicker selectedRowInComponent:0]; 44 NSInteger zipRow = [self.dependentPicker selectedRowInComponent:1]; 45 46 NSString *state = self.states[stateRow]; 47 NSString *zip = self.zips[zipRow]; 48 NSString *title = 49 [[NSString alloc] initWithFormat:@"你选择的邮政编码是%@", zip]; 50 NSString *message = [[NSString alloc] 51 initWithFormat:@"你选择了%@的州%@邮编", state, zip]; 52 UIAlertView *alert = [[UIAlertView alloc] initWithTitle:title 53 message:message 54 delegate:nil 55 cancelButtonTitle:@"OK" 56 otherButtonTitles:nil, nil]; 57 [alert show]; 58 } 59 60 - (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView { 61 return 2; 62 } 63 64 - (NSInteger)pickerView:(UIPickerView *)pickerView 65 numberOfRowsInComponent:(NSInteger)component { 66 if (component == kStateComponent) { 67 return [self.states count]; 68 } else { 69 return [self.zips count]; 70 } 71 } 72 73 - (NSString *)pickerView:(UIPickerView *)pickerView 74 titleForRow:(NSInteger)row 75 forComponent:(NSInteger)component { 76 if (component == kStateComponent) { 77 return self.states[row]; 78 } else { 79 return self.zips[row]; 80 } 81 } 82 83 // TODO: 滚轮变化时调用 84 - (void)pickerView:(UIPickerView *)pickerView 85 didSelectRow:(NSInteger)row 86 inComponent:(NSInteger)component { 87 if (component == kStateComponent) { 88 /** 89 * 获取左滚轮选取的值 90 * 查找对应邮编 91 * 右滚轮重新加载数据 92 * 右滚轮默认选择第0行 93 */ 94 NSString *selectState = self.states[row]; 95 self.zips = self.stateZips[selectState]; 96 [self.dependentPicker reloadComponent:kZipComponent]; 97 [self.dependentPicker selectRow:0 inComponent:kZipComponent animated:YES]; 98 } 99 } 100 // TODO: 设置滚轮宽度 101 - (CGFloat)pickerView:(UIPickerView *)pickerView 102 widthForComponent:(NSInteger)component { 103 if (component == kStateComponent) { 104 return self.view.frame.size.width * 0.6; 105 } else { 106 return self.view.frame.size.width * 0.3; 107 } 108 } 109 @end
效果图:
包含图像的自定义选择器制作的简易小游戏
目标:
这次综合利用之前几个选择器中的知识,用picker view制作一个包含5个滚轮的简易老.虎.机.(敏感词汇 -_-||| )
思路:
如果把之前的几个小例子都看完了,做这个应该是很轻松的。
重点在图片的处理和判断是否win的算法,难点是声音部分。
界面布局:
界面顶部添加一个picker view,pickerview下依次是一个label和一个Button。
button用来启动游戏,label显示游戏结果。
代码讲解:
首先定义1个NSArray来存放滚轮上显示的图片,1个NSMutableDictionary来存放一次游戏的运行结果。
在viewDidLoad中就要把图片加载到nsarray中,而且要初始化一个随机数生成器,在后面转动滚轮时会用到随机数。
srandom(time(NULL));//初始化随机数生成器
button的点击事件处理:
首先初始化一些局部变量,把NSMutableDictionary初始化,每个uiimage作为一个Key,每个Key的初始值为1。初始化一个BOOL变量,并设置为NO,作为是否胜利的标识。
NSNumber* initNumber = [NSNumber numberWithInt:0]; self.selects = [NSMutableDictionary dictionaryWithObjectsAndKeys: initNumber, self.images[0], initNumber, self.images[1], initNumber, self.images[2], initNumber, self.images[3], initNumber, self.images[4],
initNumber, self.images[5],nil];
BOOL win = NO;
接下来就是为每个滚轮设置随机显示一个图像,并记录每个滚轮显示的图像内容.
为每个滚轮选取一个随机图像使用的方法是用随机数和图像个数取模.
每个滚轮选取的图像内容是通过NSMutableDictionary来记录的,首先找到当前选取的uiimage,根据这个uiimage找NSMutableDictionary中的对应记录,并把对应的Key做+1处理。
for (int i = 0; i < 5; i++) { NSInteger newVal = random() % [self.images count]; UIImage* img = self.images[newVal]; NSInteger val = [[self.selects objectForKey:img] integerValue]; val++; [self.selects setObject:[NSNumber numberWithInteger:val] forKey:[img copy]]; //为相应滚轮设置新值 [self.picker selectRow:newVal inComponent:i animated:YES]; //这行代码是让picker重新加载相应滚轮,可是注释掉也能正常运行,请大神指教 //[self.picker reloadComponent:i]; }
所有的滚轮都设置完成之后就要判断 本次游戏的结果了,如果有图片出现了3次或以上就判定为本次游戏胜利。
这是通过判断之前的NSMutableDictionary中的allValue来实现的。
for (NSNumber* j in [self.selects allValues]) { if ([j integerValue] >= 3) { win = YES; break; } }
为了提升游戏乐趣,可以在这些处理完成之后播放一次滚轮转动的声音
要包含“AudioToolbox/AudioToolbox.h”
首先判断声音是否播放完成,如果播放完成实例会被初始化为0
那就要根据声音文件的NSURL信息初始化实例,然后播放
if (crunchSoundID == 0) { NSURL* soundURL = [[NSBundle mainBundle] URLForResource:@"crunch" withExtension:@"wav"]; //!!!: bridge前有两个下划线 AudioServicesCreateSystemSoundID((__bridge CFURLRef)(soundURL),&crunchSoundID); } AudioServicesPlaySystemSound(crunchSoundID);
其中crunchSoundID是用如下代码定义的
@implementation CustomPickerViewController { SystemSoundID winSoundID; //胜利声音引用 SystemSoundID crunchSoundID; //滚动声音引用 }
如果游戏胜利调用一个方法,如果不胜利调用另一个方法处理。这里使用的是performSelector:调用的其他方法,主要是想延迟执行这些方法
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 = @"";
这样button的事件处理就完成了。
接下来看看胜利的处理方法playWinSound:和游戏失败的处理方法showButton:
playWinSound:中要做的就是播放胜利的音效,显示胜利的标识,显示按钮,按钮显示要有一定延迟,所以使用performSelector:调用showButton:方法。
showButton:方法只是简单的显示按钮。
记得要实现必须的委托方法和数据源方法,比较特殊的一个是
- (UIView*)pickerView:(UIPickerView*)pickerView viewForRow:(NSInteger)row forComponent:(NSInteger)component reusingView:(UIView*)view
这个方法返回的是UIView类型,所以可以返回UIImage,这样就能实现“包含图像”的目标啦
完整代码:
1 // 2 // CustomPickerViewController.m 3 // Pickers 4 // 5 // Created by 张光发 on 15/10/13. 6 // Copyright (c) 2015年 张光发. All rights reserved. 7 // 8 9 #import "CustomPickerViewController.h" 10 #import <AudioToolbox/AudioToolbox.h> 11 12 @interface CustomPickerViewController () 13 @property (strong, nonatomic) NSArray* images; 14 @property (strong, nonatomic) NSMutableDictionary* selects; 15 @property (weak, nonatomic) IBOutlet UILabel* winLabel; 16 @property (weak, nonatomic) IBOutlet UIPickerView* picker; 17 @property (weak, nonatomic) IBOutlet UIButton* button; 18 19 @end 20 21 @implementation CustomPickerViewController { 22 SystemSoundID winSoundID; 23 SystemSoundID crunchSoundID; 24 } 25 - (void)showButton 26 { 27 self.button.hidden = NO; 28 } 29 - (void)playWinSound 30 { 31 //检查声效是否完成 32 if (winSoundID == 0) { 33 NSURL* soundURL = 34 [[NSBundle mainBundle] URLForResource:@"win" 35 withExtension:@"wav"]; 36 //!!!: bridge前有两个下划线 37 AudioServicesCreateSystemSoundID((__bridge CFURLRef)(soundURL), 38 &winSoundID); 39 } 40 AudioServicesPlaySystemSound(winSoundID); 41 self.winLabel.text = @"WINING!"; 42 [self performSelector:@selector(showButton) withObject:nil afterDelay:1.5]; 43 } 44 - (void)viewDidLoad 45 { 46 [super viewDidLoad]; 47 self.images = @[ 48 [UIImage imageNamed:@"seven"], 49 [UIImage imageNamed:@"bar"], 50 [UIImage imageNamed:@"crown"], 51 [UIImage imageNamed:@"cherry"], 52 [UIImage imageNamed:@"lemon"], 53 [UIImage imageNamed:@"apple"] 54 ]; 55 //???: 随机数生成器 56 /** 57 * srandom用来初始化随机数生成器的 58 参数是种子值,如果种子相同,随机数列就会相同 59 所以此处的种子我们设置为时间 60 time()函数会返回当前时间的时间戳 61 */ 62 srandom(time(NULL)); 63 NSLog(@"time(null)=%ld",time(NULL)); 64 } 65 - (IBAction)spin:(id)sender 66 { 67 NSNumber* initNumber = [NSNumber numberWithInt:0]; 68 self.selects = [NSMutableDictionary dictionaryWithObjectsAndKeys: 69 initNumber, self.images[0], 70 initNumber, self.images[1], 71 initNumber, self.images[2], 72 initNumber, self.images[3], 73 initNumber, self.images[4], 74 initNumber, self.images[5], 75 nil]; 76 BOOL win = NO; 77 for (int i = 0; i < 5; i++) { 78 NSInteger newVal = random() % [self.images count]; 79 NSLog(@"newval=%ld",newVal); 80 81 UIImage* img = self.images[newVal]; 82 NSInteger val = [[self.selects objectForKey:img] integerValue]; 83 val++; 84 [self.selects setObject:[NSNumber numberWithInteger:val] forKey:[img copy]]; 85 86 //为相应滚轮设置新值 87 [self.picker selectRow:newVal inComponent:i animated:YES]; 88 //[self.picker reloadComponent:i]; 89 } 90 for (NSNumber* j in [self.selects allValues]) { 91 if ([j integerValue] >= 3) { 92 win = YES; 93 break; 94 } 95 } 96 if (crunchSoundID == 0) { 97 NSURL* soundURL = 98 [[NSBundle mainBundle] URLForResource:@"crunch" 99 withExtension:@"wav"]; 100 //!!!: bridge前有两个下划线 101 AudioServicesCreateSystemSoundID((__bridge CFURLRef)(soundURL), 102 &crunchSoundID); 103 } 104 AudioServicesPlaySystemSound(crunchSoundID); 105 106 if (win) { 107 [self performSelector:@selector(playWinSound) withObject:nil afterDelay:0.5]; 108 } 109 else { 110 [self performSelector:@selector(showButton) withObject:nil afterDelay:0.5]; 111 } 112 self.button.hidden = YES; 113 self.winLabel.text = @""; 114 } 115 116 - (NSInteger)numberOfComponentsInPickerView:(UIPickerView*)pickerView 117 { 118 return 5; 119 } 120 - (NSInteger)pickerView:(UIPickerView*)pickerView 121 numberOfRowsInComponent:(NSInteger)component 122 { 123 return [self.images count]; 124 } 125 - (CGFloat)pickerView:(UIPickerView*)pickerView 126 rowHeightForComponent:(NSInteger)component 127 { 128 return 60; 129 } 130 - (UIView*)pickerView:(UIPickerView*)pickerView 131 viewForRow:(NSInteger)row 132 forComponent:(NSInteger)component 133 reusingView:(UIView*)view 134 { 135 UIImage* image = self.images[row]; 136 UIImageView* imageview = [[UIImageView alloc] initWithImage:image]; 137 return imageview; 138 } 139 @end