UI008:超级猜图app

案例截图

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:



最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容

  • 2023.5.5周五 吃了辣酱于是肚子一直很痛,睡不好,梦见到处找厕所,睡前右眼又过敏水肿,好在半夜起来去厕所发现...
    偏偏喜欢你sky阅读 70评论 0 1
  • 缘起有24种,因缘有6种,佛是能够看见世间一切缘起的人,修行必须身心合一,看见自己的缘起。 缘起诸支不应是对于生死...
    S爱笑的眼睛阅读 58评论 0 1
  • Ukrainischer Kult-Comic kommt nach Berlin:Alle lieben „Ma...
    Eva_9c90阅读 577评论 0 0
  • 2023.5.6 心若有所向往,何惧道阻且长。 读书卡 1.真正的修行是内观和觉察,大自然的振动频率特别高,是最接...
    坚持读写阅读 102评论 0 0
  • 希望我这个爱哭、爱笑、爱瞎想的人,调整好心态,拐个弯,与自己和解,未来的日子,会越来越好
    碎碎念丶阅读 67评论 0 0