学习目标
1.【理解】应用的需求
2.【理解】设置图标和启动图片
3.【理解】创建模型类加载数据
4.【理解】搭建基本界面并初始化
5.【理解】实现图片放大缩小功能
6.【理解】动态创建答案区
7.【理解】动态创建选项区
8.【理解】答案区和选项区的交互
9.【理解】实现提示按钮功能
一、应用的需求
应用需求:
选项区:点击选项区中的按钮,可将文字显示到答案区的按钮上。
答案区:点击答案区的按钮,会将对应的文字返回到选项区。
下一题:进入下一题,重新加载各种数据。
正误判断:如果答案错误则文字变红,正确则变绿并进入下一题。
图片缩放:点击图片、大图按钮都能实现图片放大缩小功能。
大图后的阴影:图片放大后会有一层阴影,点击阴影可以缩小图片。
提示:能清空答案区先有文字并取出正确答案的第一个文字显示到第一个按钮中。
帮助:目前没有增加功能,可以随意增加。
积分:答对一题加100分,答错一题扣100分,点击提示扣100分。答题完毕或分数小于0则提示游戏结束,是否重新开始。
图片有点大,点击链接查看动态图:点击这里查看动态图
二、设置图标和启动图片
创建项目,先去掉Ceneral中的Launch Screen File选项,并选择Launch Images Source为Images。
将启动界面的所有素材图片拖到Images.xcassets中的LaunchImage目录,Xcode会自动适配各种屏幕和版本。
将app用到的各种图标素材拖到Images.xcassets中的AppIcon目录,这样启动图片和各种图片都设置好了。
将app用到的各种图片、图标素材和plist文件导入,注意有plist文件就先创建Model类加载数据。
三、创建模型类加载数据
创建的Model类命名应该与plist文件名保存一致,并且根据plist文件中的数据类型定义Model类的属性。
//根据plist文件中的字典key来定义属性 @property (copy, nonatomic) NSString *answer; //答案 @property (copy, nonatomic) NSString *icon; //图像 @property (copy, nonatomic) NSString *title; //标题 @property (strong, nonatomic) NSArray *options; //选项区数据
Model类一般会提供快速创建模型的对象方法和类方法,还有快速返回一个模型数组的类方法。
JFQuestions.h文件
#import <Foundation/Foundation.h> @interface JFQuestions : NSObject //根据plist文件中的字典key来定义属性 @property (copy, nonatomic) NSString *answer; //答案 @property (copy, nonatomic) NSString *icon; //图像 @property (copy, nonatomic) NSString *title; //标题 @property (strong, nonatomic) NSArray *options; //选项区数据 //快速创建模型对象的对象方法 - (instancetype)initWithDictionary:(NSDictionary *)dict; //快速创建模型对象的类方法 + (instancetype)questionsWithDictionary:(NSDictionary *)dict; //返回模型数组 + (NSArray *)questions; @end
JFQuestions.m文件
#import "JFQuestions.h" @implementation JFQuestions //快速创建模型对象的对象方法 - (instancetype)initWithDictionary:(NSDictionary *)dict { if (self = [super init]) { //根据字典初始化对象的属性 self.answer = dict[@"answer"]; self.icon = dict[@"icon"]; self.title = dict[@"title"]; self.options = dict[@"options"]; } return self; } //快速创建模型对象的类方法 + (instancetype)questionsWithDictionary:(NSDictionary *)dict { return [[self alloc] initWithDictionary:dict]; } //返回模型数组 + (NSArray *)questions { //加载plist文件 NSArray *array = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"questions.plist" ofType:nil]]; //创建可变数组存储创建的模型对象 NSMutableArray *arrayM = [NSMutableArray array]; for (NSDictionary *dict in array) { JFQuestions *question = [self questionsWithDictionary:dict]; [arrayM addObject:question]; } //返回创建好的模型数组 return arrayM; } @end
创建好Model类后,先在Contronller中定义一个数组属性用于存储模型对象,并重新这个属性的get方法加载模型数组。
ViewController.m文件
#import "ViewController.h" #import "JFQuestions.h" @interface ViewController () @property (strong, nonatomic) NSArray *questions;//模型数组 @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; } #pragma mark - 懒加载数据到数组 -(NSArray *)questions { if (_questions == nil) { //调用Model类的questions方法快速返回一个模型数组 _questions = [JFQuestions questions]; } return _questions; } @end
四、搭建基本界面并初始化
背景图:先拖拽一个UIImageView并全屏,用于设置应用的背景图。
积分:最好是Button,因为积分旁边有个小图标,可以通过设置按钮Image来实现。
索引:只显示文字数据,所以用Label。
标题:只显示文字数据,所以用Label。
图片:图片可点击,所以用Button。
其他按钮:用脚趾头都能想到使用按钮,并设置。
设置界面控件的各种属性,我们这里先不考虑答案区、选项区。
然后各种控件拖线,拖线的原则是要调用控件(修改控件属性等)就设置为属性,如果只是点击后触发一个事件就连线方法。
ViewController.m文件
#import "ViewController.h" #import "JFQuestions.h" @interface ViewController () @property (strong, nonatomic) NSArray *questions;//模型数组 @property (assign, nonatomic) int index;//模型数组的索引(下标) @property (weak, nonatomic) IBOutlet UIButton *scoreView;//显示积分 @property (weak, nonatomic) IBOutlet UILabel *indexView;//显示索引 @property (weak, nonatomic) IBOutlet UILabel *titleView;//显示标题 @property (weak, nonatomic) IBOutlet UIButton *iconView;//表示图片 @property (weak, nonatomic) IBOutlet UIButton *nextButton;//下一题按钮 - (IBAction)tip;//提示按钮 - (IBAction)help;//帮助按钮 - (IBAction)bigImage;//大图按钮 - (IBAction)next;//下一题按钮 @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; //加载界面完界面让模型数组索引为-1 self.index = -1; //调用next方法即可显示索引为0的数据 [self next]; } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; } #pragma mark - 懒加载数据到数组 -(NSArray *)questions { if (_questions == nil) { //调用Model类的questions方法快速返回一个模型数组 _questions = [JFQuestions questions]; } return _questions; } #pragma mark - 提示 - (IBAction)tip { } #pragma mark - 帮助 - (IBAction)help { } #pragma mark - 图片变大 - (IBAction)bigImage { } #pragma mark - 下一题 - (IBAction)next { //模型数组索引自增 self.index++; //加载界面数据 [self loadDataView]; //如果到了最后一题,就禁用下一题按钮 self.nextButton.enabled = self.index != self.questions.count - 1; } #pragma mark - 加载数据 - (void)loadDataView { //取出当前索引的模型对象 JFQuestions *question = self.questions[self.index]; //加载索引数据 self.indexView.text = [NSString stringWithFormat:@"%d/%lu", self.index + 1, self.questions.count]; //加载标题数据 self.titleView.text = question.title; //加载图片数据,注意这里是设置按钮图片不是,按钮背景图 [self.iconView setImage:[UIImage imageNamed:question.icon] forState:UIControlStateNormal]; } @end
加载界面并初始化索引为0的模型数组的各种数据到控件上显示,并对下一题按钮进行监控。如果到最后一题就禁用下一题按钮,防止下标越界。
实现效果如下图:
五、实现图片放大缩小功能
阴影控制:阴影不能直接加到界面,这样会覆盖掉包括图片的整个界面,所以放大实现中要将图片放到最外一层。阴影背景会不止在一个方法中调用,所以我们最好设置为属性,方便调用。
图片缩放:点击大图、图片都能实现图片放大,点击图片和阴影背景都能实现图片缩小。图片放大、缩小和阴影出现、消失都有渐变效果,所以使用动画来实现。图片实现缩放,必须要知道原先图片的frame,所以我们最好设置一个属性保存图片原先的frame。
注意:点击图片默认会有点击效果,去掉Highlighted Adjusts Image即可解决。
ViewController.m文件
#import "ViewController.h" #import "JFQuestions.h" @interface ViewController () #pragma mark - 属性 @property (strong, nonatomic) NSArray *questions; //模型数组 @property (assign, nonatomic) int index; //模型数组的索引(下标) @property (weak, nonatomic) IBOutlet UIButton *scoreView; //显示积分 @property (weak, nonatomic) IBOutlet UILabel *indexView; //显示索引 @property (weak, nonatomic) IBOutlet UILabel *titleView; //显示标题 @property (weak, nonatomic) IBOutlet UIButton *iconView; //表示图片 @property (weak, nonatomic) IBOutlet UIButton *nextButton; //下一题按钮 @property (assign, nonatomic) CGRect iconFrame; //图片原始frame @property (weak, nonatomic) UIButton *shade; //阴影背景 #pragma mark - 方法 - (IBAction)tip; //提示按钮 - (IBAction)help; //帮助按钮 - (IBAction)bigImage; //大图按钮 - (IBAction)iconButton; //点击图片 - (IBAction)next; //下一题按钮 - (void)smallImage; //图片变小 @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; //加载界面完界面让模型数组索引为-1 self.index = -1; //调用next方法即可显示索引为0的数据 [self next]; //保存图片元素frame值 self.iconFrame = self.iconView.frame; } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; } #pragma mark - 懒加载数据到数组 -(NSArray *)questions { if (_questions == nil) { //调用Model类的questions方法快速返回一个模型数组 _questions = [JFQuestions questions]; } return _questions; } #pragma mark - 提示 - (IBAction)tip { } #pragma mark - 帮助 - (IBAction)help { } #pragma mark - 图片变大 - (IBAction)bigImage { //创建阴影背景,因为可以点击所以使用UIButton UIButton *shade = [[UIButton alloc] init]; //设置阴影背景的frame shade.frame = CGRectMake(0, 0, 375, 667); //设置阴影背景的背景色 [shade setBackgroundColor:[UIColor blackColor]]; //设置阴影背景的初始透明度(为了在动画效果中渐变) shade.alpha = 0; //将shade添加父视图 [self.view addSubview:shade]; //计算图片放大后的frame CGFloat iconW = self.view.frame.size.width; CGFloat iconH = iconW; CGFloat iconX = 0; CGFloat iconY = (self.view.frame.size.height - iconH) / 2; //动画渐变设置图片放大、阴影背景出现的效果 [UIView animateWithDuration:1 animations:^{ self.iconView.frame = CGRectMake(iconX, iconY, iconW, iconH); shade.alpha = 0.6; }]; //将图片控件调整到最外层 [self.view bringSubviewToFront:self.iconView]; //将背景赋值给属性(因为要在其他方法中让阴影消失,需要访问到阴影背景控件) self.shade = shade; //为阴影背景注册单击事件,点击后图片缩放,并让去掉阴影背景 [shade addTarget:self action:@selector(smallImage) forControlEvents:UIControlEventTouchUpInside]; } #pragma mark - 图片变小 - (void)smallImage { //动画渐变变小效果 [UIView animateWithDuration:1 animations:^{ //将图片frame还原,将阴影背景设为透明 self.iconView.frame = self.iconFrame; self.shade.alpha = 0; } completion:^(BOOL finished) { //去掉阴影背景 self.shade = nil; }]; } #pragma mark - 点击图片 - (IBAction)iconButton { if (self.shade == nil) { //如果没有阴影背景就说明是缩小状态,点击后放大 [self bigImage]; } else { //如果有就说明是放大状态,点击后缩小 [self smallImage]; } } #pragma mark - 下一题 - (IBAction)next { //模型数组索引自增 self.index++; //加载界面数据 [self loadDataView]; //如果到了最后一题,就禁用下一题按钮 self.nextButton.enabled = self.index != self.questions.count - 1; } #pragma mark - 加载数据 - (void)loadDataView { //取出当前索引的模型对象 JFQuestions *question = self.questions[self.index]; //加载索引数据 self.indexView.text = [NSString stringWithFormat:@"%d/%lu", self.index + 1, self.questions.count]; //加载标题数据 self.titleView.text = question.title; //加载图片数据,注意这里是设置按钮图片不是,按钮背景图 [self.iconView setImage:[UIImage imageNamed:question.icon] forState:UIControlStateNormal]; } @end
实现效果如下图:
六、动态创建答案区
答案区我们可以动态创建答案按钮在一个View中,然后把这个View添加到父视图(屏幕)上。View我们可以直接拖控件,再循环创建按钮控件并添加到这个View上。和我们前两天写的九宫格大同小异,非常easy,这里我就不分析计算过程了。
ViewController.m文件
#import "ViewController.h" #import "JFQuestions.h" @interface ViewController () #pragma mark - 属性 @property (strong, nonatomic) NSArray *questions; //模型数组 @property (assign, nonatomic) int index; //模型数组的索引(下标) @property (weak, nonatomic) IBOutlet UIButton *scoreView; //显示积分 @property (weak, nonatomic) IBOutlet UILabel *indexView; //显示索引 @property (weak, nonatomic) IBOutlet UILabel *titleView; //显示标题 @property (weak, nonatomic) IBOutlet UIButton *iconView; //表示图片 @property (weak, nonatomic) IBOutlet UIButton *nextButton; //下一题按钮 @property (assign, nonatomic) CGRect iconFrame; //图片原始frame @property (weak, nonatomic) UIButton *shade; //阴影背景 @property (weak, nonatomic) IBOutlet UIView *answerView; //答案区View #pragma mark - 方法 - (IBAction)tip; //提示按钮 - (IBAction)help; //帮助按钮 - (IBAction)bigImage; //大图按钮 - (IBAction)iconButton; //点击图片 - (IBAction)next; //下一题按钮 - (void)smallImage; //图片变小 - (void)loadAnswerView; //加载答案区 @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; //加载界面完界面让模型数组索引为-1 self.index = -1; //调用next方法即可显示索引为0的数据 [self next]; //保存图片元素frame值 self.iconFrame = self.iconView.frame; } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; } #pragma mark - 懒加载数据到数组 -(NSArray *)questions { if (_questions == nil) { //调用Model类的questions方法快速返回一个模型数组 _questions = [JFQuestions questions]; } return _questions; } #pragma mark - 提示 - (IBAction)tip { } #pragma mark - 帮助 - (IBAction)help { } #pragma mark - 图片变大 - (IBAction)bigImage { //创建阴影背景,因为可以点击所以使用UIButton UIButton *shade = [[UIButton alloc] init]; //设置阴影背景的frame shade.frame = CGRectMake(0, 0, 375, 667); //设置阴影背景的背景色 [shade setBackgroundColor:[UIColor blackColor]]; //设置阴影背景的初始透明度(为了在动画效果中渐变) shade.alpha = 0; //将shade添加父视图 [self.view addSubview:shade]; //计算图片放大后的frame CGFloat iconW = self.view.frame.size.width; CGFloat iconH = iconW; CGFloat iconX = 0; CGFloat iconY = (self.view.frame.size.height - iconH) / 2; //动画渐变设置图片放大、阴影背景出现的效果 [UIView animateWithDuration:1 animations:^{ self.iconView.frame = CGRectMake(iconX, iconY, iconW, iconH); shade.alpha = 0.6; }]; //将图片控件调整到最外层 [self.view bringSubviewToFront:self.iconView]; //将背景赋值给属性(因为要在其他方法中让阴影消失,需要访问到阴影背景控件) self.shade = shade; //为阴影背景注册单击事件,点击后图片缩放,并让去掉阴影背景 [shade addTarget:self action:@selector(smallImage) forControlEvents:UIControlEventTouchUpInside]; } #pragma mark - 图片变小 - (void)smallImage { //动画渐变变小效果 [UIView animateWithDuration:1 animations:^{ //将图片frame还原,将阴影背景设为透明 self.iconView.frame = self.iconFrame; self.shade.alpha = 0; } completion:^(BOOL finished) { //去掉阴影背景 self.shade = nil; }]; } #pragma mark - 点击图片 - (IBAction)iconButton { if (self.shade == nil) { //如果没有阴影背景就说明是缩小状态,点击后放大 [self bigImage]; } else { //如果有就说明是放大状态,点击后缩小 [self smallImage]; } } #pragma mark - 下一题 - (IBAction)next { //模型数组索引自增 self.index++; //加载界面数据 [self loadDataView]; //如果到了最后一题,就禁用下一题按钮 self.nextButton.enabled = self.index != self.questions.count - 1; } #pragma mark - 加载数据 - (void)loadDataView { //取出当前索引的模型对象 JFQuestions *question = self.questions[self.index]; //加载索引数据 self.indexView.text = [NSString stringWithFormat:@"%d/%lu", self.index + 1, self.questions.count]; //加载标题数据 self.titleView.text = question.title; //加载图片数据,注意这里是设置按钮图片不是,按钮背景图 [self.iconView setImage:[UIImage imageNamed:question.icon] forState:UIControlStateNormal]; //加载答案区 [self loadAnswerView]; } #pragma mark - 加载答案区 - (void)loadAnswerView { //清除上一次加载的答案区所有按钮 for (UIButton *answer in self.answerView.subviews) { [answer removeFromSuperview]; } //获取当前索引的模型对象 JFQuestions *question = self.questions[self.index]; //答案长度就是答案按钮个数 NSUInteger length = question.answer.length; CGFloat margin = 10; //每个按钮之间的外边距 CGFloat answerW = 35; //按钮宽度 CGFloat answerH = 35; //按钮高度 CGFloat answerY = 0; //按钮靠着答案区View,所以y坐标为0 //左边距,也就是第一个按钮的x坐标 CGFloat marginLeft = (self.answerView.frame.size.width - (answerW + margin) * length) / 2; //循环创建答案区按钮 for (int i = 0; i < length; i++) { //创建按钮 UIButton *answer = [[UIButton alloc] init]; //计算每个按钮的x坐标 CGFloat answerX = marginLeft + (answerW + margin) * i; //设置按钮的frame answer.frame = CGRectMake(answerX, answerY, answerW, answerH); //设置答案区按钮背景图 [answer setBackgroundImage:[UIImage imageNamed:@"btn_answer"] forState:UIControlStateNormal]; [answer setBackgroundImage:[UIImage imageNamed:@"btn_answer_highlighted"] forState:UIControlStateHighlighted]; //设置答案区按钮文字颜色为黑色 [answer setTitleColor:[UIColor blackColor] forState:UIControlStateNormal]; //将创建好的答案按钮添加到答案区View [self.answerView addSubview:answer]; } } @end
实现效果如下图:
七、动态创建选项区
和创建答案区的方式类似,先拖一个UIView,动态创建选项区按钮并添加到选项区的UIView。
ViewController.m文件
#import "ViewController.h" #import "JFQuestions.h" @interface ViewController () #pragma mark - 属性 @property (strong, nonatomic) NSArray *questions; //模型数组 @property (assign, nonatomic) int index; //模型数组的索引(下标) @property (weak, nonatomic) IBOutlet UIButton *scoreView; //显示积分 @property (weak, nonatomic) IBOutlet UILabel *indexView; //显示索引 @property (weak, nonatomic) IBOutlet UILabel *titleView; //显示标题 @property (weak, nonatomic) IBOutlet UIButton *iconView; //表示图片 @property (weak, nonatomic) IBOutlet UIButton *nextButton; //下一题按钮 @property (assign, nonatomic) CGRect iconFrame; //图片原始frame @property (weak, nonatomic) UIButton *shade; //阴影背景 @property (weak, nonatomic) IBOutlet UIView *answerView; //答案区View @property (weak, nonatomic) IBOutlet UIView *optionsView; //选项区View #pragma mark - 方法 - (IBAction)tip; //提示按钮 - (IBAction)help; //帮助按钮 - (IBAction)bigImage; //大图按钮 - (IBAction)iconButton; //点击图片 - (IBAction)next; //下一题按钮 - (void)smallImage; //图片变小 - (void)loadAnswerView; //加载答案区 @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; //加载界面完界面让模型数组索引为-1 self.index = -1; //调用next方法即可显示索引为0的数据 [self next]; //保存图片元素frame值 self.iconFrame = self.iconView.frame; } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; } #pragma mark - 懒加载数据到数组 -(NSArray *)questions { if (_questions == nil) { //调用Model类的questions方法快速返回一个模型数组 _questions = [JFQuestions questions]; } return _questions; } #pragma mark - 提示 - (IBAction)tip { } #pragma mark - 帮助 - (IBAction)help { } #pragma mark - 图片变大 - (IBAction)bigImage { //创建阴影背景,因为可以点击所以使用UIButton UIButton *shade = [[UIButton alloc] init]; //设置阴影背景的frame shade.frame = CGRectMake(0, 0, 375, 667); //设置阴影背景的背景色 [shade setBackgroundColor:[UIColor blackColor]]; //设置阴影背景的初始透明度(为了在动画效果中渐变) shade.alpha = 0; //将shade添加父视图 [self.view addSubview:shade]; //计算图片放大后的frame CGFloat iconW = self.view.frame.size.width; CGFloat iconH = iconW; CGFloat iconX = 0; CGFloat iconY = (self.view.frame.size.height - iconH) / 2; //动画渐变设置图片放大、阴影背景出现的效果 [UIView animateWithDuration:1 animations:^{ self.iconView.frame = CGRectMake(iconX, iconY, iconW, iconH); shade.alpha = 0.6; }]; //将图片控件调整到最外层 [self.view bringSubviewToFront:self.iconView]; //将背景赋值给属性(因为要在其他方法中让阴影消失,需要访问到阴影背景控件) self.shade = shade; //为阴影背景注册单击事件,点击后图片缩放,并让去掉阴影背景 [shade addTarget:self action:@selector(smallImage) forControlEvents:UIControlEventTouchUpInside]; } #pragma mark - 图片变小 - (void)smallImage { //动画渐变变小效果 [UIView animateWithDuration:1 animations:^{ //将图片frame还原,将阴影背景设为透明 self.iconView.frame = self.iconFrame; self.shade.alpha = 0; } completion:^(BOOL finished) { //去掉阴影背景 self.shade = nil; }]; } #pragma mark - 点击图片 - (IBAction)iconButton { if (self.shade == nil) { //如果没有阴影背景就说明是缩小状态,点击后放大 [self bigImage]; } else { //如果有就说明是放大状态,点击后缩小 [self smallImage]; } } #pragma mark - 下一题 - (IBAction)next { //模型数组索引自增 self.index++; //加载界面数据 [self loadDataView]; //如果到了最后一题,就禁用下一题按钮 self.nextButton.enabled = self.index != self.questions.count - 1; } #pragma mark - 加载数据 - (void)loadDataView { //取出当前索引的模型对象 JFQuestions *question = self.questions[self.index]; //加载索引数据 self.indexView.text = [NSString stringWithFormat:@"%d/%lu", self.index + 1, self.questions.count]; //加载标题数据 self.titleView.text = question.title; //加载图片数据,注意这里是设置按钮图片不是,按钮背景图 [self.iconView setImage:[UIImage imageNamed:question.icon] forState:UIControlStateNormal]; //加载答案区数据 [self loadAnswerView]; //加载选项区数据 [self loadOptionsView]; } #pragma mark - 加载答案区 - (void)loadAnswerView { //清除上一次加载的答案区所有按钮 for (UIButton *answer in self.answerView.subviews) { [answer removeFromSuperview]; } //获取当前索引的模型对象 JFQuestions *question = self.questions[self.index]; //答案长度就是答案按钮个数 NSUInteger length = question.answer.length; CGFloat margin = 10; //每个按钮之间的外边距 CGFloat answerW = 35; //按钮宽度 CGFloat answerH = 35; //按钮高度 CGFloat answerY = 0; //按钮靠着答案区View,所以y坐标为0 //左边距,也就是第一个按钮的x坐标 CGFloat marginLeft = (self.answerView.frame.size.width - (answerW + margin) * length) / 2; //循环创建答案区按钮 for (int i = 0; i < length; i++) { //创建按钮 UIButton *answer = [[UIButton alloc] init]; //计算每个按钮的x坐标 CGFloat answerX = marginLeft + (answerW + margin) * i; //设置按钮的frame answer.frame = CGRectMake(answerX, answerY, answerW, answerH); //设置答案区按钮背景图 [answer setBackgroundImage:[UIImage imageNamed:@"btn_answer"] forState:UIControlStateNormal]; [answer setBackgroundImage:[UIImage imageNamed:@"btn_answer_highlighted"] forState:UIControlStateHighlighted]; //设置答案区按钮文字颜色为黑色 [answer setTitleColor:[UIColor blackColor] forState:UIControlStateNormal]; //将创建好的答案按钮添加到答案区View [self.answerView addSubview:answer]; } } #pragma mark - 加载选项区 - (void)loadOptionsView { //清除上一次加载的选项区所有按钮 for (UIButton *option in self.optionsView.subviews) { [option removeFromSuperview]; } //获取当前索引的模型对象 JFQuestions *question = self.questions[self.index]; //选项长度就是选项按钮个数 NSUInteger length = question.options.count; //每行的按钮个数 int count = 7; CGFloat margin = 10; //每个按钮的外边距 CGFloat optionW = 35; //按钮宽 CGFloat optionH = 35; //按钮高 //左边距,也就是每行第一个按钮的x坐标 CGFloat marginLeft = (self.optionsView.frame.size.width - (optionW + margin) * count) / 2; //循环创建选项区的按钮 for (int i = 0; i < length; i++) { //创建按钮 UIButton *option = [[UIButton alloc] init]; //计算行索引、列索引 int rowIndex = i % count; int colIndex = i / count; //计算x、y坐标 CGFloat optionX = marginLeft + (optionW + margin) * rowIndex; CGFloat optionY = 0 + (optionH + margin) * colIndex; //设置按钮的frame option.frame = CGRectMake(optionX, optionY, optionW, optionH); //设置答案区按钮背景图 [option setBackgroundImage:[UIImage imageNamed:@"btn_answer"] forState:UIControlStateNormal]; [option setBackgroundImage:[UIImage imageNamed:@"btn_answer_highlighted"] forState:UIControlStateHighlighted]; //设置答案区按钮文字颜色为黑色 [option setTitleColor:[UIColor blackColor] forState:UIControlStateNormal]; //设置按钮文字颜色 [option setTitle:question.options[i] forState:UIControlStateNormal]; //将创建好的答案按钮添加到答案区View [self.optionsView addSubview:option]; } } @end
实现效果如下图:
八、答案区和选项区的交互
点击选项区会将按钮上的文字赋值给答案区的按钮,并隐藏被点击的选项区按钮。点击答案区会清楚被点击按钮的文字,并将选项区对应按钮显示出来。如果答案区按钮都全部赋值,就判断答案是否成功,如果成功则加分并判断是否是最后一题,不是则进入下一题,是则提示游戏结束。
ViewController.m文件
#import "ViewController.h" #import "JFQuestions.h" @interface ViewController () <UIAlertViewDelegate>//监听对话框并遵守协议 #pragma mark - 属性 @property (strong, nonatomic) NSArray *questions; //模型数组 @property (assign, nonatomic) int index; //模型数组的索引(下标) @property (weak, nonatomic) IBOutlet UIButton *scoreView; //显示积分 @property (weak, nonatomic) IBOutlet UILabel *indexView; //显示索引 @property (weak, nonatomic) IBOutlet UILabel *titleView; //显示标题 @property (weak, nonatomic) IBOutlet UIButton *iconView; //表示图片 @property (weak, nonatomic) IBOutlet UIButton *nextButton; //下一题按钮 @property (assign, nonatomic) CGRect iconFrame; //图片原始frame @property (weak, nonatomic) UIButton *shade; //阴影背景 @property (weak, nonatomic) IBOutlet UIView *answerView; //答案区View @property (weak, nonatomic) IBOutlet UIView *optionsView; //选项区View #pragma mark - 方法 - (IBAction)tip; //提示按钮 - (IBAction)help; //帮助按钮 - (IBAction)bigImage; //大图按钮 - (IBAction)iconButton; //点击图片 - (IBAction)next; //下一题按钮 - (void)smallImage; //图片变小 - (void)loadAnswerView; //加载答案区 - (void)loadOptionsView; //加载选项区 - (void)answerClick:(UIButton *)sender; //答案区按钮单击事件 - (void)optionClick:(UIButton *)sender; //选项区按钮单击事件 - (void)changeColor:(UIColor *)color; //改变按钮颜色 - (void)changeScore:(int)score; //改变积分 - (void)gameOver; //游戏结束对话框 - (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex;//实现协议方法 @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; //加载界面完界面让模型数组索引为-1 self.index = -1; //调用next方法即可显示索引为0的数据 [self next]; //保存图片元素frame值 self.iconFrame = self.iconView.frame; } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; } #pragma mark - 懒加载数据到数组 -(NSArray *)questions { if (_questions == nil) { //调用Model类的questions方法快速返回一个模型数组 _questions = [JFQuestions questions]; } return _questions; } #pragma mark - 提示 - (IBAction)tip { } #pragma mark - 帮助 - (IBAction)help { } #pragma mark - 图片变大 - (IBAction)bigImage { //创建阴影背景,因为可以点击所以使用UIButton UIButton *shade = [[UIButton alloc] init]; //设置阴影背景的frame shade.frame = CGRectMake(0, 0, 375, 667); //设置阴影背景的背景色 [shade setBackgroundColor:[UIColor blackColor]]; //设置阴影背景的初始透明度(为了在动画效果中渐变) shade.alpha = 0; //将shade添加父视图 [self.view addSubview:shade]; //计算图片放大后的frame CGFloat iconW = self.view.frame.size.width; CGFloat iconH = iconW; CGFloat iconX = 0; CGFloat iconY = (self.view.frame.size.height - iconH) / 2; //动画渐变设置图片放大、阴影背景出现的效果 [UIView animateWithDuration:1 animations:^{ self.iconView.frame = CGRectMake(iconX, iconY, iconW, iconH); shade.alpha = 0.6; }]; //将图片控件调整到最外层 [self.view bringSubviewToFront:self.iconView]; //将背景赋值给属性(因为要在其他方法中让阴影消失,需要访问到阴影背景控件) self.shade = shade; //为阴影背景注册单击事件,点击后图片缩放,并让去掉阴影背景 [shade addTarget:self action:@selector(smallImage) forControlEvents:UIControlEventTouchUpInside]; } #pragma mark - 图片变小 - (void)smallImage { //动画渐变变小效果 [UIView animateWithDuration:1 animations:^{ //将图片frame还原,将阴影背景设为透明 self.iconView.frame = self.iconFrame; self.shade.alpha = 0; } completion:^(BOOL finished) { //去掉阴影背景 self.shade = nil; }]; } #pragma mark - 点击图片 - (IBAction)iconButton { if (self.shade == nil) { //如果没有阴影背景就说明是缩小状态,点击后放大 [self bigImage]; } else { //如果有就说明是放大状态,点击后缩小 [self smallImage]; } } #pragma mark - 下一题 - (IBAction)next { //开启选项区按钮交互 self.optionsView.userInteractionEnabled = YES; //模型数组索引自增 self.index++; //加载界面数据 [self loadDataView]; //如果到了最后一题,就禁用下一题按钮 self.nextButton.enabled = self.index != self.questions.count - 1; } #pragma mark - 加载数据 - (void)loadDataView { //取出当前索引的模型对象 JFQuestions *question = self.questions[self.index]; //加载索引数据 self.indexView.text = [NSString stringWithFormat:@"%d/%lu", self.index + 1, self.questions.count]; //加载标题数据 self.titleView.text = question.title; //加载图片数据,注意这里是设置按钮图片不是,按钮背景图 [self.iconView setImage:[UIImage imageNamed:question.icon] forState:UIControlStateNormal]; //加载答案区数据 [self loadAnswerView]; //加载选项区数据 [self loadOptionsView]; } #pragma mark - 加载答案区 - (void)loadAnswerView { //清除上一次加载的答案区所有按钮 for (UIButton *answer in self.answerView.subviews) { [answer removeFromSuperview]; } //获取当前索引的模型对象 JFQuestions *question = self.questions[self.index]; //答案长度就是答案按钮个数 NSUInteger length = question.answer.length; CGFloat margin = 10; //每个按钮之间的外边距 CGFloat answerW = 35; //按钮宽度 CGFloat answerH = 35; //按钮高度 CGFloat answerY = 0; //按钮靠着答案区View,所以y坐标为0 //左边距,也就是第一个按钮的x坐标 CGFloat marginLeft = (self.answerView.frame.size.width - (answerW + margin) * length) / 2; //循环创建答案区按钮 for (int i = 0; i < length; i++) { //创建按钮 UIButton *answer = [[UIButton alloc] init]; //计算每个按钮的x坐标 CGFloat answerX = marginLeft + (answerW + margin) * i; //设置按钮的frame answer.frame = CGRectMake(answerX, answerY, answerW, answerH); //设置答案区按钮背景图 [answer setBackgroundImage:[UIImage imageNamed:@"btn_answer"] forState:UIControlStateNormal]; [answer setBackgroundImage:[UIImage imageNamed:@"btn_answer_highlighted"] forState:UIControlStateHighlighted]; //设置答案区按钮文字颜色为黑色 [answer setTitleColor:[UIColor blackColor] forState:UIControlStateNormal]; //将创建好的答案按钮添加到答案区View [self.answerView addSubview:answer]; //为答案区的按钮注册单击事件 [answer addTarget:self action:@selector(answerClick:) forControlEvents:UIControlEventTouchUpInside]; } } #pragma mark - 加载选项区 - (void)loadOptionsView { //清除上一次加载的选项区所有按钮 for (UIButton *option in self.optionsView.subviews) { [option removeFromSuperview]; } //获取当前索引的模型对象 JFQuestions *question = self.questions[self.index]; //选项长度就是选项按钮个数 NSUInteger length = question.options.count; //每行的按钮个数 int count = 7; CGFloat margin = 10; //每个按钮的外边距 CGFloat optionW = 35; //按钮宽 CGFloat optionH = 35; //按钮高 //左边距,也就是每行第一个按钮的x坐标 CGFloat marginLeft = (self.optionsView.frame.size.width - (optionW + margin) * count) / 2; //循环创建选项区的按钮 for (int i = 0; i < length; i++) { //创建按钮 UIButton *option = [[UIButton alloc] init]; //计算行索引、列索引 int rowIndex = i % count; int colIndex = i / count; //计算x、y坐标 CGFloat optionX = marginLeft + (optionW + margin) * rowIndex; CGFloat optionY = 0 + (optionH + margin) * colIndex; //设置按钮的frame option.frame = CGRectMake(optionX, optionY, optionW, optionH); //设置答案区按钮背景图 [option setBackgroundImage:[UIImage imageNamed:@"btn_answer"] forState:UIControlStateNormal]; [option setBackgroundImage:[UIImage imageNamed:@"btn_answer_highlighted"] forState:UIControlStateHighlighted]; //设置答案区按钮文字颜色为黑色 [option setTitleColor:[UIColor blackColor] forState:UIControlStateNormal]; //设置按钮文字颜色 [option setTitle:question.options[i] forState:UIControlStateNormal]; //给每个选项区按钮设置一个唯一tag值 option.tag = i; //将创建好的答案按钮添加到答案区View [self.optionsView addSubview:option]; //为选项区的按钮注册单击事件 [option addTarget:self action:@selector(optionClick:) forControlEvents:UIControlEventTouchUpInside]; } } #pragma mark - 答案区的按钮单击事件 - (void)answerClick:(UIButton *)sender { //点击答案区,就开放选项区的交互功能 self.optionsView.userInteractionEnabled = YES; //将被点击的按钮文字清空 [sender setTitle:nil forState:UIControlStateNormal]; //遍历找到与被点击按钮tag值相同的按钮让其取消隐藏 for (UIButton *option in self.optionsView.subviews) { if (option.tag == sender.tag) { option.hidden = NO; } } } #pragma mark - 选项区的按钮单击事件 - (void)optionClick:(UIButton *)sender { BOOL isPull = YES; //遍历答案区按钮,遇到空的按钮就依次赋值 for (UIButton *answer in self.answerView.subviews) { if (answer.currentTitle == nil) { //将被点击按钮的文字赋值给答案按钮 [answer setTitle:sender.currentTitle forState:UIControlStateNormal]; //将被点击按钮的tag值也赋值给答案按钮 answer.tag = sender.tag; //隐藏被点击按钮 sender.hidden = YES; //每次点击只赋值一个按钮 break; } } //定义一个可变字符串用于拼接用户点击 NSMutableString *userInput = [NSMutableString string]; //取出当前索引的模型对象 JFQuestions *questions = self.questions[self.index]; for (UIButton *answer in self.answerView.subviews) { if (answer.currentTitle == nil) { //只要进了循环就说明还没满 isPull = NO; } else { //不为空就拼接 [userInput appendString:answer.currentTitle]; } } if (isPull == YES) { //满了就关闭选项区交互功能 self.optionsView.userInteractionEnabled = NO; //判断答案正误 if ([userInput isEqualToString:questions.answer]) { //正确、改变按钮文字颜色为蓝色 [self changeColor:[UIColor blueColor]]; //增加100分 [self changeScore:100]; if (self.index != self.questions.count - 1) { //延迟一秒调到下一题 [self performSelector:@selector(next) withObject:nil afterDelay:1]; } else { [self gameOver]; } } else { //错误就改变按钮文字颜色为红色 [self changeColor:[UIColor redColor]]; //较少100分 [self changeScore:-100]; } } } #pragma mark - 改变文字颜色 - (void)changeColor:(UIColor *)color { for (UIButton *answer in self.answerView.subviews) { [answer setTitleColor:color forState:UIControlStateNormal]; } } #pragma mark - 改变积分 - (void)changeScore:(int)score { //取出当前分数 int currentScore = self.scoreView.currentTitle.intValue; //如果当前分数小于0就提示游戏结束,否则继续扣分 if (currentScore <= 0) { [self gameOver]; } else { //修改分数 currentScore += score; } //为控件赋值新分数 [self.scoreView setTitle:[NSString stringWithFormat:@"%d",currentScore] forState:UIControlStateNormal]; } #pragma mark - 游戏结束对话框 - (void)gameOver { //创建对话框 UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"温馨提醒" message:@"GAME OVER!是否继续游戏?" delegate:self cancelButtonTitle:@"继续游戏" otherButtonTitles:nil]; //显示对话框 [alertView show]; } #pragma mark - 实现协议方法 - (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex { if (buttonIndex == 0) { //初始化游戏数据,重新开始 [self viewDidLoad]; } } @end
九、实现提示按钮功能
每次点击提示按钮会扣分,再清空答案区按钮并恢复选项区按钮,然后截取正确答案第一个文字添加到答案区第一个按钮上。
方法如下代码:
#pragma mark - 提示 - (IBAction)tip { //每次点击提示扣分100 [self changeScore:-100]; //取出当前的模型对象 JFQuestions *question = self.questions[self.index]; //截取正确答案的第一个文字 NSString *answer = [question.answer substringToIndex:1]; for (UIButton *answer in self.answerView.subviews) { //模拟点击答案区所有按钮,清空答案区 [self answerClick:answer]; } for (UIButton *option in self.optionsView.subviews) { if ([answer isEqualToString:option.currentTitle]) { //模拟点击匹配到的选项区按钮,添加到答案区 [self optionClick:option]; } } }
至此整个小练习就完成了,再随意新增一些简单功能,就不多做演示。