模型
模型取代字典的好處
- 使⽤用字典的壞處
- ⼀一般情況下,設置數據和取出數據都使⽤用“字符串類型的key”,編寫這些key時,編譯器不會有任何友善提示,需要手敲。例如:
dict[@"name"] = @"Jack";
NSString *name = dict[@"name"];
- 手敲字符串key,key容易寫錯 。Key如果寫錯了,編譯器不會有任何警告和報錯,造成設錯數據或者取錯數據。
- ⼀一般情況下,設置數據和取出數據都使⽤用“字符串類型的key”,編寫這些key時,編譯器不會有任何友善提示,需要手敲。例如:
- 使⽤用模型的好處
- 所謂模型,其實就是數據模型,專門⽤用來存放數據的對象,⽤用它來表⽰示數據會更加專業。
- 模型設置數據和取出數據都是通過它的屬性,屬性名如果寫錯了,編譯器會馬上報錯,因此,保證了數據的正確性。
- 使⽤模型訪問屬性時,編譯器會提供一系列的提⽰,提⾼編碼效率。例如:
app.name = @"Jack”;
NSString *name = app.name;
字典轉模型
- 字典轉模型的過程最好封裝在模型內部
- 模型應該提供⼀一個可以傳⼊入字典參數的構造⽅方法
-(instancetype)initWithDict:(NSDictionary*)dict;
+(instancetype)xxxWithDict:(NSDictionary*)dict;
- instancetype在類型表⽰上,跟id一樣,可以表示任何對象類型
instancetype只能用在返回值類型上,不能像id一樣用在參數類型上
instancetype比id多一個好處:編譯器會檢測instancetype的真實類型
構建一個模型類 App 來存儲數據
// App.h
// 模型類:用來存放數據的類
#import <Foundation/Foundation.h>
@interface App : NSObject
// 模型的名稱
@property (nonatomic, copy) NSString *name;
// 模型的圖標
@property (nonatomic, copy) NSString *icon;
// 通過字典來初始化模型對象
- (instancetype) initWithDict:(NSDictionary *)dict;
+ (instancetype) appWithDict:(NSDictionary *)dict;
@end
// App.m
#import "App.h"
@implementation App
- (instancetype)initWithDict:(NSDictionary *)dict
{
if(self = [super init]){
self.name = dict[@"name"];
self.icon = dict[@"icon"];
}
return self;
}
+ (instancetype)appWithDict:(NSDictionary *)dict
{
return [[self alloc] initWithDict:dict];
}
@end
字典轉模型的過程
Xib文件
Xib的創建及使用
- Xib文件可以用來描述某一塊局部的UI界⾯
- 如何創建Xib文件
- 首先創建一個繼承UIView的自定義view。假如這個類叫:APPView。
- 新建一個AppView.xib文件來描述AppView內部的結構。修改UIView的類型爲AppView真實類型,即把AppView.xib文件的class 改成 AppView如下圖所示:
Xib文件的加載
- 調用方法
NSArray *objs = [[NSBundle mainBundle] loadNibNamed:@"AppView" owner:nil
options:nil]; - 這個方法會創建xib中的所有對象,並且將對象按順序放到objs數組中。
如果xib如下圖所⽰,那麼objs數組中依次會有3個對象:1個UIView、1個UILabel、1個UIButton
- 在開發階段,面向開發者的是xib文件; 當把應用裝到⼿機上時,xib⽂件就會轉爲nib⽂件
使用xib封裝一個自定義view的步驟
- 調用方法
- 1> 新建一個繼承UIView的自定義view,假設類名叫做(AppView)
- 2> 新建一個AppView.xib文件來描述MJAppView內部的結構
- 3> 修改UIView的類型爲AppView真實類型
- 4> 將內部的子控件跟AppView進行屬性連線
- 5> AppView中創建一個模型屬性
- 6> 重寫模型屬性的set方法,因爲在set方法中可以拿到外界傳遞的模型數據
- 7> 把模型數據拆開,分別設置數據到對應的子控件中
8> 補充:提供一個創建MJAppView的類方法,將讀取xib文件的代碼屏蔽起來
Xib和storyboard對比
- 共同點:
- 都用來描述軟件界面
- 都用Interface Builder⼯具來編輯
- 不同點
- Xib是輕量級的,用來描述局部的UI界面
- Storyboard是重量級的,用來描述整個軟件的多個界面,並且能展⽰多個界面之間的跳轉關係
源碼
結構框架
代碼
// ViewController.h
//
#import <UIKit/UIKit.h>
@interface ViewController : UIViewController
@end
//
// ViewController.m
//
#import "ViewController.h"
#import "AppView.h"
#import "App.h"
@interface ViewController () <AppViewDelegate>
@property (nonatomic, strong) NSArray *apps;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 添加應用信息
// 1.總列數(一行最多3列)
int totalColumns = 3;
// 2.應用的尺寸
CGFloat appW = 90;
CGFloat appH = 90;
// 3.間隙 = (控制器view的寬度 - 3 * 應用寬度) / 4
CGFloat marginX = (self.view.frame.size.width - totalColumns * appW) / (totalColumns + 1);
CGFloat marginY = 40;
// 4.根據應用個數創建對應的框框(index 0 ~ 11)
for(int index = 0 ; index < self.apps.count; index++){
// 3.1 創建view,設置數據
AppView *appView = [AppView appViewWithApp:self.apps[index]];
// 3.2 把view的代理設置爲控制器
appView.delegate = self;
// 3.3 添加view
[self.view addSubview:appView];
// 3.4 設置frame
int row = index / totalColumns;
int col = index % totalColumns;
// 計算x和y
CGFloat appX = marginX + col * (appW + marginX);
CGFloat appY = 40 + row * (appH + marginY);
appView.frame = CGRectMake(appX, appY, appW, appH);
}
}
// 點擊下載按鈕時就會調用
- (void)appViewClickedDownloadButton:(AppView *)appView{
// 1.取出模型
App *app = appView.app;
// 添加標籤
UILabel *tipLabel = [[UILabel alloc] init];
tipLabel.text = [NSString stringWithFormat:@"下載成功%@", app.name];
tipLabel.font = [UIFont systemFontOfSize:14];
tipLabel.textAlignment = NSTextAlignmentCenter;
tipLabel.textColor = [UIColor whiteColor];
tipLabel.backgroundColor = [UIColor blackColor];
tipLabel.frame = CGRectMake(0, 0, 170, 40);
tipLabel.center = CGPointMake(187.5, 300);
tipLabel.alpha = 0.0;
tipLabel.layer.cornerRadius = 5;
tipLabel.clipsToBounds = YES;
[self.view addSubview:tipLabel];
// 3.動畫
[UIView animateWithDuration:1.0 animations:^{
tipLabel.alpha = 0.5;
} completion:^(BOOL finished) {
[UIView animateWithDuration:1.0 delay:1.0 options:UIViewAnimationOptionCurveLinear animations:^{
tipLabel.alpha = 0.0;
} completion:^(BOOL finished) {
[tipLabel removeFromSuperview];
}];
}];
}
- (NSArray *)apps
{
if(_apps == nil){
// 初始化
// 1.獲得plist的全路徑
NSString *path = [[NSBundle mainBundle] pathForResource:@"app.plist" ofType:nil];
// 2.加載數組
NSArray *dictArray = [NSArray arrayWithContentsOfFile:path];
// 3.將dictArray裏面的所有字典轉成模型對象,放到新的數組中
NSMutableArray *appArray = [NSMutableArray array];
for(NSDictionary *dict in dictArray){
// 3.1 創建模型對象
App *app = [App appWithDict:dict];
// 3.2 添加模型對象到數組中
[appArray addObject:app];
}
// 4.賦值
_apps = appArray;
}
return _apps;
}
@end
//
// App.h
// 模型類:用來存放數據的類
#import <Foundation/Foundation.h>
@interface App : NSObject
// 模型的名稱
@property (nonatomic, copy) NSString *name;
// 模型的圖標
@property (nonatomic, copy) NSString *icon;
// 通過字典來初始化模型對象
- (instancetype) initWithDict:(NSDictionary *)dict;
+ (instancetype) appWithDict:(NSDictionary *)dict;
@end
//
// App.m
//
#import "App.h"
@implementation App
- (instancetype)initWithDict:(NSDictionary *)dict
{
if(self = [super init]){
self.name = dict[@"name"];
self.icon = dict[@"icon"];
}
return self;
}
+ (instancetype)appWithDict:(NSDictionary *)dict
{
return [[self alloc] initWithDict:dict];
}
@end
//
// AppView.h
//
#import <UIKit/UIKit.h>
@class App, AppView;
// 聲明一個協議
@protocol AppViewDelegate <NSObject>
@optional
- (void)appViewClickedDownloadButton:(AppView *)appView;
@end
@interface AppView : UIView
// 代理
@property (nonatomic, weak) id<AppViewDelegate> delegate;
// 數據模型
@property (nonatomic, strong) App *app;
// 創建模型
+ (instancetype)appView;
//通過模型數據來創建一個view
+ (instancetype)appViewWithApp:(App *)app;
@end
//
// AppView.m
//
#import "AppView.h"
#import "App.h"
@interface AppView()
@property (weak, nonatomic) IBOutlet UIImageView *iconView;
@property (weak, nonatomic) IBOutlet UILabel *nameLabel;
- (IBAction)download:(UIButton *)sender;
@end
@implementation AppView
+ (instancetype)appViewWithApp:(App *)app{
NSBundle *bundle = [NSBundle mainBundle];
// 讀取xib文件(會創建xib中的描述的所有對象,並且按順序放到數組中返回)
NSArray *objs = [bundle loadNibNamed:@"AppView" owner:nil options:nil];
AppView *appView = [objs lastObject];
appView.app = app;
return appView;
}
+ (instancetype)appView{
return [self appViewWithApp:nil];
}
- (void)setApp:(App *)app
{
_app = app;
// 1.設置圖標
self.iconView.image = [UIImage imageNamed:app.icon];
// 2.設置名稱
self.nameLabel.text = app.name;
}
// 下載按鈕
- (IBAction)download:(UIButton *)sender {
// 1.讓按鈕失效(文字變爲“已下載”)
sender.enabled = NO;
[sender setTitle:@"已下載" forState:UIControlStateDisabled];
// 2.通知代理,download按鈕被點擊。
if([self.delegate respondsToSelector:@selector(appViewClickedDownloadButton:)]){
[self.delegate appViewClickedDownloadButton:self];
}
}
@end
代理模式
爲什麼要用代理模式
- 我們要拿到控制器View,然後讓它顯示tipLabel,但是怎麼拿到控制器View?
- (1)將控制器View傳到AppView.m裏面,然後在download:方法調用,但這種方法非常不好,耦合性太強。
- (2) 將按鈕拋出去(放在AppView.h裏面,這樣控制器也可以監聽),讓控制器View監聽,這樣也不好。
- 正確的方法是用代理模式,控制器作爲AppView的代理。
對代理模式的分析
- 當點擊下載按鈕的時候,應該通知控制器,控制器得知點擊了下載按鈕,就執行:添加tipLabel到控制器的View
- 該做的事情應該交給該做的那個類去做(比較適合誰做的事情,就交給誰去做)。比如說我們要拿到控制器的view,添加一個label,誰能直接拿到控制器的view,就是控制器。所以我們得到一個結論,拿到控制器,添加lable的代碼(操作),應該放到控制器裏面。
- 控制器要監聽【AppView】內部下載按鈕的點擊,當下載按鈕被點擊了,會到download:方法,我們要在download:方法裏面通知控制器(代理)(
[self.delegate appViewClickedDownloadButton:self];
這個方法就是通知代理),下載按鈕被點擊,然後控制器把label添加到它的view上面上去。
達到效果
- 點擊下載按鈕後變成不能點擊的“已安裝”
- 中間慢慢彈出提⽰示:已經成功安裝xxx,然後提⽰示會慢慢消失