本项目是取自传智播客的教学项目,加入笔者的修改和润饰。
1. 项目名称:超级猜图
2. 项目截图展示
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsICMwITO1QDMzIjNwATM1EDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
3. 项目功能
- 点击图片或“大图”按钮,图片放大;再点击图片或点击周围区域,图片复原。
- 点击备选按钮,相应字填入答案区按钮。
- 按“下一题”按钮或答案正确:进入下一题。
- 点击答案区某按钮,相应字回到备选区原来位置。
- 点击“帮助按钮”会清空答案区按钮,并提示正确答案的第一个字。
- 答案正确或错误都有相应的扣分和加分。
4. 项目代码
- 模型代码:Question.h
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface Question : NSObjec
@property (nonatomic, copy) NSString *answer;
@property (nonatomic, copy) NSString *icon;
@property (nonatomic, copy) NSString *title;
@property (nonatomic, strong) NSArray *options;
- (instancetype)initWithDict:(NSDictionary *)dict;
+ (instancetype)questionWithDict:(NSDictionary *)dict;
@property (nonatomic, strong, readonly) UIImage *image;
/** 返回所有题目数组 */
+ (NSArray *)questions;
/** 打乱备选文字的数组 */
- (void)randamOptions;
@end
- 模型代码:Question.m
#import "Question.h"
@implementation Question
@synthesize image = _image;
- (UIImage *)image
{
if (_image == nil) {
_image = [UIImage imageNamed:self.icon];
}
return _image;
}
- (instancetype)initWithDict:(NSDictionary *)dict
{
self = [super init];
if (self) {
[self setValuesForKeysWithDictionary:dict];
// 对备选按钮进行乱序,只在加载的时候,做一次乱序
[self randamOptions];
}
return self;
}
+ (instancetype)questionWithDict:(NSDictionary *)dict
{
return [[self alloc] initWithDict:dict];
}
+ (NSArray *)questions
{
NSArray *array = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"questions.plist" ofType:nil]];
NSMutableArray *arrayM = [NSMutableArray array];
for (NSDictionary *dict in array) {
[arrayM addObject:[self questionWithDict:dict]];
}
return arrayM;
}
- (void)randamOptions
{
// 对options数组乱序
self.options = [self.options sortedArrayUsingComparator:^NSComparisonResult(NSString *str1, NSString *str2) {
int seed = arc4random_uniform();
if (seed) {
return [str1 compare:str2];
} else {
return [str2 compare:str1];
}
}];
NSLog(@"%@", self.options);
}
@end
- ViewController.m
#import "ViewController.h"
#import "Question.h"
#define kButtonWidth 35
#define kButtonHeight 35
#define kButtonMargin 10
#define kTotolCol 7
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIButton *iconButton;//中间图像
@property (weak, nonatomic) IBOutlet UIButton *scoreButton;//分数
@property (weak, nonatomic) IBOutlet UILabel *noLabel;
@property (weak, nonatomic) IBOutlet UILabel *titleLabel;
@property (weak, nonatomic) IBOutlet UIButton *nextQuestionButton;
@property (nonatomic, strong) NSArray *questions;//模型数组
@property (weak, nonatomic) IBOutlet UIView *answerView;//摆放答案按钮的答案区
@property (weak, nonatomic) IBOutlet UIView *optionsView;//摆放备选答案的备选答案区
@property (nonatomic, assign) int index;//题目索引
@property (nonatomic, strong) UIButton *cover;//蒙板
@end
@implementation ViewController
//getter方法懒加载问题数组
- (NSArray *)questions
{
if (_questions == nil) {
_questions = [Question questions];
}
return _questions;
}
//getter方法懒加载蒙板
- (UIButton *)cover
{
if (_cover == nil) {
//1. 设置蒙板大小:与窗口大小一致
_cover = [[UIButton alloc] initWithFrame:self.view.bounds];
//2. 设置蒙板颜色:灰色
_cover.backgroundColor = [UIColor colorWithWhite: alpha:];
//3. 添加模版到view
[self.view addSubview:_cover];
//4. 使蒙板无法响应事件(alpha<0.01无法接受到消息)
_cover.alpha = ;
//设定蒙板的监听事件:点击蒙板触发bigImage方法
[_cover addTarget:self action:@selector(bigImage) forControlEvents:UIControlEventTouchUpInside];
}
return _cover;
}
- (void)viewDidLoad
{
[super viewDidLoad];
//初始界面
self.index = -;
[self nextQuestion];
}
-(UIStatusBarStyle)preferredStatusBarStyle{
return UIStatusBarStyleLightContent;
}
#pragma mark - 大图小图切换
/**
* 大图小图显示切换
*/
- (IBAction)bigImage
{
// 如果没有放大,就放大,否则就缩小
// 1. 通过蒙板的alpha来判断按钮是否已经被放大:当蒙板无法响应时间的时候即图片被点击放大
if (self.cover.alpha == ) { // 放大
// 2. 将图像按钮弄到最前面
// bringSubviewToFront将子视图前置
[self.view bringSubviewToFront:self.iconButton];
// 3. 动画放大图像按钮
CGFloat w = self.view.bounds.size.width;
CGFloat h = w;
CGFloat y = (self.view.bounds.size.height - h) * ;
[UIView animateWithDuration:f animations:^{
//图片放大后的大小
self.iconButton.frame = CGRectMake(, y, w, h);
//蒙板的alpha
self.cover.alpha = ;
}];
} else { // 缩小
[UIView animateWithDuration: animations:^{
// 将图像恢复初始位置和大小
self.iconButton.frame = CGRectMake(, , , );
// 蒙板不可用
self.cover.alpha = ;
}];
}
}
#pragma mark - 下一题
/**
* 下一题目(主要的方法,尽量保留简洁的代码,主要体现思路和流程即可)
*/
- (IBAction)nextQuestion
{
// 1. 当前答题的索引,索引递增
self.index++;
// 2. 从数组中按照索引取出题目模型数据,取出某一数组元素
Question *question = self.questions[self.index];
// 3. 设置基本信息
[self setupBasicInfo:question];
// 4. 设置答案按钮
[self creatAnswerButtons:question];
// 5. 设置备选按钮
[self creatOptionsButtons:question];
}
//设置基本信息
- (void)setupBasicInfo:(Question *)question
{
//1. 序号
self.noLabel.text = [NSString stringWithFormat:@"%d/%d", self.index + , self.questions.count];
//2. 问题名称
self.titleLabel.text = question.title;
//3. 图像
[self.iconButton setImage:[UIImage imageNamed:question.icon] forState:UIControlStateNormal];
// 如果到达最后一题,禁用下一题按钮
self.nextQuestionButton.enabled = (self.index < self.questions.count - );
}
//设置答案按钮
- (void)creatAnswerButtons:(Question *)question
{
// 首先清除掉答题区内的所有按钮
// 所有的控件都继承自UIView,多态的应用
for (UIView *btn in self.answerView.subviews) {
[btn removeFromSuperview];
}
//按钮的总宽度
CGFloat answerW = self.answerView.bounds.size.width;
//按钮的个数等于答案的字数
int length = question.answer.length;
//最左边的按钮的x坐标
CGFloat answerX = (answerW - kButtonWidth * length - kButtonMargin * (length - )) * ;
// 创建所有答案的按钮
for (int i = ; i < length; i++) {
//第i个按钮的x坐标
CGFloat x = answerX + i * (kButtonMargin + kButtonWidth);
UIButton *btn = [[UIButton alloc] initWithFrame:CGRectMake(x, , kButtonWidth, kButtonHeight)];
//默认情况下白色;点击之后黑色
[btn setBackgroundImage:[UIImage imageNamed:@"btn_answer"] forState:UIControlStateNormal];
[btn setBackgroundImage:[UIImage imageNamed:@"btn_answer_highlighted"] forState:UIControlStateHighlighted];
//按钮中文字黑色
[btn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
[self.answerView addSubview:btn];
// 添加按钮监听方法:answerClick:
[btn addTarget:self action:@selector(answerClick:) forControlEvents:UIControlEventTouchUpInside];
}
}
// 设置备选按钮
- (void)creatOptionsButtons:(Question *)question
{
// 问题:每次调用下一题方法时,都会重新创建21个按钮
// 解决:如果按钮已经存在,并且是21个,只需要更改按钮标题即可
if (self.optionsView.subviews.count != question.options.count) {
// 清楚所有按钮
for (UIView *view in self.optionsView.subviews) {
[view removeFromSuperview];
}
//备选按钮区宽度
CGFloat optionW = self.optionsView.bounds.size.width;
CGFloat optionX = (optionW - kTotolCol * kButtonWidth - (kTotolCol - ) * kButtonMargin) * ;
for (int i = ; i < question.options.count; i++) {
int row = i / kTotolCol;
int col = i % kTotolCol;
CGFloat x = optionX + col * (kButtonMargin + kButtonWidth);
CGFloat y = row * (kButtonMargin + kButtonHeight);
UIButton *btn = [[UIButton alloc] initWithFrame:CGRectMake(x, y, kButtonWidth, kButtonHeight)];
[btn setBackgroundImage:[UIImage imageNamed:@"btn_option"] forState:UIControlStateNormal];
[btn setBackgroundImage:[UIImage imageNamed:@"btn_option_highlighted"] forState:UIControlStateHighlighted];
[btn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
[self.optionsView addSubview:btn];
// 添加按钮监听方法:optionClick:
[btn addTarget:self action:@selector(optionClick:) forControlEvents:UIControlEventTouchUpInside];
}
NSLog(@"创建候选按钮");
}
// 如果按钮已经存在,在点击下一题的时候,只需要设置标题即可
int i = ;
for (UIButton *btn in self.optionsView.subviews) {
// 设置备选答案
[btn setTitle:question.options[i++] forState:UIControlStateNormal];
// 回复所有按钮的隐藏状态
btn.hidden = NO;
}
}
#pragma mark - 候选按钮点击方法
/** 点击候选按钮:文字移动到答案按钮 */
- (void)optionClick:(UIButton *)button
{
// 1. 在答案区找到第一个文字为空的按钮
UIButton *btn = [self firstAnswerButton];
// 如果没有找到按钮,直接返回
if (btn == nil) return;
// 2. 将button的标题设置给答案区的按钮
[btn setTitle:button.currentTitle forState:UIControlStateNormal];
// 3. 将button隐藏
button.hidden = YES;
// 4. 判断结果
[self judge];
}
/** 判断结果 */
- (void)judge
{
// 如果四个按钮都有文字,才需要判断结果
// 遍历所有答题区的按钮
BOOL isFull = YES;
NSMutableString *strM = [NSMutableString string];
for (UIButton *btn in self.answerView.subviews) {
if (btn.currentTitle.length == ) {
// 只要有一个按钮没有字
isFull = NO;
break;
} else {
// 有字,拼接临时字符串
[strM appendString:btn.currentTitle];
}
}
if (isFull) {
// 判断是否和答案一致
// 根据self.index获得当前的question
Question *question = self.questions[self.index];
// 如果一致,进入下一题
if ([strM isEqualToString:question.answer]) {
[self setAnswerButtonsColor:[UIColor blueColor]];
// 增加分数
[self changeScore:];
// 等待0.5秒,进入下一题
[self performSelector:@selector(nextQuestion) withObject:nil afterDelay:];
} else {
// 如果不一致,修改按钮文字颜色,提示用户
[self setAnswerButtonsColor:[UIColor redColor]];
}
}
}
/** 修改答题区按钮的颜色 */
- (void)setAnswerButtonsColor:(UIColor *)color
{
for (UIButton *btn in self.answerView.subviews) {
[btn setTitleColor:color forState:UIControlStateNormal];
}
}
// 在答案区找到第一个文字为空的按钮
- (UIButton *)firstAnswerButton
{
// 取按钮的标题
// 遍历答题区所有按钮
for (UIButton *btn in self.answerView.subviews) {
if (btn.currentTitle.length == ) {//当前按钮上的文字长度为0
return btn;
}
}
return nil;
}
#pragma mark - 答题区按钮点击方法
- (void)answerClick:(UIButton *)button
{
// 1. 如果按钮没有字,直接返回
if (button.currentTitle.length == ) return;
// 2. 如果有字,清除文字,候选区按钮显示
// 1> 使用button的title去查找候选区中对应的按钮
UIButton *btn = [self optionButtonWithTilte:button.currentTitle isHidden:YES];
// 2> 在备选答案区显示对应按钮
btn.hidden = NO;
// 3> 在答案区清除button的文字
[button setTitle:@"" forState:UIControlStateNormal];
// 4> 只要点击了按钮上的文字,意味着答题区的内容不完整
[self setAnswerButtonsColor:[UIColor blackColor]];
}
- (UIButton *)optionButtonWithTilte:(NSString *)title isHidden:(BOOL)isHidden
{
// 遍历候选区中的所有按钮
for (UIButton *btn in self.optionsView.subviews) {
if ([btn.currentTitle isEqualToString:title] && btn.isHidden == isHidden) {
return btn;
}
}
return nil;
}
#pragma mark - 提示功能
- (IBAction)tipClick
{
// 1. 把答题区中所有的按钮清空
for (UIButton *btn in self.answerView.subviews) {
// 用代码执行点击答题按钮的操作
[self answerClick:btn];
}
// 2. 把正确答案的第一个字,设置到答题区中
// 1> 知道答案的第一个字
Question *question = self.questions[self.index];
NSString *first = [question.answer substringToIndex:];
UIButton *btn = [self optionButtonWithTilte:first isHidden:NO];
[self optionClick:btn];
// 扣分
[self changeScore:-];
}
#pragma mark - 分数处理
- (void)changeScore:(int)score
{
// 取出当前的分数
int currentScore = self.scoreButton.currentTitle.intValue;
// 使用score调整分数
currentScore += score;
// 重新设置分数
[self.scoreButton setTitle:[NSString stringWithFormat:@"%d", currentScore] forState:UIControlStateNormal];
}
@end
5. 本项目必须掌握的代码段
- 使控件看不见而且无法响应事件
- 将子视图前置
[self.view bringSubviewToFront:self.iconButton];
- 加入动画
[UIView animateWithDuration:f animations:^{
//图片放大后的大小
self.iconButton.frame = CGRectMake(, y, w, h);
//蒙板的alpha
self.cover.alpha = ;
}];
- 清除答案区view所有按钮
for (UIView *btn in self.answerView.subviews) {
[btn removeFromSuperview];
}
- 子视图的个数
- 找到第一个空的按钮
- (UIButton *)firstAnswerButton
{
for (UIButton *btn in self.answerView.subviews) {
if (btn.currentTitle.length == ) {
return btn;
}
}
return nil;
}
- 延迟n秒执行某方法
[self performSelector:@selector(nextQuestion) withObject:nil afterDelay:.];
6. 笔记
- UIButton的一些属性
@property(nonatomic,readonly,retain) NSString *currentTitle;
@property(nonatomic,readonly,retain) UIColor *currentTitleColor;
@property(nonatomic,readonly,retain) UIImage *currentImage;
@property(nonatomic,readonly,retain) UIImage *currentBackgroundImage;