模型的应用 && Xib &&代理模式的应用【应用管理】

模型

模型取代字典的好处

  • 使⽤用字典的坏处
    • ⼀一般情况下,设置数据和取出数据都使⽤用“字符串类型的key”,编写这些key时,编译器不会有任何友善提示,需要手敲。例如:
      dict[@"name"] = @"Jack";
      NSString *name = dict[@"name"];
    • 手敲字符串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,然后提⽰示会慢慢消失
    这里写图片描述 这里写图片描述
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章