功能分析
- 以九宫格的形式展示应用信息
- 点击下载按钮后,做出相应操作(弹出一个提示"正在下载",相应应用的下载按钮变为"已下载")
步骤分析
- 搭建UI界面
- 加载应用信息
- 根据应用的个数创建对应的view
- 监听下载按钮的点击
应用截图:
这个小的综合实例涉及到的知识点:
- UIView的常见属性和方法
- 九宫格的计算方法
- 字典转模型
- xib的使用
- view的封装
- 简单的MVC
九宫格分析:
搭建九宫格的步骤
- 明确每一块用的是什么view
- 明确每个view之间的父子关系
- 先尝试逐个添加格子,最后考虑使用for循环
- 加载app数据,根据数据的长度创建对应个数的格子
- 添加格子内部的子控件
- 给格子内部的子控件装载数据
使用到的一些控件的常用方法
1.UILabel的常用属性:
//显示的文字 @property(nonatomic,copy) NSString *text; //字体 @property(nonatomic,retain) UIFont *font; //文字颜色 @property(nonatomic,retain) UIColor *textColor; //对齐模式(比如左对齐、居中对齐、右对齐) @property(nonatomic) NSTextAlignment textAlignment;
2.UIFont的常见设置方法
//系统默认字体 + (UIFont *)systemFontOfSize:(CGFloat)fontSize; //粗体 + (UIFont *)boldSystemFontOfSize:(CGFloat)fontSize; //斜体 + (UIFont *)italicSystemFontOfSize:(CGFloat)fontSize;
3.UIButton的常见方法
//设置按钮的文字 - (void)setTitle:(NSString *)title forState:(UIControlState)state; //设置按钮的文字颜色 - (void)setTitleColor:(UIColor *)color forState:(UIControlState)state; //设置按钮内部的小图片 - (void)setImage:(UIImage *)image forState:(UIControlState)state; //设置按钮的背景图片 - (void)setBackgroundImage:(UIImage *)image forState:(UIControlState)state; //设置按钮的文字字体(需要拿到按钮内部的label来设置) //btn.titleLabel.font = [UIFont systemFontOfSize:13];
整个应用的思路分析:
1 /************************ "应用管理"思路分析************************/ 2 3 1. 功能分析 4 * 以九宫格的方式展示应用信息 5 * 点击"下载"按钮做出相应操作 6 7 8 2. 步骤分析 9 * 从对应的app.plist文件中加载数据("应用信息") 10 * 根据加载好的"应用信息"的数据创建对应的view 11 * 监听"下载"按钮的点击事件 12 13 14 3. 开始编写代码 15 16 1> 根据apps中的数据, 创建相应的view并展示到屏幕上 17 一、分析: 18 * 在界面一加载完毕就看到应用, 所以要在viewDidLoad方法中创建应用 19 * 分析每个一个应用中都包含了一个UIImageView、UILabel、UIButton 20 * 为了便于统一管理, 为每一个应用封装一个UIView, 然后在该UIView内部再放上面3个子控件 21 22 二、创建UIView, 并添加到屏幕上 23 * 通过代码实现,一个一个的创建UIView 24 * 保证每行的应用之间的间距相等, 所以要计算每行的应用之间的间距{间距 = (屏幕宽度 - (3 * 每个应用的宽度)) / 4} 25 26 1. 添加一个UIView 27 /* // 添加第一个格子 28 UIView *appView = [[UIView alloc]init];//创建 29 CGFloat appViewX = 20; 30 CGFloat appViewY = 30; 31 CGFloat appViewW = 100; 32 CGFloat appViewH = 120; 33 appView.frame = CGRectMake(appViewX, appViewY, appViewW, appViewH);//设置frame 34 [self.view addSubview:appView]; 35 appView.backgroundColor = [UIColor redColor];//设置背景色 36 */ 37 2. 通过循环根据app的个数添加多个UIView, 但是会覆盖, 因为frame相同 38 for循环的次数取决于应用的个数(这里读取plist文件的数据,根据数据的长度确定循环的次数) 39 40 2> 懒加载数据 41 * 通过懒加载的方式加载app.plist中的数据到NSArray中 42 /* - (NSArray *)apps{ 43 44 if (_apps == nil) { 45 //1.获取plis文件的路径 46 NSString *path = [[NSBundle mainBundle]pathForResource:@"app.plist" ofType:nil]; 47 //2.读取文件 48 _apps = [NSArray arrayWithContentsOfFile:path]; 49 50 } 51 return _apps; 52 } 53 54 */ 55 56 3. 计算第一个UIView的x和y(注意将里面的具体数字提取出来,便于后面使用和扩展)leftMargin ,topMaring,spacexMarign,spaceyMargrin 57 58 4. 然后分析每个UIView的x和y,找出规律: 59 x = leftMargin +列号 *(spacexMargin + appViewW); 60 Y = topMargin + 行号*(spaceyMargin + appViewH); 61 ) 62 63 5. 引入需要基础每个UIView所在的行的索引和列的索引 64 int row = i /3;//行号 65 int col = i % 3;//列号 66 67 6.设置view的frame 68 CGFloat appViewX = leftMargin + (appViewW + spaceX)*col; 69 CGFloat appViewY = topMargin + (appViewH + spaceY )*row; 70 // 2.2设置frame 71 appView.frame = CGRectMake(appViewX, appViewY, appViewW, appViewH); 72 73 74 75 */ 76 77 78 4. 在每个UIView中创建3个子元素: UIImageView、UILabel、UIButton 79 * UIImageView: 80 CGFloat iconW = 60; 81 CGFloat iconH = 60; 82 CGFloat iconX = (appView.frame.size.width - iconW) * 0.5; 83 CGFloat iconY = 0;) 84 85 * UILabel: 86 CGFloat titleW = appW; 87 CGFloat titleH = 20; 88 CGFloat titleX = 0; 89 CGFloat titleY = iconY + iconH; 90 91 * UIButton 92 CGFloat downloadW = appViewW - downLoadBtnX *2; 93 CGFloat downloadH = 30; 94 CGFloat downloadX = 10; 95 CGFloat downloadY = nameLabelY + nameLabelH + 10; 96 downloadBtn.frame = CGRectMake(downloadX, downloadY, downloadW, downloadH); 97 98 99 5> 向UIImageView、UILabel、UIButton中添加数据 100 1.取得字典 101 NSDictionary *dict = self.apps[i]; 102 head.image = [UIImage imageNamed:dict[@"icon"]];//图片赋值数据 103 nameLabel.text = dict[@"name"];//名字赋值数据 104 [downLoadBtn setTitle:@"下载" forState:UIControlStateNormal];//设置文字 105 106 107 108 109 110 6> 修改Label的字体、文字居中显示、按钮背景图片、设置按钮的字体大小。 111 // 2.5.2设置文字大小 112 nameLabel.font = [UIFont systemFontOfSize:13]; 113 // 2.5.3设置文字居中 114 nameLabel.textAlignment = NSTextAlignmentCenter; 115 downLoadBtn setBackgroundImage:[UIImage imageNamed:@"buttongreen"] forState:UIControlStateNormal]; 116 [downLoadBtn setBackgroundImage: [UIImage imageNamed:@"buttongreen_highlighted"] forState:UIControlStateHighlighted]; 117 118 119 120 * 了解按钮的内部结构:按钮至少有两个控件,一个label,一个imageView; 121 *修改按钮文字的大小直接拿到他的对应的label进行设置即可 122 downLoadBtn.titleLabel.font = [UIFont systemFontOfSize:14]; 123 124 125 * 注意: 修改按钮的文字的时候还是通过setTitle:来修改(这个方法可以设置不同状态), 不要直接通过titleLabel来修改文字。 126 //* UIImageView设置图片居中:contentMode属性 127 128 129 130 7. 字典转模型 131 0> 字典:用来存储数据的,用键值对存储数据,是一个nsdictionary ,(不好处:key值容易写错) 132 模型: 用来存储数据的,一个字典对应一个模型,模型用属性来存储数据,是以纯洁的object对象 133 @property(nonatomic,copy)NSString *name; 134 @property(nonatomic,copy)NSString *icon; 135 136 字典转模型:一个字典对应一个模型,把键值对转化模型的属性,就是把键值对的value赋值给模型的属性 137 // appViewModel.name = dict[@"name"]; 138 // appViewModel.icon = dict[@"icon"]; 139 140 属性的类型和名称:属性的类型和键值对的key值的类型是一样,最好名称也和key值的名称一样 141 1> 为什么要把字典转成模型? 142 * 字典缺陷: 143 0> 写代码的时候字典的键没有智能提示, 但是模型的属性可以有智能提示 144 1> "键"是字符串, 如果写错了, 编译器不报错(在编译的时候不报错), 运行时可能出错, 出错了很难找错。 145 146 147 148 149 150 151 152 * 把字典转模型的过程封装到"模型"内部 153 * 原因: 将来的这个"模型"可能会在很多地方被用到(比如有很多个控制器都会使用这个模型), 那么每次用到模型的地方都需要写一次把字典中的数据赋给模型属性的代码, 此时如果把这些赋值语句封装到模型内部, 会大大简化了使用复杂度与代码量。 154 * 思路: 155 1> 在模型中接收一个NSDictionary的参数, 然后在模型内部把NSDictioanry中的键值对数据赋值给模型的属性。 156 2> 封装一个initWithDict方法和一个appWithDict方法(规范) 157 - (id)initWithDict:(NSDictionary *)dict{ 158 159 160 161 if (self = [super init]) { 162 self.name = dict[@"name"]; 163 self.icon = dict[@"icon"]; 164 165 } 166 return self; 167 168 169 } 170 171 + (id)appViewWithDict:(NSDictionary *)dict{ 172 // AppViewModel *appViewModel = [[AppViewModel alloc]initWithDict:dict]; 173 // 174 // return appViewModel; 175 return [[self alloc ]initWithDict:dict];//必须用self,因为这样创建出来的对象才是真实我们需要的对象, 176 回忆:GoodApp; 177 } 178 179 180 181 * id与instancetype的介绍 182 1. 使用id作为方法返回值的问题: 183 1> 在接收方法的返回值的时候可以使用任何类型来接收, 编译都不报错, 但是运行时可能出错。 184 185 2. instancetype需要注意的点 186 1> instancetype在类型表示上, 与id意思一样, 都表示任何对象类型 187 2> instancetype只能用作返回值类型, 不能向id一样声明变量、用作参数等 188 3> 使用instancetype, 编译器会检测instancetype的真实类型, 如果类型不匹配, 编译时就警告了。(instancetype出现在哪个类型中就表示对应的类型) 189 190 2> xib与storyboard既然都是描述软件界面的, 有什么区别吗? 191 * xib 一般只用来描述一个界面中的某部分内容(用来描述局部UI界面)。 192 193 * storyboard 一般用来描述软件的多个界面, 以及不同界面之间的跳转关系。 194 195 196 7.使用xib精简代码。 197 1> 什么是xib? xib能做什么? 198 * 用来描述软件的局部界面的文件。 199 * 如果没有xib, 所有的界面都需要通过代码来手动创建。 200 * 进行创建xib,command +n ->user interface -> view; 201 * 有了xib以后, 可以在xib中进行可视化开发,进行拖拽三个小的控件 202 * 然后加载xib文件的. 203 UIView *appView = [[NSBundle mainBundle]loadNibNamed:@"appView" owner:nil options:nil][0]; 204 205 8. 自定义View。创建属性、传递Model进去。 206 *自定义一个appview用来描述xib,然后我们需要把xib的真实类型改变为appview(必须有这一步) 207 *用拖线的方式拿到里面的三个小的控件 208 *给这些控件进行赋数据,数据在模型中,我们拥有一个模型属性,为了从控制器中拿到模型,我们重写模型的set方法, 209 这样我们就可以通过set方法把控制器中的模型数据赋值给我们内部的模型,拿到模型后我们进行控件的赋值, 210 //重写模型的set方法 211 - (void)setAppViewModel:(AppViewModel *)appViewModel{ 212 _appViewModel = appViewModel; 213 214 215 self.head.image = [UIImage imageNamed:appViewModel.icon]; 216 self.nameLabel.text = appViewModel.name; 217 218 219 } 220 *这样我们就可以在控制器中直接赋值模型,然后显示数据 221 // 2.4赋值 222 AppViewModel *appViewModel = self.apps[i]; 223 224 225 appOldView.appViewModel = appViewModel; 226 227 228 229 230 231 9. 封装创建View的代码, 让用户不知道是通过xib创建的还是通过代码创建的,安全,扩展性比较好。 232 * 封装一个类方法 233 + (instancetype )loadNib{ 234 return [[NSBundle mainBundle]loadNibNamed:@"appView" owner:nil options:nil][0]; 235 236 237 238 } 239 240 10.下载按钮的操作 241 // 1.让按钮不能点击 242 243 btn.enabled = NO; 244 // 2.添加一个label 245 // 2.1创建一个label 246 UILabel *msgLabel = [[UILabel alloc]init]; 247 // 2.2设中label的frame 248 CGFloat msgLabelW = 150; 249 CGFloat msgLabelH = 30; 250 CGFloat msgLabelX = (self.superview.frame.size.width - msgLabelW)*0.5; 251 CGFloat msgLabelY = (self.superview.frame.size.height - msgLabelH)*0.5; 252 msgLabel.frame = CGRectMake(msgLabelX, msgLabelY, msgLabelW, msgLabelH); 253 // 2.3设置背景色 254 msgLabel.backgroundColor = [UIColor blackColor]; 255 // 2.4添加 256 [self.superview addSubview:msgLabel]; 257 // 2.5设置文字 258 msgLabel.text = @"正在下载"; 259 // 2.6设置文字颜色 260 msgLabel.textColor = [UIColor redColor]; 261 // 2.7让文字居中 262 msgLabel.textAlignment = NSTextAlignmentCenter; 263 // 2.8设置label为圆角 264 // 2.8.1设置圆角的半径 265 msgLabel.layer.cornerRadius = 8; 266 // 2.8.2多余的部分切掉 267 msgLabel.layer.masksToBounds = YES; 268 // 2.9设置透明度,0~1,0代表完全透明,1代表完全不透明 269 msgLabel.alpha = 0.0; 270 // 2.10设置动画,(block式动画) 271 // animateWithDuration:动画执行的时间 272 // animations:执行动画的代码 273 // completion:动画完成后执行的代码 274 [UIView animateWithDuration:3 animations:^{ 275 276 msgLabel.alpha = 0.5; 277 } completion:^(BOOL finished) { 278 if (finished) { 279 // delay:延迟:表示动画动画延迟多长时间以后执行 280 [UIView animateWithDuration:3 delay:0.5 options:UIViewAnimationOptionCurveLinear animations:^{ 281 msgLabel.alpha = 0.0; 282 } completion:^(BOOL finished) { 283 // 让msgLabel这个控件从父类中移除 284 [msgLabel removeFromSuperview]; 285 }]; 286 } 287 }]; 288 *注意动画的使用:不是头尾式,用的是block,block可以在动画执行后做一些事情. 289 290 11.xib文件的加载过程。 291 0> 根据路径, 搜索对应的xib文件(nib文件) 292 1> 加载xib文件的时候, 会按顺序加载xib文件中的每个控件。 293 2> 对于每个控件, 创建的时候都会查找对应的Class属性中配置的是那个类, 那么就创建对应的类的对象。 294 3> 获取到某个视图以后, 按照在xib中配置的属性的值, 放在数组中, 295 4> 最后返回一个数组, 这个数组中包含创建的所有元素对象。 296 297 12.使用xib封装一个自定义view的步骤 298 1> 新建一个AppView.xib文件来描述AppView内部的结构 299 2> 新建一个继承UIView的自定义view,假设类名叫做(appView) 300 3> 修改UIView的类型为appView真实类型 301 4> 将内部的子控件跟appView进行属性连线 302 5> appView提供一个模型属性 303 6> 重写模型属性的set方法,因为在set方法中可以拿到外界控制器传递的模型数据 304 7> 把模型数据拆开,分别设置数据到对应的子控件中 305 8> 补充:提供一个创建AppView的类方法,将读取xib文件的代码屏蔽起来 306 307 308 309 310 311 312 313 ------------ 补充两个知识点------------ 314 1. 根据subViews[index]获取子元素 315 2. 通过[appView viewWithTag:10]根据tag的值来获取对应的子元素。 316 以上两种方式返回值都是UIView将来还需要强制类型转换。 317 318 319 320 3. 设置Label的圆角效果 321 // 4. 设置Label为圆角 322 // 设置四周圆角的半径 323 lblMsg.layer.cornerRadius = 5; 324 // 把多余的部分裁剪掉。 325 lblMsg.layer.masksToBounds = YES;