前言(扯幾句淡先)
回顧到學(xué)習(xí)UI過程中的九宮格布局時,發(fā)現(xiàn)當(dāng)時學(xué)的東西真是不少荚虚。
這個階段最大的特點(diǎn)就是:知識點(diǎn)繁多且瑣碎洼滚。
我們的目標(biāo)就是要將這瑣碎的知識點(diǎn)靈活運(yùn)用、融匯貫通良蒸,通過不同的實(shí)現(xiàn)方式來實(shí)現(xiàn)相同的功能,最后進(jìn)行比較得到最好的那種方式伍玖。這個求知的過程就是我們最需要學(xué)習(xí)的嫩痰,在過程中我們學(xué)會了自我思考,并且在自己的思考和比較中窍箍,我們的腦海里逐漸形成了自己的編程思想串纺。
本文主要以九宮格購物車的實(shí)現(xiàn)為引子,從最基礎(chǔ)的實(shí)現(xiàn)方法層層遞進(jìn)直到最完美的實(shí)現(xiàn)方式椰棘。代碼從起初的低效造垛、耦合度高到后期的層層分離、MVC各模塊封裝晰搀,高內(nèi)聚五辽、低耦合,更具擴(kuò)展性等方面逐步深化和擴(kuò)展外恕;經(jīng)過編程思想的層層深入和代碼的步步完善最后完整的展現(xiàn)在用戶面前杆逗。
本文大體目錄
- 九宮格購物車demo
- 對應(yīng)物品封裝(View的簡單封裝)
- 數(shù)據(jù)的加載方式對比 與 演變
- 屬性列表文件(plist)的創(chuàng)建和使用
- MVC 思想的引入和介紹
九宮格購物車Demo
九宮格是我們在開發(fā)過程中對于一些有規(guī)律的UI布局常用的一種布局算法。
九宮格特點(diǎn):簡單鳞疲,封裝性好罪郊,可復(fù)用性高,很適合一些頁面同類型item數(shù)量動態(tài)變化UI頁面布局
下面就實(shí)現(xiàn)一個買書購物車
基本要求和思路如下
思路:
1. 最初沒有書(刪除按鈕不可用)
2. 點(diǎn)擊添加就添加一本書(刪除按鈕可用)
3. 添加基本之后(提示購物車已滿尚洽,添加按鈕不可用)
4. 刪除按鈕點(diǎn)擊(購物車不滿的時候添加按鈕又可以使用)
5. 添加書籍按九宮格的樣式在頁面中顯示
先說說最直接的方法
// 添加書
- (IBAction)addBook:(id)sender {
// 1. 創(chuàng)建書圖標(biāo)
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.添加到數(shù)組(分開寫好像很難明確如何添加這本書)
}
這是最直接的方法來添加的一本書悔橄,這樣確實(shí)能添加一本書,但是這種直觀腺毫、死板的思想是不對的癣疟。
- 面對這樣的動態(tài)變化的UI頁面,每次添加和刪除書籍的操作都是用戶隨機(jī)的潮酒,所以代碼每次也是根據(jù)對應(yīng)的點(diǎn)擊來計(jì)算對應(yīng)書籍要添加的位置睛挚。
- 每本書的位置主要是和它對應(yīng)的index來確定,這就涉及到“書”這個對象要每次計(jì)數(shù)急黎,書是一個整體扎狱,所以書內(nèi)部的東西應(yīng)當(dāng)封裝起來侧到。
- “書”在UI上表現(xiàn)是 圖標(biāo) + 書名,也就是iamgeview + label淤击,從用戶看到的整體性上來說匠抗,每次添加和刪除同一本書也需要對 圖標(biāo) 和 書名 分別計(jì)算兩次來計(jì)算和排列。這也是非常不合理的污抬,并且很容易計(jì)算出現(xiàn)問題汞贸。應(yīng)該根據(jù)UIView 父子控件相關(guān)特性對“書”進(jìn)行封裝,添加/刪除的時候統(tǒng)一處理父控件壕吹,至于內(nèi)部屬性都會根據(jù)父控件來自動布局,方便管理删铃。
書的封裝
了解以上說的直接把代碼分開寫的局限性之后耳贬,現(xiàn)在來封裝一下“書”這個對象。
- UI層的封裝猎唁,我們先分析UI布局:內(nèi)部屬性只有 圖標(biāo)(UIImagevView) 和 書名(UILabel)
- 父控件選取原則:父控件只是承載子控件的容器咒劲,應(yīng)當(dāng)簡潔為主,所以選擇 UIView
// 0. 創(chuàng)建書
UIView *book = [UIView new];
book.frame = CGRectMake(0, 0, 60, 70);
book.backgroundColor = [UIColor redColor];
[self.shopView addSubview:book];
// 1. 創(chuàng)建書圖標(biāo)
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.添加到數(shù)組(直接添加書這個對象)
[self.books addObject:book];
這樣在創(chuàng)建和管理書的時候就方便多了诫隅,并且有一個數(shù)組來記錄添加書的數(shù)量腐魂,在添加和刪除的時候有計(jì)算的依據(jù):可計(jì)算對應(yīng)位置和兩個按鈕的可用情況
九宮格布局的思路和實(shí)現(xiàn)
書的對象已經(jīng)封裝好了,我們可以以整體思維來操作它逐纬,下面就是計(jì)算位置的思路蛔屹。
- 書的數(shù)量不定,但列數(shù)是固定的豁生,可以設(shè)置成變量 int clos = 3兔毒;
- 每本書之間可能有一定間距,橫向間距 margin = ( width - clos * book.width)/ ( clos / 2) ;
- 每本書的位置(x,y)可根據(jù)下圖發(fā)現(xiàn)規(guī)律 x = 列號 * (W + margin)甸箱; y = 行號 * (H+margin)
- 行號規(guī)律 : 行號 = index / clos ;
- 列號規(guī)律 : 列號 = index % clos ;
有了上面的鋪墊育叁,就可以寫動態(tài)代碼了,只需要用戶 設(shè)置一個列數(shù)芍殖,知道最終有多少本書豪嗽,遍歷每本書,根據(jù)書的索引來計(jì)算對應(yīng)的書的位置即可豌骏。
廢話不多說了龟梦,上代碼
// 添加書
- (IBAction)addBook:(id)sender {
// 設(shè)置列數(shù)為 3
int clos = 3;
// 設(shè)置書的寬高分別為 W H
CGFloat W = 60;
CGFloat H = 70;
CGFloat iconH = 50;
// 0. 創(chuàng)建書
UIView *book = [UIView new];
book.backgroundColor = [UIColor redColor];
[self.shopView addSubview:book];
// 計(jì)算書的位置
// 獲得索引
NSUInteger index = [self.books count];
// 計(jì)算橫間距 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. 創(chuàng)建書圖標(biāo)
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.添加到數(shù)組(分開寫好像很難明確如何添加這本書)
[self.books addObject:book];
}
效果圖如下
書數(shù)據(jù)加載方式和對比
到這里購物車?yán)锏臅呀?jīng)可以隨意添加了,并且可以根據(jù)用戶的點(diǎn)擊窃躲,無限制的添加变秦。
如果項(xiàng)目需求修改了,比如變成5列了框舔,寬高什么的也是根據(jù)項(xiàng)目自行修改就行蹦玫。
九宮格布局的代碼已經(jīng)完成了赎婚。
現(xiàn)在需要注意的問題是,“書”的屬性數(shù)據(jù)的添加問題樱溉,現(xiàn)在簡單的來說挣输,書屬性有 :書名 + icon。
“書”的各屬性值福贞,應(yīng)該如何賦值呢撩嚼?
幾種簡單數(shù)據(jù)加載方法與對比
- 直接根據(jù) index 來進(jìn)行if判斷,逐個添加數(shù)據(jù)
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";
}
...
- 創(chuàng)建一個書的數(shù)組挖帘,數(shù)組存放對應(yīng)的字典完丽,作為書的屬性方法的數(shù)據(jù)源,從中取值
// 0.創(chuàng)建書的數(shù)據(jù)源
self.books = @[
@{
@"icon":@"0",
@"name":@"book1"
},
@{
@"icon":@"1",
@"name":@"book2"
},
@{
@"icon":@"2",
@"name":@"book3"
},
@{
@"icon":@"3",
@"name":@"book4"
},
];
// 1.書圖標(biāo)
bookIcon.image = [UIImage imageNamed:self.books[index][@"icon"]];
// 2.書名
bookName.text = self.books[index][@"name"];
- 進(jìn)一步分離數(shù)據(jù)拇舀,將數(shù)據(jù)信息放到其他文件中逻族,用的時候從文件中讀取
// 獲取文件路徑
NSString *books = [[NSBundle mainBundle] pathForResource:@"bookData" ofType:@"plist"];
// 加載路徑中內(nèi)容放到數(shù)組中
_books = [NSMutableArray arrayWithContentsOfFile:books];
// 1.書圖標(biāo)
bookIcon.image = [UIImage imageNamed:self.books[index][@"icon"]];
// 2.書名
bookName.text = self.books[index][@"name"];
對于三種方法簡單的比較和點(diǎn)評
- 第一種直接if判斷index的位置
- 直接把動態(tài)判斷代碼寫到代碼中,過于死板
- 代碼耦合性太高骄崩,及其不利于后期擴(kuò)展聘鳞,如果加數(shù)據(jù)簡直是惡魔
- 一些死布局并且元素個數(shù)極少(幾個)的時候可以使用
- 代碼重復(fù)性高,技術(shù)含量低
- 第二種把數(shù)據(jù)代碼獨(dú)立出來放到一個數(shù)組中
- 避免了第一種直接寫到代碼中的低效且惡心的做法
- 代碼還是和數(shù)據(jù)耦合到一起了要拂,如果后期修改需要修改代碼
- 如果數(shù)據(jù)量大抠璃,及其不利于后期管理和數(shù)據(jù)的修改
- 相比第一種有明顯的分離,是一種進(jìn)步
- 第三種代碼和數(shù)據(jù)分離脱惰,放到外部文件中
- 徹底代碼和數(shù)據(jù)分離搏嗡,耦合性低,易于擴(kuò)展
- 數(shù)據(jù)放到文件中拉一、后期添加或者修改數(shù)據(jù)無需改動代碼彻况、更加獨(dú)立
- 代碼更簡潔直觀,只需關(guān)注功能和布局
- 適合企業(yè)開發(fā)舅踪、這是一種通用方式(網(wǎng)絡(luò)應(yīng)用更是如此)
plist文件介紹和使用
簡介
- plist文件全稱:Property List文件纽甘,中文又叫屬性列表文件。
- 是蘋果平臺上常用的一種資源描述類文件抽碌。
- 它存儲的屬性一般都是Xcode里面的基本數(shù)據(jù)類型或者OC里面的對應(yīng)類型(NSDictionary悍赢,NSArray,NSString等)
- Xcode會自動進(jìn)行解析成可以打開和合上的層疊格式
- 用文本文件打開直接就是XML格式的鍵值對
plist文件的創(chuàng)建
plist的創(chuàng)建一般直接用Xcode創(chuàng)建货徙,然后在里面添加對應(yīng)的key和value左权,(幾乎沒有人會手寫plist文件)
plist文件的使用
plist使用就和其他的文件用法一樣,先讀取目標(biāo)文件的路徑在根據(jù)路徑加載到數(shù)組中痴颊。
// 獲取文件路徑
NSString *books = [[NSBundle mainBundle] pathForResource:@"bookData" ofType:@"plist"];
// 加載路徑中內(nèi)容放到數(shù)組中
_books = [NSMutableArray arrayWithContentsOfFile:books];
plist使用注意
我們自己創(chuàng)建plist文件的時候不能使用info/Info.plist命名
由于每次創(chuàng)建工程的時候赏迟,系統(tǒng)會自動生成一個對該項(xiàng)目信息進(jìn)行描述的info.plist,它會記錄項(xiàng)目的一些包名蠢棱、項(xiàng)目名锌杀、開發(fā)工具甩栈、版本和其他項(xiàng)目的基礎(chǔ)信息。
所以我們自己創(chuàng)建plist文件的時候不能使用info/Info.plist
這是為了不和系統(tǒng)的info.plist混淆糕再,實(shí)際上自己創(chuàng)建一個info.plist文件和系統(tǒng)重名之后項(xiàng)目是運(yùn)行不了的量没。
懶加載(lazyLoad) -- 提升性能
懶加載又叫延遲加載,由于項(xiàng)目運(yùn)行時候很多數(shù)據(jù)用不到突想,可以暫時不創(chuàng)建殴蹄,等到用的時候在創(chuàng)建,這樣節(jié)省系統(tǒng)性能猾担。
如以上項(xiàng)目中袭灯,就是在添加書的方法中每次創(chuàng)建書籍?dāng)?shù)組
// 添加書
- (IBAction)addBook:(id)sender {
self.books = @[
@{
@"icon":@"0",
@"name":@"book1"
},
@{
@"icon":@"1",
@"name":@"book2"
},
@{
@"icon":@"2",
@"name":@"book3"
},
@{
@"icon":@"3",
@"name":@"book4"
},
];
// 設(shè)置列數(shù)為 3
int clos = 3;
// 設(shè)置書的寬高分別為 W H
CGFloat W = 60;
CGFloat H = 70;
CGFloat iconH = 50;
// 0. 創(chuàng)建書
UIView *book = [UIView new];
book.backgroundColor = [UIColor redColor];
這樣其實(shí)非常耗性能,每次都要創(chuàng)建一個數(shù)組绑嘹,然后用過一次就沒有用了稽荧,對此可以進(jìn)行一個優(yōu)化,對于該書籍?dāng)?shù)據(jù)其實(shí)就是一個固定數(shù)據(jù)圾叼,每次用一下蛤克,用的時候再創(chuàng)建捺癞,不用的時候就不管了夷蚊,這里正好用懶加載最好了。
懶加載實(shí)際上就是一個get方法
// 懶加載書籍?dāng)?shù)據(jù)
- (NSMutableArray *)books
{
if (_books == nil) {
// 獲取文件路徑
NSString *books = [[NSBundle mainBundle] pathForResource:@"bookData" ofType:@"plist"];
// 加載路徑中內(nèi)容放到數(shù)組中
_books = [NSMutableArray arrayWithContentsOfFile:books];
}
return _books;
}
// 使用的時候髓介,直接用數(shù)組就行
bookIcon.image = [UIImage imageNamed:self.books[index][@"icon"]];
bookName.text = self.books[index][@"name"];
MVC 思想的介紹和引入
以上的小Demo惕鼓,已經(jīng)告一段落了,可以實(shí)現(xiàn)把文件中書籍?dāng)?shù)據(jù)唐础,按九宮格的布局添加到購物車中箱歧,也可以一一刪除。
思考
還有什么可以改進(jìn)的嗎一膨?
數(shù)據(jù)就這樣存放到數(shù)組中就完美了嗎呀邢?
每次用書的數(shù)據(jù)的時候,直接從數(shù)組總?cè)〕鰜泶_實(shí)方便豹绪,但是這樣真的足夠完美嗎价淌?
現(xiàn)在的所有代碼幾乎都在一個文件中,如果項(xiàng)目大了也這樣寫嗎瞒津?
上面把對應(yīng)的數(shù)據(jù)加載方式分離到文件中了蝉衣,其他的業(yè)務(wù)邏輯能不能也分離一下,能不能讓項(xiàng)目的結(jié)構(gòu)更加清晰巷蚪?
待著這些思考病毡,答案是肯定的,項(xiàng)目的代碼還是非常耦合的屁柏,并且有個致命的缺點(diǎn)啦膜。
- 每次從數(shù)組中取書的信息時候都自己寫key值有送,self.books[index][@"icon"] 如果這個“icon“手誤寫錯,編輯器一點(diǎn)提示也沒有
- 如果書有很多屬性功戚,每次手寫key值很頭痛娶眷,并且代碼看起來很糟
所以我們需要一種新的方式:因?yàn)闀且粋€對象,可以把它封裝成對象啸臀,它用有自己的各種屬性和方法届宠。這樣做有幾點(diǎn)好處:
- 便于管理,在使用的時候直接調(diào)用屬性的get方法就行乘粒。
- 系統(tǒng)會自動提示get方法豌注,安全性高不會出錯,如果寫錯系統(tǒng)會報錯灯萍。
- 代碼封裝性好轧铁,書就是書,而不是每次到數(shù)組中去取字典根據(jù)對應(yīng)的key來取值
- 擴(kuò)展性好旦棉,如果日后需要添加新的屬性和方法齿风,直接在”書“類中加就行
MVC介紹
經(jīng)過上面的分析,可以確定的是書應(yīng)該獨(dú)立封裝起來保存數(shù)據(jù)绑洛,頁面邏輯也應(yīng)該單獨(dú)管理救斑,至于ViewController恰好就是兩者的橋梁。這樣的設(shè)計(jì)模式就是MVC
- M : (Model)數(shù)據(jù)模型真屯,用來存儲和保存數(shù)據(jù)
- V : (View)UI視圖脸候,用來展示給用戶看的頁面,一些復(fù)雜的頁面要封裝起來放到里面單獨(dú)管理绑蔫。
- C: (Controller)控制器运沦,是兩者的橋梁,主要用來處理業(yè)務(wù)邏輯配深。
使用MVC模式可以很好的簡化項(xiàng)目代碼携添,對不同的模塊進(jìn)行封裝,降低耦合性篓叶,擴(kuò)展性也會得到提高烈掠,MVC是企業(yè)開發(fā)常用的設(shè)計(jì)模式。
MVC的分層封裝和使用
經(jīng)過分析可知澜共,在此小項(xiàng)目中向叉,書和展示的書的UI和ViewController對應(yīng)MVC的關(guān)系:
- 書 -- Model:用來封裝書的各種屬性信息和方法
- 書UI -- View:封裝書這個小表象,用于展示給用戶看
- ViewController -- Controller:用來處理整體的業(yè)務(wù)邏輯嗦董,優(yōu)化代碼
廢話不多說了母谎,上各層的代碼
- 下面是書的代碼封裝:存儲書的數(shù)據(jù)和方法
頭文件 XYBook.h
@interface XYBook : NSObject
// 書圖標(biāo)
@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
- 書的UI的封裝:集中布局奇唤,減少控制器代碼幸斥,優(yōu)化控制器邏輯
XYBookView.h 頭文件
#import <UIKit/UIKit.h>
@class XYBook;
@interface XYBookView : UIView
// 只放一個數(shù)據(jù)屬性用來賦值,內(nèi)部布局咬扇,放到.m 中自己管甲葬,不暴露給外界
@property (nonatomic, strong) XYBook *book;
@end
實(shí)現(xiàn)文件 .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. 創(chuàng)建書圖標(biāo)
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));
}
// 設(shè)置書的屬性
- (void)setBook:(XYBook *)book
{
_book = book;
self.icon.image = [UIImage imageNamed:book.icon];
self.label.text = book.name;
}
@end
- 控制器:不管細(xì)節(jié),專注處理邏輯
// 懶加載數(shù)據(jù)源
- (NSMutableArray *)books
{
if (_books == nil) {
_books = [NSMutableArray array];
// 獲取文件路徑
NSString *books = [[NSBundle mainBundle] pathForResource:@"bookdata" ofType:@"plist"];
// 加載路徑中內(nèi)容放到數(shù)組中
NSMutableArray *arrayM = [NSMutableArray arrayWithContentsOfFile:books];
for (NSDictionary *dict in arrayM) {
XYBook *book = [XYBook bookWithDict:dict];
[_books addObject:book];
}
}
return _books;
}
// 添加書
- (IBAction)addBook:(id)sender {
// 設(shè)置列數(shù)為 3
int clos = 3;
// 設(shè)置書的寬高分別為 W H
CGFloat W = 60;
CGFloat H = 70;
// 0. 創(chuàng)建書
XYBookView *bookView = [XYBookView new];
bookView.backgroundColor = [UIColor redColor];
// 計(jì)算書的位置
// 獲得索引
NSUInteger index = [self.shopView.subviews count];
// 計(jì)算橫間距 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設(shè)置數(shù)據(jù)
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封裝版
不同部分各司其職懈贺,負(fù)責(zé)自己的模塊
項(xiàng)目的健壯性和封裝性也也到了對應(yīng)的提高
小記
一個簡單的九宮格購物車的小Demo经窖,真是麻雀雖小五臟俱全。
關(guān)于這個項(xiàng)目的完整代碼梭灿,歡迎私聊或評論找我要画侣,如果文章有任何問題或有其他技術(shù)問題,歡迎隨時和我交流堡妒。
最后放一張項(xiàng)目效果圖