iOS bug修复OC基础iOS进阶

iOS回顾笔记( 02 ) -- 由九宫格布局引出的一系列编程思

2017-03-02  本文已影响609人  xiaoyouPrince

前言(扯几句淡先)

回顾到学习UI过程中的九宫格布局时,发现当时学的东西真是不少。

这个阶段最大的特点就是:知识点繁多且琐碎

我们的目标就是要将这琐碎的知识点灵活运用、融汇贯通,通过不同的实现方式来实现相同的功能,最后进行比较得到最好的那种方式。这个求知的过程就是我们最需要学习的,在过程中我们学会了自我思考,并且在自己的思考和比较中,我们的脑海里逐渐形成了自己的编程思想。

本文主要以九宫格购物车的实现为引子,从最基础的实现方法层层递进直到最完美的实现方式。代码从起初的低效、耦合度高到后期的层层分离、MVC各模块封装,高内聚、低耦合,更具扩展性等方面逐步深化和扩展;经过编程思想的层层深入和代码的步步完善最后完整的展现在用户面前。

本文大体目录

九宫格购物车Demo

九宫格是我们在开发过程中对于一些有规律的UI布局常用的一种布局算法。
九宫格特点:简单,封装性好,可复用性高,很适合一些页面同类型item数量动态变化UI页面布局

下面就实现一个买书购物车

Snip20170301_3.png

基本要求和思路如下

思路:
1. 最初没有书(删除按钮不可用)
2. 点击添加就添加一本书(删除按钮可用)
3. 添加基本之后(提示购物车已满,添加按钮不可用)
4. 删除按钮点击(购物车不满的时候添加按钮又可以使用)
5. 添加书籍按九宫格的样式在页面中显示



先说说最直接的方法

// 添加书
- (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];
    

这样在创建和管理书的时候就方便多了,并且有一个数组来记录添加书的数量,在添加和删除的时候有计算的依据:可计算对应位置和两个按钮的可用情况

九宫格布局的思路和实现

书的对象已经封装好了,我们可以以整体思维来操作它,下面就是计算位置的思路。

Snip20170301_4.png

有了上面的铺垫,就可以写动态代码了,只需要用户 设置一个列数,知道最终有多少本书,遍历每本书,根据书的索引来计算对应的书的位置即可。

废话不多说了,上代码

// 添加书
- (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];
    
}

效果图如下

Snip20170301_5.png

书数据加载方式和对比

到这里购物车里的书已经可以随意添加了,并且可以根据用户的点击,无限制的添加。
如果项目需求修改了,比如变成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文件的创建

plist的创建一般直接用Xcode创建,然后在里面添加对应的key和value,(几乎没有人会手写plist文件)

3月-02-2017 15-05-43.gif

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文件和系统重名之后项目是运行不了的。

懒加载(lazyLoad) -- 提升性能

懒加载又叫延迟加载,由于项目运行时候很多数据用不到,可以暂时不创建,等到用的时候在创建,这样节省系统性能。

如以上项目中,就是在添加书的方法中每次创建书籍数组

// 添加书
- (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"];

MVC 思想的介绍和引入

以上的小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,真是麻雀虽小五脏俱全。

关于这个项目的完整代码,欢迎私聊或评论找我要,如果文章有任何问题或有其他技术问题,欢迎随时和我交流。

最后放一张项目效果图

2017-03-02 17.47.23.gif
上一篇下一篇

猜你喜欢

热点阅读