zoukankan      html  css  js  c++  java
  • iOS开发UI篇—字典转模型

    一、能完成功能的“问题代码”

    1.从plist中加载的数据

    2.实现的代码

     1 //
     2 //  LFViewController.m
     3 //  03-应用管理
     4 //
     5 //  Created by apple on 14-5-22.
     6 //  Copyright (c) 2014年 heima. All rights reserved.
     7 //
     8 
     9 #import "LFViewController.h"
    10 
    11 @interface LFViewController ()
    12 @property (nonatomic, strong) NSArray *appList;
    13 @end
    14 
    15 @implementation LFViewController
    16 
    17 - (NSArray *)appList
    18 {
    19     if (!_appList) {
    20 
    21         // 1. 从mainBundle加载
    22         NSBundle *bundle = [NSBundle mainBundle];
    23         NSString *path = [bundle pathForResource:@"app.plist" ofType:nil];
    24         _appList = [NSArray arrayWithContentsOfFile:path];
    25         
    26         NSLog(@"%@", _appList);
    27     }
    28     return _appList;
    29 }
    30 
    31 - (void)viewDidLoad
    32 {
    33     [super viewDidLoad];
    34     
    35     // 总共有3列
    36     int totalCol = 3;
    37     CGFloat viewW = 80;
    38     CGFloat viewH = 90;
    39     
    40     CGFloat marginX = (self.view.bounds.size.width - totalCol * viewW) / (totalCol + 1);
    41     CGFloat marginY = 10;
    42     CGFloat startY = 20;
    43     
    44     for (int i = 0; i < self.appList.count; i++) {
    45 
    46         int row = i / totalCol;
    47         int col = i % totalCol;
    48         
    49         CGFloat x = marginX + (viewW + marginX) * col;
    50         CGFloat y = startY + marginY + (viewH + marginY) * row;
    51         
    52         UIView *appView = [[UIView alloc] initWithFrame:CGRectMake(x, y, viewW, viewH)];
    53       
    54         [self.view addSubview:appView];
    55         
    56         // 创建appView内部的细节
    57         // 0> 读取数组中的字典
    58         NSDictionary *dict = self.appList[i];
    59         
    60         // 1> UIImageView
    61         UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, viewW, 50)];
    62         imageView.image = [UIImage imageNamed:dict[@"icon"]];
    63         imageView.contentMode = UIViewContentModeScaleAspectFit;
    64         [appView addSubview:imageView];
    65         
    66         // 2> UILabel
    67         UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0, imageView.bounds.size.height, viewW, 20)];
    68         // 设置文字
    69         label.text = dict[@"name"];
    70         label.font = [UIFont systemFontOfSize:12.0];
    71         label.textAlignment = NSTextAlignmentCenter;
    72         
    73         [appView addSubview:label];
    74         
    75         // 3> UIButton
    76         // UIButtonTypeCustom和[[UIButton alloc] init]是等价的
    77         UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
    78         button.frame = CGRectMake(15, 70, viewW - 30, 20);
    79         
    80         [button setTitle:@"下载" forState:UIControlStateNormal];
    81         // *** 不能使用如下代码直接设置title
    82 //        button.titleLabel.text = @"下载";
    83         // @property中readonly表示不允许修改对象的指针地址,但是可以修改对象的属性
    84         button.titleLabel.font= [UIFont systemFontOfSize:14.0];
    85         
    86         [button setBackgroundImage:[UIImage imageNamed:@"buttongreen"] forState:UIControlStateNormal];
    87         [button setBackgroundImage:[UIImage imageNamed:@"buttongreen_highlighted"] forState:UIControlStateHighlighted];
    88         
    89         [appView addSubview:button];
    90     }
    91 }
    92 
    93 @end

    3.实现效果

    4.代码问题

    在上述代码的第62,69行,我们是直接通过字典的键名获取plist中的数据信息,在viewController中需要直接和数据打交道,如果需要多次使用可能会因为不小心把键名写错,而程序并不报错。鉴于此,可以考虑把字典数据转换成一个模型,把数据封装到一个模型中去,让viewController不再直接和数据打交道,而是和模型交互。

    一般情况下,设置数据和取出数据都使用“字符串类型的key”,编写这些key时,编辑器没有智能提示,需要手敲。如:

    dict[@"name"] = @"Jack";

    NSString *name = dict[@"name"];

    手敲字符串key,key容易写错

    Key如果写错了,编译器不会有任何警告和报错,造成设错数据或者取错数据

    二、字典转模型

    1.字典转模型介绍

    示意图:

     

    字典转模型的好处:

    (1)降低代码的耦合度

    (2)所有字典转模型部分的代码统一集中在一处处理,降低代码出错的几率

    (3)在程序中直接使用模型的属性操作,提高编码效率 

    (4)调用方不用关心模型内部的任何处理细节

    字典转模型的注意点:

    模型应该提供一个可以传入字典参数的构造方法

    - (instancetype)initWithDict:(NSDictionary *)dict;

    + (instancetype)xxxWithDict:(NSDictionary *)dict;

    提示:在模型中合理地使用只读属性,可以进一步降低代码的耦合度。

     2.代码示例(一)

    新建一个类,用来作为数据模型

    viewController.m文件代码(字典转模型)

    #import "LFViewController.h"
    #import "LFAppInfo.h"
    
    @interface LFViewController ()
    @property (nonatomic, strong) NSArray *appList;
    @end
    
    @implementation LFViewController
    
    // 字典转模型
    - (NSArray *)appList
    {
        if (!_appList) {
            // 1. 从mainBundle加载
            NSBundle *bundle = [NSBundle mainBundle];
            NSString *path = [bundle pathForResource:@"app.plist" ofType:nil];
    //        _appList = [NSArray arrayWithContentsOfFile:path];
            
            NSArray *array = [NSArray arrayWithContentsOfFile:path];
            // 将数组转换成模型,意味着self.appList中存储的是LFAppInfo对象
            // 1. 遍历数组,将数组中的字典依次转换成AppInfo对象,添加到一个临时数组
            // 2. self.appList = 临时数组
    
            NSMutableArray *arrayM = [NSMutableArray array];
            for (NSDictionary *dict in array) {
               //用字典来实例化对象的工厂方法
                [arrayM addObject:[LFAppInfo appInfoWithDict:dict]];
            }
            
            _appList = arrayM;
        }
        return _appList;
    }
    
    - (void)viewDidLoad
    {
        [super viewDidLoad];
        
        // 总共有3列
        int totalCol = 3;
        CGFloat viewW = 80;
        CGFloat viewH = 90;
        
        CGFloat marginX = (self.view.bounds.size.width - totalCol * viewW) / (totalCol + 1);
        CGFloat marginY = 10;
        CGFloat startY = 20;
        
        for (int i = 0; i < self.appList.count; i++) {
    
            int row = i / totalCol;
            int col = i % totalCol;
            
            CGFloat x = marginX + (viewW + marginX) * col;
            CGFloat y = startY + marginY + (viewH + marginY) * row;
            
            UIView *appView = [[UIView alloc] initWithFrame:CGRectMake(x, y, viewW, viewH)];
            
            [self.view addSubview:appView];
            
            // 创建appView内部的细节
            // 0> 读取数组中的AppInfo
    //        NSDictionary *dict = self.appList[i];
            LFAppInfo *appInfo = self.appList[i];
            
            // 1> UIImageView
            UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, viewW, 50)];
            imageView.image = appInfo.image;
            imageView.contentMode = UIViewContentModeScaleAspectFit;
            
            [appView addSubview:imageView];
            
            // 2> UILabel
            UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0, imageView.bounds.size.height, viewW, 20)];
            // 设置文字
            label.text = appInfo.name;
            label.font = [UIFont systemFontOfSize:12.0];
            label.textAlignment = NSTextAlignmentCenter;
            
            [appView addSubview:label];
            
            // 3> UIButton
            // UIButtonTypeCustom和[[UIButton alloc] init]是等价的
            UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
            button.frame = CGRectMake(15, 70, viewW - 30, 20);
            
            [button setTitle:@"下载" forState:UIControlStateNormal];
            button.titleLabel.font= [UIFont systemFontOfSize:14.0];
            
            [button setBackgroundImage:[UIImage imageNamed:@"buttongreen"] forState:UIControlStateNormal];
            [button setBackgroundImage:[UIImage imageNamed:@"buttongreen_highlighted"] forState:UIControlStateHighlighted];
            
            [appView addSubview:button];
            button.tag = i;
            
            [button addTarget:self action:@selector(downloadClick:) forControlEvents:UIControlEventTouchUpInside];
        }
    }
    
    - (void)downloadClick:(UIButton *)button
    {
        NSLog(@"%d", button.tag);
        // 实例化一个UILabel显示在视图上,提示用户下载完成
        UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(80, 400, 160, 40)];
        label.textAlignment = NSTextAlignmentCenter;
        label.backgroundColor = [UIColor lightGrayColor];
        
        LFAppInfo *appInfo = self.appList[button.tag];
        label.text = [NSString stringWithFormat:@"下载%@完成", appInfo.name];
        label.font = [UIFont systemFontOfSize:13.0];
        label.alpha = 1.0;
        [self.view addSubview:label];
        
        // 动画效果
        // 动画效果完成之后,将Label从视图中删除
        // 首尾式动画,只能做动画,要处理完成后的操作不方便
    //    [UIView beginAnimations:nil context:nil];
    //    [UIView setAnimationDuration:1.0];
    //    label.alpha = 1.0;
    //    [UIView commitAnimations];
    
        // block动画比首尾式动画简单,而且能够控制动画结束后的操作
        // 在iOS中,基本都使用首尾式动画
        [UIView animateWithDuration:2.0 animations:^{
            label.alpha = 0.0;
        } completion:^(BOOL finished) {
            // 删除label
            [label removeFromSuperview];
        }];
    }
    
    @end

    模型.h文件代码

    #import <Foundation/Foundation.h>
    
    @interface LFAppInfo : NSObject
    
    // 应用程序名称
    @property (nonatomic, copy) NSString *name;
    // 应用程序图标名称
    @property (nonatomic, copy) NSString *icon;
    
    // 图像
    // 定义属性时,会生成getter&setter方法,还会生成一个带下划线的成员变量
    // 如果是readonly属性,只会生成getter方法,同时没有成员变量
    @property (nonatomic, strong, readonly) UIImage *image;
    
    // instancetype会让编译器检查实例化对象的准确类型
    // instancetype只能用于返回类型,不能当做参数使用
    
    - (instancetype)initWithDict:(NSDictionary *)dict;
    /** 工厂方法 */
    + (instancetype)appInfoWithDict:(NSDictionary *)dict;
    
    @end

    模型.m文件数据处理代码

     1 #import "LFAppInfo.h"
     2 
     3 @interface LFAppInfo()
     4 {
     5     UIImage *_imageABC;
     6 }
     7 @end
     8 
     9 @implementation LFAppInfo
    10 
    11 - (instancetype)initWithDict:(NSDictionary *)dict
    12 {
    13     self = [super init];
    14     if (self) {
    15         self.name = dict[@"name"];
    16         self.icon = dict[@"icon"];
    17     }
    18     return self;
    19 }
    20 
    21 + (instancetype)appInfoWithDict:(NSDictionary *)dict
    22 {
    23     return [[self alloc] initWithDict:dict];
    24 }
    25 
    26 - (UIImage *)image
    27 {
    28     if (!_imageABC) {
    29         _imageABC = [UIImage imageNamed:self.icon];
    30     }
    31     return _imageABC;
    32 }
    33 
    34 @end

    3.代码示例(二)

    数据信息:plist文件

    字典转模型(初步)

    模型.h文件

     1 #import <Foundation/Foundation.h>
     2 
     3 @interface LFQuestion : NSObject
     4 
     5 @property (nonatomic, copy) NSString *answer;
     6 @property (nonatomic, copy) NSString *title;
     7 @property (nonatomic, copy) NSString *icon;
     8 @property (nonatomic, strong) NSArray *options;
     9 
    10 @property (nonatomic, strong) UIImage *image;
    11 
    12 /** 用字典实例化对象的成员方法 */
    13 - (instancetype)initWithDict:(NSDictionary *)dict;
    14 /** 用字典实例化对象的类方法,又称工厂方法 */
    15 + (instancetype)questionWithDict:(NSDictionary *)dict;
    16 @end

    模型.m文件

     1 #import "LFQuestion.h"
     2 
     3 @implementation LFQuestion
     4 
     5 + (instancetype)questionWithDict:(NSDictionary *)dict
     6 {
     7     return [[self alloc] initWithDict:dict];
     8 }
     9 
    10 - (instancetype)initWithDict:(NSDictionary *)dict
    11 {
    12     self = [super init];
    13     if (self) {
    14         self.answer = dict[@"answer"];
    15         self.icon = dict[@"icon"];
    16         self.title = dict[@"title"];
    17         self.options = dict[@"options"];
    18 
    19         [self setValuesForKeysWithDictionary:dict];
    20     }
    21     return self;
    22 }

    viewController.m文件中的数据处理

     1 - (NSArray *)questions
     2 {
     3     if (!_questions) {
     4     
     5         NSArray *array = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"questions.plist" ofType:nil]];
     6         
     7         NSMutableArray *arrayM = [NSMutableArray array];
     8         
     9         for (NSDictionary *dict in array) {
    10             [arrayM addObject:[LFQuestion questionWithDict:dict]];
    11         }
    12         _questions=arrayM;
    13     }
    14     return _questions;
    15 }

    字典转模型(优化)

    上面代码可以做进一步的优化,从plist文件中读取数据是可以交给模型去处理的,优化后代码如下:

    模型.h文件

    #import <Foundation/Foundation.h>
    
    @interface LFQuestion : NSObject
    
    @property (nonatomic, copy) NSString *answer;
    @property (nonatomic, copy) NSString *title;
    @property (nonatomic, copy) NSString *icon;
    @property (nonatomic, strong) NSArray *options;
    
    @property (nonatomic, strong) UIImage *image;
    
    /** 用字典实例化对象的成员方法 */
    - (instancetype)initWithDict:(NSDictionary *)dict;
    /** 用字典实例化对象的类方法,又称工厂方法 */
    + (instancetype)questionWithDict:(NSDictionary *)dict;
    
    /** 从plist加载对象数组 */
    + (NSArray *)questions;
    
    @end

    模型.m文件

     1 #import "LFQuestion.h"
     2 
     3 @implementation LFQuestion
     4 
     5 + (instancetype)questionWithDict:(NSDictionary *)dict
     6 {
     7     return [[self alloc] initWithDict:dict];
     8 }
     9 
    10 - (instancetype)initWithDict:(NSDictionary *)dict
    11 {
    12     self = [super init];
    13     if (self) {
    14         self.answer = dict[@"answer"];
    15         self.icon = dict[@"icon"];
    16         self.title = dict[@"title"];
    17         self.options = dict[@"options"];
    18         
    19         [self setValuesForKeysWithDictionary:dict];
    20     }
    21     return self;
    22 }
    23 
    24 
    25 + (NSArray *)questions
    26 {
    27     NSArray *array = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"questions.plist" ofType:nil]];
    28     
    29     NSMutableArray *arrayM = [NSMutableArray array];
    30     
    31     for (NSDictionary *dict in array) {
    32         [arrayM addObject:[LFQuestion questionWithDict:dict]];
    33     }
    34     
    35     return arrayM;
    36 }
    37 @end

    viewController.m文件中的数据处理代码部分

    1 - (NSArray *)questions
    2 {
    3     if (!_questions) {
    4         _questions = [LFQuestion questions];
    5     }
    6     return _questions;
    7 }

    补充内容:(KVC)的使用

    (1)在模型内部的数据处理部分,可以使用键值编码来进行处理

    - (instancetype)initWithDict:(NSDictionary *)dict
    {
        self = [super init];
        if (self) {
    //        self.answer = dict[@"answer"];
    //        self.icon = dict[@"icon"];
    //        self.title = dict[@"title"];
    //        self.options = dict[@"options"];
            
            // KVC (key value coding)键值编码
            // cocoa 的大招,允许间接修改对象的属性值
            // 第一个参数是字典的数值
            // 第二个参数是类的属性
            [self setValue:dict[@"answer"] forKeyPath:@"answer"];
            [self setValue:dict[@"icon"] forKeyPath:@"icon"];
            [self setValue:dict[@"title"] forKeyPath:@"title"];
            [self setValue:dict[@"options"] forKeyPath:@"options"];
        }
        return self;
    }

    (2)setValuesForKeys的使用

    上述数据操作细节,可以直接通过setValuesForKeys方法来完成。

    - (instancetype)initWithDict:(NSDictionary *)dict
    {
        self = [super init];
        if (self) {
            // 使用setValuesForKeys要求类的属性必须在字典中存在,可以比字典中的键值多,但是不能少。
            [self setValuesForKeysWithDictionary:dict];
        }
        return self;
    }

    三、补充说明

    1.readonly属性

     (1)@property中readonly表示不允许修改对象的指针地址,但是可以修改对象的属性。

     (2)通常使用@property关键字定义属性时,会生成getter&setter方法,还会生成一个带下划线的成员变量。

     (3)如果是readonly属性,只会生成getter方法,不会生成带下划线的成员变量.

    2.instancetype类型

    (1)instancetype会让编译器检查实例化对象的准确类型 
    (2)instancetype只能用于返回类型,不能当做参数使用

    3.instancetype & id的比较

    (1) instancetype在类型表示上,跟id一样,可以表示任何对象类型

    (2) instancetype只能用在返回值类型上,不能像id一样用在参数类型上

    (3) instancetype比id多一个好处:编译器会检测instancetype的真实类型

  • 相关阅读:
    1.IntelliJ IDEA搭建SpringBoot的小Demo
    etc目录名字的意思---挖Linux中的古老缩略语
    CI当开启URL重写的时候,报错500 Internal Server Error
    app后端架构设计(转)
    Redis安装及主从配置
    ***Linux文件夹文件创建、删除、改名
    Redis中常用命令
    linux上ln链接命令详细说明
    Redis快速入门:安装、配置和操作
    redis的PHP扩展包安装方法
  • 原文地址:https://www.cnblogs.com/zengshuilin/p/5736534.html
Copyright © 2011-2022 走看看