天天看点

[iOS开发项目-7] 超级猜图

本项目是取自传智播客的教学项目,加入笔者的修改和润饰。

1. 项目名称:超级猜图

2. 项目截图展示

[iOS开发项目-7] 超级猜图

3. 项目功能

  1. 点击图片或“大图”按钮,图片放大;再点击图片或点击周围区域,图片复原。
  2. 点击备选按钮,相应字填入答案区按钮。
  3. 按“下一题”按钮或答案正确:进入下一题。
  4. 点击答案区某按钮,相应字回到备选区原来位置。
  5. 点击“帮助按钮”会清空答案区按钮,并提示正确答案的第一个字。
  6. 答案正确或错误都有相应的扣分和加分。

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;