UI008:超级猜图app
1.模型
// CZQuestion.h
#import <Foundation/Foundation.h>
@interface CZQuestion : NSObject
@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;
@end
//-------------------------
// CZQuestion.m
#import "CZQuestion.h"
@implementation CZQuestion
-(instancetype)initWithDict:(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)questionWithDict:(NSDictionary *)dict
{
return [[self alloc] initWithDict:dict];
}
@end
2.主控制器ViewController
// ViewController.m
#import "ViewController.h"
#import "CZQuestion.h"
// 超级猜图app
@interface ViewController() <UIAlertViewDelegate>
// 所有问题数据的数组
@property(nonatomic, strong) NSArray *questions;
// 控制题目索引index,默认初始值0
@property(nonatomic, assign) int index;
// 记录头像图片原始的frame信息
@property(nonatomic, assign) CGRect iconFrame;
// 页面的控件引用。索引显示、积分显示、标题显示、主图片、【下一题】按钮。
@property(nonatomic, weak) IBOutlet UILabel *lblIndex;
@property(nonatomic, weak) IBOutlet UIButton *btnScore;
@property(nonatomic, weak) IBOutlet UILabel *lblTitle;
@property(nonatomic, weak) IBOutlet UIButton *btnIcon;
@property(nonatomic, weak) IBOutlet UIButton *btnNext;
// 用来放答案的view,用来放带选项的view
@property(nonatomic, weak) IBOutlet UIView *answerView;
@property(nonatomic, weak) IBOutlet UIView *optionView;
@property(nonatomic, weak) IBOutlet UIButton *cover; // 阴影按钮
- (IBAction)btnNextClick; // 下一题点击事件
- (IBAction)bigImage:(id)sender; // 显示大图
- (IBAction)btnIconClick; // 头像按钮的点击事件
- (IBAction)btnTipClick; // 提示按钮被点击
@end
@implementation ViewController
// 改变状态栏的文字颜色为白色
- (UIStatusBarStyle)preferredStatusBarStyle {
return UIStatusBarStyleLightContent;
}
// 隐藏状态栏
- (BOOL)prefersStatusBarHidden {
return YES;
}
// 懒加载数据
-(NSArray *)questions
{
if(_questions == nil) {
// 加载数据
NSString *path = [[NSBundle mainBundle] pathForResource:@"questions.plist" ofType:nil];
NSArray *arrayDict = [NSArray arrayWithContentsOfFile:path];
NSMutableArray *arrayModel = [NSMutableArray array];
// 遍历字典,转模型
for(NSDictionary *dict in arrayDict) {
CZQuestion *model = [CZQuestion questionWithDict:dict];
[arrayModel addObject:model];
}
_questions = arrayModel;
}
return _questions;
}
-(void)viewDidLoad {
[super viewDidLoad];
// 初始化显示第一题信息。
self.index = -1;
[self nextQuestion];
}
// 下一题按钮点击事件。
- (IBAction)btnNextClick {
[self nextQuestion];
}
// 下一题
-(void)nextQuestion {
// 1. 索引++,
self.index++;
// 判断索引是否越界:越界则提示用户。
if(self.index == self.questions.count) {
NSLog(@"答题完毕了!!");
// 弹出对话框,一般代理对象是控制器。代理对象要遵守UIAlertViewDelegate协议。
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"操作提示"
message:@"恭喜通关"
delegate:self
cancelButtonTitle:@"确定"
otherButtonTitles: nil];
[alertView show];
return;
}
// 2. 根据索引获取数据模型。
CZQuestion *model = self.questions[self.index];
// 3. 模型数据设置到界面上
[self setData:model];
// 5. 动态创建‘答案按钮’
[self makeAnswerButtons:model];
// 6. 动态生成带待选项
[self makeOptionButtons:model];
}
// 监听点击的是对话框的那个按钮。
// 实现UIAlertView的协议代理方法。buttonIndex == 0是取消。
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
// NSLog(@"%ld", buttonIndex);
if(buttonIndex == 0) {
// 让程序再次回到第1个问题
self.index = -1;
[self nextQuestion];
}
}
// 6. 动态生成 待选项按钮
-(void)makeOptionButtons:(CZQuestion *)model
{
// 6.0 设置optionView可用
self.optionView.userInteractionEnabled = YES;
// 6.1 清除view中所有的子控件
[self.optionView.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
// 6.2 获取当前题目,待选文字的数组
NSArray *words = model.options;
// 6.3 根据待选文字数组,循环来创建按钮
int columns = 7; // 每行item的个数
CGFloat optionW = 35;
CGFloat optionH = 35;
CGFloat margin = 10; // 间距
// 每行第一个item距离左边的距离
CGFloat marginLeft = (self.answerView.frame.size.width - columns*optionW - (columns-1)*margin)/2;
// 循环遍历添加待选项按钮
for (int i=0; i < words.count;i++ ) {
UIButton *btnOpt = [[UIButton alloc] init];
// 给每个option按钮一个唯一的tag值。用于按钮匹配
btnOpt.tag = i;
[btnOpt setBackgroundImage:[UIImage imageNamed:@"btn_option"] forState:UIControlStateNormal];
[btnOpt setBackgroundImage:[UIImage imageNamed:@"btn_option_highlighted"] forState:UIControlStateHighlighted];
[btnOpt setTitle:words[i] forState:UIControlStateNormal];
[btnOpt setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
// 计算行和列的索引。计算按钮坐标。设置按钮的frame
int colIdx = i % columns; // 列
int rowIdx = i / columns; // 行索引
CGFloat optionX = marginLeft + colIdx *(optionW + margin);
CGFloat optionY = 0 + rowIdx *(optionH + margin);
btnOpt.frame = CGRectMake(optionX, optionY, optionW, optionH);
// 把待选按钮,添加到optionsView中
[self.optionView addSubview:btnOpt];
// 待选按钮,注册单机事件
[btnOpt addTarget:self action:@selector(optionButtonClick:) forControlEvents:UIControlEventTouchUpInside];
}
}
// 待选按钮的点击事件,sender是点击的按钮对象
- (void)optionButtonClick:(UIButton *)sender
{
// 1.隐藏当前被点击按钮
sender.hidden = YES;
// 2.获取按钮上文字,放到答案的按钮上。
// NSString *text = [sender titleForState:UIControlStateNormal];
NSString *text = sender.currentTitle;
// 2.1.把文字显示到第1个为空的 ‘答案按钮’上
// 遍历每一个答案按钮
for(UIButton *answerBtn in self.answerView.subviews) {
// 判断当前按钮上的文字是否为nil
if(answerBtn.currentTitle != nil) {
// 设置答案按钮文字 + tag值设置
[answerBtn setTitle:text forState:UIControlStateNormal];
answerBtn.tag = sender.tag;
break;
}
}
// 3.判断答案按钮是否满了。
BOOL isFull = YES; // 假设一开始是填满的
// 用来保存用户输入的答案的字符串
NSMutableString *userInput = [NSMutableString string];
for(UIButton *answerBtn in self.answerView.subviews) {
// 判断当前按钮上的文字是否为nil
if(answerBtn.currentTitle != nil) {
isFull = NO; // 没填满,还能继续填答案
break;
} else {
// 答案的文字拼接
[userInput appendString: answerBtn.currentTitle];
}
}
// 4.填满了,则禁用optionView与用户交互
if(isFull) {
// 4.1 禁止‘待选按钮’被点击
self.optionView.userInteractionEnabled = NO;
// 获取正确答案
CZQuestion *model = self.questions[self.index];
// 4.2 填满了,就判断答案是否正确:
if([model.answer isEqualToString:userInput]) {
// 一致则设置答案按钮文字为蓝色,0.5s后显示自动跳转下一题;
// 1. 本题回答正确,+100分
[self addScore:100];
[self setAnswerButtonsColor:[UIColor blueColor]];
// 2. 延迟0.5s,自动跳转
[self performSelector:@selector(nextQuestion) withObject:nil afterDelay:0.5];
} else {
// 答案错了,设置答案文字为红色。
[self setAnswerButtonsColor:[UIColor redColor]];
}
}
}
// 根据指定的分数,对界面的按钮进行+-分。
-(void)addScore:(int )score
{
NSString *str = self.btnScore.currentTitle;
int currentScore = str.intValue; // string转int数字类型
currentScore += score;
// 计算后的分数,重新设置给按钮。
[self.btnScore setTitle:[NSString stringWithFormat:@"%d", currentScore] forState:UIControlStateNormal];
}
// 统一设置答案按钮的文字颜色
-(void)setAnswerButtonsColor:(UIColor *)color
{
for(UIButton *answerBtn in self.answerView.subviews) {
[answerBtn setTitleColor:color forState:UIControlStateNormal];
}
}
// 5.动态创建答案按钮
-(void)makeAnswerButtons:(CZQuestion *)model
{
// 5.0 清除说有的‘答案按钮’
// while (self.answerView.subviews.firstObject) {
// [self.answerView.subviews.firstObject removeFromSuperview];
// }
[self.answerView.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
// 5.1 获取当前答案的文字
NSUInteger len = model.answer.length;
// 5.2 循环创建答案按钮
CGFloat margin = 10;
CGFloat answerW = 35;
CGFloat answerH = 35;
CGFloat answerY = 0;
// 左右的间距
CGFloat marginLeft = (self.answerView.frame.size.width - (len * answerW) - (len-1)*margin)/2;
for(int i = 0; i < len; i++){
UIButton *btnAnswer = [[UIButton alloc] init];
// 设置答案按钮的背景,frame.动态计算两遍的间接。
[btnAnswer setBackgroundImage:[UIImage imageNamed:@"btn_answer"] forState:UIControlStateNormal];
[btnAnswer setBackgroundImage:[UIImage imageNamed:@"btn_answer_highlighted"] forState:UIControlStateHighlighted];
// 计算按钮的x值
CGFloat answerX = marginLeft + i *(answerW + margin);
btnAnswer.frame = CGRectMake(answerX, answerY, answerW, answerH);
// 设置按钮文字颜色
[btnAnswer setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
// 把按钮添加到answerView中
[self.answerView addSubview:btnAnswer];
// 答案按钮,注册单机事件。
[btnAnswer addTarget:self action:@selector(btnAnswerClick:) forControlEvents:UIControlEventTouchUpInside];
}
}
// 答案区的答案按钮,被点击。
-(void)btnAnswerClick:(UIButton *)sender
{
// 0. 启用optionView
self.optionView.userInteractionEnabled = YES;
// 1. 所有的答案按钮文字,恢复成黑色
[self setAnswerButtonsColor:[UIColor blackColor]];
// 2.在‘待选按钮’区域中,找到与当前点击文字相同的待选按钮,设置该按钮可见。
for (UIButton *optBtn in self.optionView.subviews) {
// 比较待选按钮的文字,是否与当前被点击答案按钮的文字一致。
// if([sender.currentTitle isEqualToString:optBtn.currentTitle]) {
// optBtn.hidden = NO;
// break;
// }
if(sender.tag == optBtn.tag) {
optBtn.hidden = NO;
break;
}
}
// 3.清空当前被点击的答案按钮的文字。
[sender setTitle:nil forState:UIControlStateNormal];
}
// 模型数据设置到界面上
-(void)setData:(CZQuestion *)model
{
// 3.模型数据设置到页面控件。
self.lblIndex.text = [NSString stringWithFormat:@"%d / %ld",(self.index+1), self.questions.count];
self.lblTitle.text = model.title;
[self.btnIcon setImage:[UIImage imageNamed:model.icon] forState:UIControlStateNormal];
// 4.到达最后一题,禁用【下一题】按钮
self.btnNext.enabled = (self.index != self.questions.count-1);
}
// 点击[变大]按钮,显示大图
-(IBAction)bigImage:(id)sender {
// 0.缓存头像图片frame信息。
self.iconFrame = self.btnIcon.frame;
// 1.创建大小与self.view一样的按钮,作为‘阴影’
UIButton *btnCover = [[UIButton alloc] init];
// 设置大小,背景色,透明度,背景按钮加到self.view上
btnCover.frame = self.view.bounds;
btnCover.backgroundColor = [UIColor blackColor];
btnCover.alpha = 0.0;
[self.view addSubview: btnCover];
// 给阴影注册点击事件
[btnCover addTarget:self action:@selector(smallImage)
forControlEvents:UIControlEventTouchUpInside];
// 2.把图片设置到阴影的上面
// self.view中的所有子控件中,只把self.btnIcon显示到最上面
[self.view bringSubviewToFront: self.btnIcon];
// 通过self.cover 来引用btnCover。其他方法中也能访问到该变量
self.cover = btnCover;
// 3.通过动画将图片btnIcon变大
CGFloat iconW = self.view.frame.size.width;
CGFloat iconH = iconW;
CGFloat iconX = 0;
CGFloat iconY = (self.view.frame.size.height - iconH)/2;
[UIView animateWithDuration:0.7 animations:^ {
// 设置图片新的frame信息(放大如果无效:属性--Use Auto Layout 去掉勾选)
self.btnIcon.frame = CGRectMake(iconX, iconY, iconW, iconH);
btnCover.alpha = 0.6;
}];
}
// 图标自己被点击:小图状态就变大图;大图状态就变小图。
- (IBAction)btnIconClick {
if(self.cover == nil) { // 变大图
[self bigImage:nil]; // 调用本类的方法,不传id
} else { // 变小图
[self smallImage];
}
}
// 提示按钮被点击,提示1次-1000分。
- (IBAction)btnTipClick {
// 1.分数-1000
[self addScore: -1000];
// 2.把所有的答案按钮‘清空’。(其实这里的清空,就是调用每个按钮的点击事件)
for(UIButton *btnAnswer in self.answerView.subviews) {
// 让每个答案按钮,自己点击自己一下。
[self btnAnswerClick:btnAnswer];
}
// 3.根据当前索引,从数据数组中找到对应的数据模型。
// 从中获取正确答案的第一个字符,把待选按钮中与这个字符相等的按钮,点击一下。
CZQuestion *model = self.questions[self.index];
// 中国人---> 中:第一个字符'字符串',直接显示到答案区域。字符串截取方法。
NSString *firstChar = [model.answer substringToIndex:1];
// 4.根据firstChar,在option按钮中找到option按钮,让他点击一下。
for(UIButton *btnOpt in self.optionView.subviews) {
if([btnOpt.currentTitle isEqualToString:firstChar]) {
[self optionButtonClick:btnOpt]; // option按钮自己点击一下
break;
}
}
}
// '阴影'的单击事件
- (void)smallImage {
[UIView animateWithDuration:0.7 animations: ^{
// 1.还原btnIcon的frame还原
self.btnIcon.frame = self.iconFrame;
// 2.阴影的透明度变成0
self.cover.alpha = 0.0;
} completion: ^(BOOL finished) {
if(finished) {
// 3.移除'阴影'按钮. cover赋值为nil,便于标记当前是大图还是小图状态。
[self.cover removeFromSuperview];
self.cover = nil;
}
}];
}
@end
1~15:加法器功能实现。
16~23:图片浏览器,汤姆猫图片优化。
24~34: 九宫格显示app管理页面,item显示下载按钮。
35~48:超级猜图app
49~60:喜马拉雅案例,图片轮播,实现缩放。
61~72:
73~83:
84~96:
97~106:
107~120: