回顾到学习 UI 过程中的九宫格布局时,发现当时学的东西真是不少。
这个阶段最大的特点就是:知识点繁多且琐碎。
我们的目标就是要将这琐碎的知识点灵活运用、融汇贯通,通过不同的实现方式来实现相同的功能,最后进行比较得到最好的那种方式。这个求知的过程就是我们最需要学习的,在过程中我们学会了自我思考,并且在自己的思考和比较中,我们的脑海里逐渐形成了自己的编程思想。
本文主要以九宫格购物车的实现为引子,从最基础的实现方法层层递进直到最完美的实现方式。代码从起初的低效、耦合度高到后期的层层分离、MVC 各模块封装,高内聚、低耦合,更具扩展性等方面逐步深化和扩展;经过编程思想的层层深入和代码的步步完善最后完整的展现在用户面前。
本文大体目录
九宫格是我们在开发过程中对于一些有规律的 UI 布局常用的一种布局算法。
九宫格特点:简单,封装性好,可复用性高,很适合一些页面同类型 item 数量动态变化 UI 页面布局
下面就实现一个买书购物车
基本要求和思路如下
先说说最直接的方法
- // 添加书
- - (IBAction)addBook:(id)sender {
- // 1. 创建书图标
- UIImageView *bookIcon = [UIImageView new];
- bookIcon.image = [UIImage imageNamed:@"0"];
- bookIcon.frame = CGRectMake(0, 0, 50, 50);
- [self.shopView addSubview:bookIcon];
- // 2.书名
- UILabel *bookName = [UILabel new];
- bookName.frame = CGRectMake(0, 50, 50, 20);
- bookName.text = @"book1";
- bookName.textAlignment = NSTextAlignmentCenter;
- [self.shopView addSubview:bookName];
- // 3.添加到数组(分开写好像很难明确如何添加这本书)
- }
这是最直接的方法来添加的一本书,这样确实能添加一本书,但是这种直观、死板的思想是不对的。
了解以上说的直接把代码分开写的局限性之后,现在来封装一下 "书" 这个对象。
- // 0. 创建书
- UIView *book = [UIView new];
- book.frame = CGRectMake(0, 0, 60, 70);
- book.backgroundColor = [UIColor redColor];
- [self.shopView addSubview:book];
- // 1. 创建书图标
- UIImageView *bookIcon = [UIImageView new];
- bookIcon.image = [UIImage imageNamed:@"0"];
- bookIcon.frame = CGRectMake(0, 0, 60, 50);
- [book addSubview:bookIcon];
- // 2.书名
- UILabel *bookName = [UILabel new];
- bookName.frame = CGRectMake(0, 50, 60, 20);
- bookName.text = @"book1";
- bookName.textAlignment = NSTextAlignmentCenter;
- [book addSubview:bookName];
- // 3.添加到数组(直接添加书这个对象)
- [self.books addObject:book];
这样在创建和管理书的时候就方便多了,并且有一个数组来记录添加书的数量,在添加和删除的时候有计算的依据:可计算对应位置和两个按钮的可用情况
书的对象已经封装好了,我们可以以整体思维来操作它,下面就是计算位置的思路。
有了上面的铺垫,就可以写动态代码了,只需要用户 设置一个列数,知道最终有多少本书,遍历每本书,根据书的索引来计算对应的书的位置即可。
废话不多说了,上代码
- // 添加书
- - (IBAction)addBook:(id)sender {
- // 设置列数为 3
- int clos = 3;
- // 设置书的宽高分别为 W H
- CGFloat W = 60;
- CGFloat H = 70;
- CGFloat iconH = 50;
- // 0. 创建书
- UIView *book = [UIView new];
- book.backgroundColor = [UIColor redColor];
- [self.shopView addSubview:book];
- // 计算书的位置
- // 获得索引
- NSUInteger index = [self.books count];
- // 计算横间距 margin
- CGFloat margin = (self.shopView.frame.size.width - clos * W) / (clos - 1);
- // 书 frame 的 X
- CGFloat x = (index % clos) * (W + margin);
- // 书 frame 的 Y
- CGFloat y = (index / clos) * (H + margin);
- book.frame = CGRectMake(x, y, W, H);
- // 1. 创建书图标
- UIImageView *bookIcon = [UIImageView new];
- bookIcon.image = [UIImage imageNamed:@"0"];
- bookIcon.frame = CGRectMake(0, 0, W, iconH);
- [book addSubview:bookIcon];
- // 2.书名
- UILabel *bookName = [UILabel new];
- bookName.frame = CGRectMake(0, iconH, W, H-iconH);
- bookName.text = @"book1";
- bookName.textAlignment = NSTextAlignmentCenter;
- [book addSubview:bookName];
- // 3.添加到数组(分开写好像很难明确如何添加这本书)
- [self.books addObject:book];
- }
效果图如下
到这里购物车里的书已经可以随意添加了,并且可以根据用户的点击,无限制的添加。
如果项目需求修改了,比如变成 5 列了,宽高什么的也是根据项目自行修改就行。
九宫格布局的代码已经完成了。
现在需要注意的问题是,"书" 的属性数据的添加问题,现在简单的来说,书属性有 :书名 + icon。
"书" 的各属性值,应该如何赋值呢?
几种简单数据加载方法与对比
- if (index == 0) {
- bookIcon.image = [UIImage imageNamed:@"0"];
- bookName.text = @"Book1";
- }else if(index == 1)
- {
- bookIcon.image = [UIImage imageNamed:@"1"];
- bookName.text = @"Book2";
- }
- else if(index == 2)
- {
- bookIcon.image = [UIImage imageNamed:@"2"];
- bookName.text = @"Book3";
- }
- ...
- // 0.创建书的数据源
- self.books = @[
- @{
- @"icon":@"0",
- @"name":@"book1"
- },
- @{
- @"icon":@"1",
- @"name":@"book2"
- },
- @{
- @"icon":@"2",
- @"name":@"book3"
- },
- @{
- @"icon":@"3",
- @"name":@"book4"
- },
- ];
- // 1.书图标
- bookIcon.image = [UIImage imageNamed:self.books[index][@"icon"]];
- // 2.书名
- bookName.text = self.books[index][@"name"];
- // 获取文件路径
- NSString *books = [[NSBundle mainBundle] pathForResource:@"bookData" ofType:@"plist"];
- // 加载路径中内容放到数组中
- _books = [NSMutableArray arrayWithContentsOfFile:books];
- // 1.书图标
- bookIcon.image = [UIImage imageNamed:self.books[index][@"icon"]];
- // 2.书名
- bookName.text = self.books[index][@"name"];
对于三种方法简单的比较和点评
简介
plist 文件的创建
plist 的创建一般直接用 Xcode 创建,然后在里面添加对应的 key 和 value,(几乎没有人会手写 plist 文件)
plist 文件的使用
plist 使用就和其他的文件用法一样,先读取目标文件的路径在根据路径加载到数组中。
- // 获取文件路径
- NSString *books = [[NSBundle mainBundle] pathForResource:@"bookData" ofType:@"plist"];
- // 加载路径中内容放到数组中
- _books = [NSMutableArray arrayWithContentsOfFile:books];
plist 使用注意
我们自己创建 plist 文件的时候不能使用 info/Info.plist 命名
由于每次创建工程的时候,系统会自动生成一个对该项目信息进行描述的 info.plist,它会记录项目的一些包名、项目名、开发工具、版本和其他项目的基础信息。
所以我们自己创建 plist 文件的时候不能使用 info/Info.plist
这是为了不和系统的 info.plist 混淆,实际上自己创建一个 info.plist 文件和系统重名之后项目是运行不了的。
懒加载又叫延迟加载,由于项目运行时候很多数据用不到,可以暂时不创建,等到用的时候在创建,这样节省系统性能。
如以上项目中,就是在添加书的方法中每次创建书籍数组
- // 添加书
- - (IBAction)addBook:(id)sender {
- self.books = @[
- @{
- @"icon":@"0",
- @"name":@"book1"
- },
- @{
- @"icon":@"1",
- @"name":@"book2"
- },
- @{
- @"icon":@"2",
- @"name":@"book3"
- },
- @{
- @"icon":@"3",
- @"name":@"book4"
- },
- ];
- // 设置列数为 3
- int clos = 3;
- // 设置书的宽高分别为 W H
- CGFloat W = 60;
- CGFloat H = 70;
- CGFloat iconH = 50;
- // 0. 创建书
- UIView *book = [UIView new];
- book.backgroundColor = [UIColor redColor];
这样其实非常耗性能,每次都要创建一个数组,然后用过一次就没有用了,对此可以进行一个优化,对于该书籍数据其实就是一个固定数据,每次用一下,用的时候再创建,不用的时候就不管了,这里正好用懒加载最好了。
懒加载实际上就是一个 get 方法
- // 懒加载书籍数据
- - (NSMutableArray *)books
- {
- if (_books == nil) {
- // 获取文件路径
- NSString *books = [[NSBundle mainBundle] pathForResource:@"bookData" ofType:@"plist"];
- // 加载路径中内容放到数组中
- _books = [NSMutableArray arrayWithContentsOfFile:books];
- }
- return _books;
- }
- // 使用的时候,直接用数组就行
- bookIcon.image = [UIImage imageNamed:self.books[index][@"icon"]];
- bookName.text = self.books[index][@"name"];
以上的小 Demo,已经告一段落了,可以实现把文件中书籍数据,按九宫格的布局添加到购物车中,也可以一一删除。
思考
待着这些思考,答案是肯定的,项目的代码还是非常耦合的,并且有个致命的缺点。
所以我们需要一种新的方式:因为书是一个对象,可以把它封装成对象,它用有自己的各种属性和方法。这样做有几点好处:
MVC 介绍
经过上面的分析,可以确定的是书应该独立封装起来保存数据,页面逻辑也应该单独管理,至于 ViewController 恰好就是两者的桥梁。这样的设计模式就是 MVC
使用 MVC 模式可以很好的简化项目代码,对不同的模块进行封装,降低耦合性,扩展性也会得到提高,MVC 是企业开发常用的设计模式。
MVC 的分层封装和使用
经过分析可知,在此小项目中,书和展示的书的 UI 和 ViewController 对应 MVC 的关系:
废话不多说了,上各层的代码
- 头文件 XYBook.h
- @interface XYBook : NSObject
- // 书图标
- @property (nonatomic, copy) NSString *icon;
- // 书名字
- @property (nonatomic, copy) NSString *name;
- // 对象方法,返回自己对象
- - (instancetype)initWithDict:(NSDictionary *)dict;
- // 类方法,返回自己对象
- + (instancetype)bookWithDict:(NSDictionary *)dict;
- @end
- #import "XYBook.h"
- @implementation XYBook
- - (instancetype)initWithDict:(NSDictionary *)dict
- {
- if (self = [super init]) {
- [self setValuesForKeysWithDictionary:dict];
- }
- return self;
- }
- + (instancetype)bookWithDict:(NSDictionary *)dict
- {
- return [[self alloc] initWithDict:dict];
- }
- @end
- XYBookView.h 头文件
- #import <UIKit/UIKit.h>
- @class XYBook;
- @interface XYBookView : UIView
- // 只放一个数据属性用来赋值,内部布局,放到.m 中自己管,不暴露给外界
- @property (nonatomic, strong) XYBook *book;
- @end
- 实现文件 .m文件
- #import "XYBookView.h"
- #include "XYBook.h"
- @interface XYBookView ()
- @property (nonatomic, weak) UIImageView *icon;
- @property (nonatomic, weak) UILabel *label;
- @end
- @implementation XYBookView
- - (instancetype)initWithFrame:(CGRect)frame
- {
- if (self = [super initWithFrame:frame]) {
- // 1. 创建书图标
- UIImageView *icon = [UIImageView new];
- self.icon = icon;
- [self addSubview:self.icon];
- // 2.书名
- UILabel *bookName = [UILabel new];
- bookName.textAlignment = NSTextAlignmentCenter;
- self.label = bookName;
- [self addSubview:self.label];
- }
- return self;
- }
- // 重写布局
- - (void)layoutSubviews
- {
- [super layoutSubviews];
- CGSize size = self.frame.size;
- self.icon.frame = CGRectMake(0, 0, size.width , size.height * 0.7);
- self.label.frame = CGRectMake(0, size.height * 0.7, size.width, size.height *(1 - 0.7));
- }
- // 设置书的属性
- - (void)setBook:(XYBook *)book
- {
- _book = book;
- self.icon.image = [UIImage imageNamed:book.icon];
- self.label.text = book.name;
- }
- @end
- // 懒加载数据源
- - (NSMutableArray *)books
- {
- if (_books == nil) {
- _books = [NSMutableArray array];
- // 获取文件路径
- NSString *books = [[NSBundle mainBundle] pathForResource:@"bookdata" ofType:@"plist"];
- // 加载路径中内容放到数组中
- NSMutableArray *arrayM = [NSMutableArray arrayWithContentsOfFile:books];
- for (NSDictionary *dict in arrayM) {
- XYBook *book = [XYBook bookWithDict:dict];
- [_books addObject:book];
- }
- }
- return _books;
- }
- // 添加书
- - (IBAction)addBook:(id)sender {
- // 设置列数为 3
- int clos = 3;
- // 设置书的宽高分别为 W H
- CGFloat W = 60;
- CGFloat H = 70;
- // 0. 创建书
- XYBookView *bookView = [XYBookView new];
- bookView.backgroundColor = [UIColor redColor];
- // 计算书的位置
- // 获得索引
- NSUInteger index = [self.shopView.subviews count];
- // 计算横间距 margin
- CGFloat margin = (self.shopView.frame.size.width - clos * W) / (clos - 1);
- // 书 frame 的 X
- CGFloat x = (index % clos) * (W + margin);
- // 书 frame 的 Y
- CGFloat y = (index / clos) * (H + margin);
- bookView.frame = CGRectMake(x, y, W, H);
- [self.shopView addSubview:bookView];
- // 给书的UI设置数据
- bookView.book = self.books[index];
- [self checkState];
- self.removeBtn.enabled = YES;
- }
- // 移除书
- - (IBAction)removeBook:(id)sender {
- [[self.shopView.subviews lastObject] removeFromSuperview];
- [self checkState];
- self.addBtn.enabled = YES;
- }
以上就是对于本 Demo 的最终 MVC 封装版
不同部分各司其职,负责自己的模块
项目的健壮性和封装性也也到了对应的提高
小记
一个简单的九宫格购物车的小 Demo,真是麻雀虽小五脏俱全。
关于这个项目的完整代码,欢迎私聊或评论找我要,如果文章有任何问题或有其他技术问题,欢迎随时和我交流。
最后放一张项目效果图
来源: http://www.cnblogs.com/xiaoyouPrince/p/6494710.html