[注意]转载时请注明出处博客园-吃唐僧肉的小悟空http://www.cnblogs.com/hukezhu/
本篇文章介绍一个比较综合的小应用----超级猜图.
功能分析:
-
- 根据显示的图片,在下面的待选项按钮中选中正确答案按钮,选中的按钮会显示在正确答案按钮中
- 答案错误,答案颜色变为红色,分数减小
- 答案正确,答案颜色变为蓝色,两秒自动跳入下一题,分数增加
- 点击"下一题"可以进入下一个题目
- 点击"大图",可以放大显示图片,再次点击图片或者背景,图片缩小至原来大小
- 点击"提示",自动情况答案选项,并显示正确答案的第一个字,并且分数减小
应用截图:
下面附上完成这个小应用的思路:
1 1. 通过storyboard设计"超级猜图"的上半部分界面。 2 1> 使用UIImageView做背景(最后面的背景图片,首先添加这个) 3 2> 分析界面上的控件,显示图片的也使用button,显示内容的使用label, 4 下面显示答案按钮和最下面显示待选项按钮的位置使用view 5 中间的图片可以点击:用button,button有一个inset:分别设置内部内容距离上左下右有多少不能显示 6 7 8 2. 实现点击"下一题"功能.(最开始状态是没有数据的,首先点击下一题,我们家在数据之后,才能做其他的功能,所以最开始先完成"下一题"的功能) 9 * 声明一个index属性来记录当前显示的题目的索引。 10 11 * 当我们点击"下一题"的时候, 从数组中获取对应的题目, 并显示到对应的界面控件上。 12 13 14 * 解决:最后一题之后再次点击"下一题"索引越界问题。(此处进行判断) 15 16 /* 0.判断索引值是否越界,并且弹框 17 if (self.index == self.questions.count - 1) { 18 // UIAlertView *alert = [[UIAlertView alloc]initWithTitle:@"恭喜" message:@"过关了" delegate:nil 19 // cancelButtonTitle:@"取消" otherButtonTitles:@"A", nil]; 20 // [alert show]; 21 UIActionSheet *acionsheet = [[UIActionSheet alloc]initWithTitle:@"恭喜" delegate:self cancelButtonTitle:@"取消" destructiveButtonTitle:@"确定" otherButtonTitles:@"A",@"B", nil]; 22 [acionsheet showInView:self.view]; 23 24 return; 25 } 26 27 */ 28 29 30 3. 在viewDidLoad中初始化第一个问题(显示第一个问题到界面上)。 31 * 设置self.index = -1 32 * 手动调用"下一题"按钮的点击事件 33 [self next]; 34 35 36 37 4. 实现"查看大图"功能(点击右侧"大图"按钮, 显示大图) 38 39 * 实现思路(步骤): 40 1> 添加一个"阴影"按钮, 因为该"阴影"要实现点击, 所以用"按钮" 41 2> 然后再把"头像按钮"显示在"阴影"上面。 42 3> 通过动画的方式改变"头像按钮"的frame(位置和尺寸)变成大图效果。 43 ** 注意: 取消"自动布局(Auto Layout)" 44 45 ** 点击"遮罩阴影", 回到小图 46 1> 通过动画慢慢将"遮罩阴影"的透明度变为0 47 2> 通过动画慢慢将"头像图片"的frame修改为原来的位置 48 3> 在动画执行完毕后, 移除"遮罩阴影" 49 50 ** 点击图片本身显示大图, 再次点击图片本身显示小图 51 52 53 54 55 56 5. 动态生成"答案按钮"。 57 * 思路: 58 0> 在点击"下一题"按钮中实现该功能 59 1> 创建一个UIView来存放所有的"答案按钮" 60 2> 根据每个问题的答案的文字个数来创建按钮 61 3> 每次创建按钮之前, 先把旧的按钮都删除 62 此处有两种方案: 63 1-循环便利数组,执行removefromsuperview 64 2-使用makeObjectsPerformSelector:@selector(removeFromSuperview) 65 4> 指定每个"答案按钮"的尺寸和中间的margin, 然后计算第一个按钮的x值(marginLeft)。 66 5> 在循环中, 计算每个按钮的x值(y值都是0)。 67 68 69 70 6. 动态生成"待选项按钮"。 71 * 思路: 72 0> 在点击"下一题"按钮中实现该功能 73 1> 创建一个UIView来存放所有的"待选项按钮" 74 2> 根据待选项按钮的个数来创建 75 3> 清除之前的待选项按钮 76 4> 利用九宫格的算法创建待选项按钮 77 78 7. 实现"待选按钮"的单击事件 79 * 隐藏当前被点击的"待选按钮" 80 * 将当前被点击的"待选按钮"的文字显示到"答案按钮"的左起第一个为空的按钮上 81 * 如果"答案按钮"已经全部填满了, 不允许再点击"待选按钮" 82 83 ** 注意: 只要父控件不能处理事件, 那么子控件也无法处理事件(另外一种解决方案)。 84 1> 如果"答案按钮"文字填满了, 则设置option view禁止与用户交互 85 self.optionsView.userInteractionEnabled = NO; 86 87 2> 当用户再次点击"答案按钮"时 或 点击"下一题"后在创建"待选按钮"的时候再次启用option view与用户的交互功能 88 self.optionsView.userInteractionEnabled = YES; 89 90 91 92 93 8. 实现"答案按钮"的单击事件 94 * 设置被点击的"答案按钮"文字为空(nil) 95 * 设置与当前被点击的"答案按钮"相对应的"待选按钮"显示出来 96 ( 97 ** 注意:当答案按钮中有两个相同的文字的option按钮时的问题 98 ** 解决方案一: 99 1> 为每个option按钮设置一个唯一的tag 100 2> 在点击某个option按钮的时候, 把option按钮的text和tag都设置到answer按钮上 101 3> 在点击answer按钮的时候, 判断answer按钮的文字与tag同时都与某个option按钮匹配时, 再显示这个option按钮 102 ** 解决方案二: 103 执行判断的时候,并且判断字体的hidden属性为YES 104 ) 105 * 设置所有"答案按钮"的文字颜色为黑色 106 107 108 9. 在"待选按钮"的单击事件中, 判断当前的答案的正确性 109 * 每次点击"待选按钮"都需要做判断, 如果答案按钮"满了", 再去判断正确性 110 * 如果正确: 111 1> 那么设置"答案按钮"的文字颜色为蓝色 112 2> 加分 113 3> 2秒钟后自动跳转到下一题 114 115 * 如果错误: 116 1> 答案按钮的文字设置为红色 117 118 119 10. 点击"提示"按钮 120 * 加分 121 * 清空所有"答案按钮"的文字(相当于点击了每一个"答案按钮", 不是简单的设置"答案按钮"的文字为nil) 122 * 并将正确答案的第一个文字设置到第一个"答案按钮"上(相当于正确答案的option按钮被点击了), 通过调用字符串的substringToIndex:来截取第一个字符的字符串
[注意]转载时请注明出处博客园-吃唐僧肉的小悟空http://www.cnblogs.com/hukezhu/
[注意]转载时请注明出处博客园-吃唐僧肉的小悟空http://www.cnblogs.com/hukezhu/
附上应用代码结构图:
storyboard拖线图:
附上源代码:
KZAppModel.h
1 // 2 // KZAppModel.h 3 // UI基础-04-05-16 4 // 5 // Created by hukezhu on 15/5/16. 6 // 7 // 8 9 #import <Foundation/Foundation.h> 10 11 @interface KZAppModel : NSObject 12 @property (nonatomic,copy) NSString *answer; 13 @property (nonatomic,copy) NSString *icon; 14 @property (nonatomic,copy) NSString *title; 15 @property (nonatomic,strong)NSArray *options; 16 17 - (instancetype)initWithDict:(NSDictionary *)dict; 18 19 + (instancetype)appWithDict:(NSDictionary *)dict; 20 @end
KZAppModel.m
1 // 2 // KZAppModel.m 3 // UI基础-04-05-16 4 // 5 // Created by hukezhu on 15/5/16. 6 // 7 // 8 9 #import "KZAppModel.h" 10 11 @implementation KZAppModel 12 -(instancetype)initWithDict:(NSDictionary *)dict{ 13 14 if (self = [super init]) { 15 self.answer = dict[@"answer"]; 16 self.icon = dict[@"icon"]; 17 self.title = dict[@"title"]; 18 self.options = dict[@"options"]; 19 } 20 return self; 21 } 22 23 + (instancetype)appWithDict:(NSDictionary *)dict{ 24 25 return [[self alloc]initWithDict:dict]; 26 } 27 @end
ViewController.m
1 // 2 // ViewController.m 3 // 01-超级猜图 4 // 5 // Created by hukezhu on 15/5/16. 6 // [注意]转载时请注明出处博客园-吃唐僧肉的小悟空http://www.cnblogs.com/hukezhu/ 7 // 8 9 #import "ViewController.h" 10 #import "KZAppModel.h" 11 12 @interface ViewController () 13 @property (nonatomic,assign)int index; 14 @property (weak, nonatomic) IBOutlet UIButton *big; 15 @property (weak, nonatomic) IBOutlet UIButton *head; 16 @property (weak, nonatomic) IBOutlet UILabel *noLabel; 17 @property (weak, nonatomic) IBOutlet UILabel *titleLabel; 18 - (IBAction)nextClick; 19 @property (weak, nonatomic) IBOutlet UIView *answerView; 20 @property (weak, nonatomic) IBOutlet UIView *optionView; 21 @property (weak, nonatomic) IBOutlet UIButton *coinBtn; 22 - (IBAction)tipClick; 23 24 - (IBAction)bigImg; 25 - (IBAction)headClick; 26 27 @property (nonatomic,strong) UIButton *cover; 28 @property (nonatomic,strong)NSArray *questions; 29 @end 30 31 @implementation ViewController 32 33 - (void)viewDidLoad { 34 [super viewDidLoad]; 35 self.index = -1; 36 [self nextClick]; 37 38 } 39 40 /** 41 * 懒记载 42 */ 43 - (IBAction)nextClick { 44 //判断当前索引,如果到达数据数组长度减1 45 if (self.index == self.questions.count - 1 ) { 46 UIAlertView *alert = [[UIAlertView alloc]initWithTitle:@"提示" message:@"已经到达最后一张图片" delegate:nil cancelButtonTitle:@"取消" otherButtonTitles:@"好的", nil]; 47 [alert show]; 48 return; 49 } 50 51 self.index++; 52 53 //创建一个模型,存储数据 54 KZAppModel *appModel = self.questions[self.index]; 55 56 //添加数据 57 [self addData:appModel]; 58 59 //添加答案按钮 60 [self addAnswerBtn:appModel]; 61 62 //添加待选项按钮 63 [self addOptionBtn:appModel]; 64 } 65 66 67 68 /** 69 * 添加待选项按钮 70 * 71 * @param appModel 数据模型 72 */ 73 - (void)addOptionBtn:(KZAppModel *)appModel{ 74 75 //清除之前的待选项按钮 76 [self.optionView.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)]; 77 //定义每行显示的待选项按钮的个数 78 int totalCol = 7; 79 CGFloat optionW = 40; 80 CGFloat optionH = 40; 81 CGFloat optionMargin = 10; 82 //左边距 83 CGFloat leftOptionMargin = (self.optionView.frame.size.width - totalCol * optionW - (totalCol - 1 )*optionMargin)*0.5; 84 //遍历数组,创建待选项按钮(九宫格的创建方法) 85 for (int i = 0; i < appModel.options.count; i++) { 86 87 UIButton *optionBtn = [[UIButton alloc]init]; 88 //行号 89 int row = i / totalCol; 90 //列号 91 int col = i % totalCol; 92 CGFloat optionX = leftOptionMargin + col * (optionMargin + optionW); 93 CGFloat optionY = optionMargin + row * (optionMargin + optionH); 94 optionBtn.frame = CGRectMake(optionX, optionY, optionW, optionH); 95 //设置两种状态下的背景图片 96 [optionBtn setBackgroundImage:[UIImage imageNamed:@"btn_option"] forState:UIControlStateNormal]; 97 [optionBtn setBackgroundImage:[UIImage imageNamed:@"btn_option_highlighted"] forState:UIControlStateHighlighted]; 98 //给这些待选项按钮添加文字 99 [optionBtn setTitle:appModel.options[i] forState:UIControlStateNormal]; 100 //设置字体颜色 101 [optionBtn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal]; 102 //将按钮添加到optionView中 103 [self.optionView addSubview:optionBtn]; 104 105 //创建待选项按钮的点击方法 106 [optionBtn addTarget:self action:@selector(optionClick:) forControlEvents:UIControlEventTouchUpInside]; 107 108 } 109 110 } 111 112 113 /** 114 * 添加答案按钮 115 * 116 * @param appModel 模型数据 117 */ 118 - (void)addAnswerBtn:(KZAppModel *)appModel{ 119 120 //清除之前的答案按钮 121 [self.answerView.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)]; 122 123 124 125 CGFloat answerW = 30; 126 CGFloat answerH = 30; 127 CGFloat answerY = 0; 128 CGFloat margin = 10; 129 CGFloat leftMargin = (self.answerView.frame.size.width - appModel.answer.length *(margin + answerW))*0.5; 130 //循环遍历数组,创建答案按钮 131 for(int i = 0; i < appModel.answer.length; i++){ 132 133 UIButton *answerBtn = [[UIButton alloc]init]; 134 135 CGFloat answerX =leftMargin + i *(margin + answerW); 136 answerBtn.frame = CGRectMake(answerX, answerY, answerW, answerH); 137 //设置背景图片 138 [answerBtn setBackgroundImage:[UIImage imageNamed:@"btn_answer"] forState:UIControlStateNormal]; 139 140 //设置显示文字的颜色 141 [answerBtn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal]; 142 [self.answerView addSubview:answerBtn]; 143 144 //创建点击按钮的点击响应方法 145 [answerBtn addTarget:self action:@selector(answerClick:) forControlEvents:UIControlEventTouchUpInside]; 146 } 147 148 } 149 150 151 152 153 /** 154 * 添加数据 155 * 156 * @param appModel 数据模型 157 */ 158 -(void)addData:(KZAppModel *)appModel{ 159 160 161 162 self.noLabel.text = [NSString stringWithFormat:@"%d/%ld",self.index + 1,self.questions.count]; 163 164 self.titleLabel.text = appModel.title; 165 166 [self.head setImage:[UIImage imageNamed:appModel.icon] forState:UIControlStateNormal]; 167 } 168 169 170 - (void)optionClick:(UIButton *)optionBtn{ 171 172 //首先将被点击的待选项按钮的文字取到(后面会讲这个文字隐藏) 173 NSString *title = [optionBtn currentTitle]; 174 optionBtn.hidden = YES; 175 176 //循环遍历答案按钮,遇到第一个不为空的按钮,显示被点击的待选项按钮的文字 177 for (UIButton *answerBtn in self.answerView.subviews) { 178 if ([answerBtn currentTitle] == nil ) { 179 [answerBtn setTitle:title forState:UIControlStateNormal]; 180 break; 181 } 182 } 183 184 //判断答案是否为满,如果满了,就去判断对不对 185 //定义一个可变的字符串,用来拼接循环得到的答案,得到这个答案,去和正确答案比较 186 NSMutableString *tempString = [NSMutableString string]; 187 BOOL full = YES; 188 189 //遍历数组,如果遇到按钮为空的话,就赋值full标记为no,表明没有满,否则的话就去拼接 190 for (UIButton *answerBtn in self.answerView.subviews) { 191 if ([answerBtn currentTitle] == nil ) { 192 full = NO; 193 }else{ 194 195 [tempString appendString:[answerBtn currentTitle]]; 196 } 197 198 } 199 if (full) { 200 201 //NSLog(@"满了"); 202 KZAppModel *appModel = self.questions[self.index]; 203 for (UIButton *optionBtn in self.optionView.subviews) { 204 optionBtn.enabled = NO; 205 } 206 207 208 if ([tempString isEqualToString:appModel.answer]) { 209 NSLog(@"答对了"); 210 for (UIButton *answerBtn in self.answerView.subviews){ 211 [answerBtn setTitleColor:[UIColor blueColor] forState:UIControlStateNormal]; 212 213 } 214 // int score = [self.coinBtn currentTitle].intValue ; 215 // score += 50; 216 // [self.coinBtn setTitle:[NSString stringWithFormat:@"%d",score] forState:UIControlStateNormal]; 217 [self score:50]; 218 219 [self performSelector:@selector(nextClick) withObject:nil afterDelay:2.0]; 220 221 222 223 }else{ 224 225 NSLog(@"没答对"); 226 for (UIButton *answerBtn in self.answerView.subviews){ 227 [answerBtn setTitleColor:[UIColor redColor] forState:UIControlStateNormal]; 228 [self score:-200]; 229 } 230 } 231 } 232 } 233 234 /** 235 * 答案选线点击的方法 236 * 237 */ 238 - (void)answerClick:(UIButton *)answerBtn{ 239 240 //循环遍历待选项按钮,找到与答案相同的文字,删除答案文字,显示待选项文字 241 for (UIButton *optionBtn in self.optionView.subviews) { 242 if ([[optionBtn currentTitle] isEqualToString:[answerBtn currentTitle]] && optionBtn.hidden == YES) { 243 [answerBtn setTitle:nil forState:UIControlStateNormal]; 244 optionBtn.hidden = NO; 245 for (UIButton *optionBtn in self.optionView.subviews) { 246 optionBtn.enabled = YES; 247 } 248 } 249 } 250 251 for (UIButton *answerBtn in self.answerView.subviews) { 252 [answerBtn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal]; 253 } 254 } 255 256 257 /** 258 * 点击提示的响应方法 259 */ 260 - (IBAction)tipClick { 261 //清除所有的答案按钮 262 for (UIButton *answerBtn in self.answerView.subviews) { 263 [self answerClick:answerBtn]; 264 } 265 //取出正确答案,显示出来 266 KZAppModel *appModel = self.questions[self.index]; 267 NSString *str = [appModel.answer substringToIndex:1]; 268 269 270 // /** 271 // * 最开始想的是这种方法,但是发现更好的办法,,就相当于点击了对应的待选项按钮 272 // */ 273 // [self.answerView.subviews[0] setTitle:str forState:UIControlStateNormal]; 274 // [self.answerView.subviews[0] setTitleColor:[UIColor blackColor] forState:UIControlStateNormal]; 275 276 for (UIButton *optionBtn in self.optionView.subviews) { 277 if ([[optionBtn currentTitle] isEqualToString:str]) { 278 [self optionClick:optionBtn]; 279 } 280 } 281 282 [self score:-100]; 283 284 285 } 286 287 - (IBAction)bigImg { 288 //点击大图之后,先创建一个阴影(button), 289 UIButton *cover = [[UIButton alloc]init]; 290 self.cover = cover; 291 cover.frame = self.view.bounds; 292 [cover setBackgroundColor:[UIColor blackColor]]; 293 cover.alpha = 0.0; 294 [self.view addSubview:cover]; 295 //调整图片和阴影位置 296 [self.view bringSubviewToFront:self.head]; 297 298 [cover addTarget:self action:@selector(coverClick) forControlEvents:UIControlEventTouchUpInside]; 299 [UIView animateWithDuration:2.0 animations:^{ 300 cover.alpha = 0.6; 301 //放大图片 302 303 CGFloat headW = self.view.frame.size.width; 304 CGFloat headH = headW; 305 CGFloat headX = 0; 306 CGFloat headY = 0.5 * (self.view.frame.size.height- headH); 307 self.head.frame = CGRectMake(headX, headY, headW, headH); 308 309 }]; 310 311 } 312 313 314 /** 315 * 计算分数方法 316 * 317 * @param score 传入需要增加或者减少的分数(减为负) 318 */ 319 - (void)score:(int)score{ 320 321 int tempScore = [self.coinBtn currentTitle].intValue ; 322 tempScore += score; 323 [self.coinBtn setTitle:[NSString stringWithFormat:@"%d",tempScore] forState:UIControlStateNormal]; 324 325 } 326 327 /** 328 * 图片放大状态下,点击阴影缩小图片 329 */ 330 -(void)coverClick{ 331 332 if (self.cover != nil) { 333 [self smallImg]; 334 335 } 336 } 337 /** 338 * 点击头像图片,放大或缩小 339 */ 340 - (IBAction)headClick { 341 if (self.cover == nil) { 342 [self bigImg]; 343 }else{ 344 345 [self smallImg]; 346 } 347 } 348 /** 349 * 缩小图片的方法 350 */ 351 - (void)smallImg{ 352 353 354 [UIView animateWithDuration:2.0 animations:^{ 355 self.cover.alpha = 0.0; 356 self.head.frame = CGRectMake(97, 120, 180, 180); 357 self.cover = nil; 358 }]; 359 360 } 361 362 363 364 /** 365 * 懒加载数据 366 * 367 * @return 返回数据 368 */ 369 -(NSArray *)questions{ 370 371 if(_questions == nil){ 372 NSString *path = [[NSBundle mainBundle]pathForResource:@"questions" ofType:@"plist"]; 373 NSArray *dictArray = [NSArray arrayWithContentsOfFile:path]; 374 NSMutableArray *tempArray = [NSMutableArray array]; 375 for (NSDictionary *dict in dictArray) { 376 KZAppModel *appModel = [KZAppModel appWithDict:dict]; 377 [tempArray addObject:appModel]; 378 } 379 _questions = tempArray; 380 381 } 382 return _questions; 383 } 384 385 @end
KVC大体介绍:
1 KVC(Key Value Coding) 2 介绍: 通过给定一个对象的属性名称(以字符串方式), 然后找到对象的相应属性进行赋值。 3 4 * setValue:forKeyPath: 5 ** 示例: [self setValue:dict[@"icon"] forKeyPath:@"icon"] 6 ** 含义: 表示根据forKeyPath:@"icon"提供的@"icon"去self对象中查找名字叫icon的属性, 找到以后把dict[@"icon"]中获取到的值赋值给self的icon属性。 7 8 9 * setValuesForKyesWithDictionary: 10 ** 含义: 更简便的调用方式。内部相当于调用了多次setValue:forKeyPath: 11 12 ** 注意: 13 1> 必须保证字典中的key与模型的属性名称一致。 14 2> 必须保证模型的属性个数与字典一致或者模型的属性个数要大于等于字典的个数。 15 16 * 演示KVC: 17 1> 新建一个model类。 18 2> 演示对字符串类型、数字类型进行KVC赋值 19 3> 通过KVC取值。 20 id v = [对象 valueForKeyPath:@"key"]; 21 int v1 = [[对象 valueForKeyPath:@"key"] intValue]; 22 23 4> 把模型转成字典, 把对象中指定的属性转换为字典。 24 NSDictioanry *dict = [对象 dictionaryWithValuesForKeys:@[@"name", @"age"]]; 25 26 5> 把一个person数组中的每个person对象的name都获取出来然后放到一个新的数组中。 27 ( 28 NSArray *names = [person数组 valueForKeyPath:@"name"]; 29 ) 30 31 6> keyPath介绍, 人拥有一本书, 通过kvc获取人所拥有的书的名称 32 ( 33 NSString *bkName = [person valueForKeyPath:@"book.name"]; 34 35 等价于 36 37 NSString *bkName = person.book.name; 38 ) 39 40 * KVO (Key Value Observing), 监听对象的属性值变化。
[注意]转载时请注明出处博客园-吃唐僧肉的小悟空http://www.cnblogs.com/hukezhu/