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文件代码(字典转模型)

    复制代码
      1 #import "LFViewController.h"
      2 #import "LFAppInfo.h"
      3 
      4 @interface LFViewController ()
      5 @property (nonatomic, strong) NSArray *appList;
      6 @end
      7 
      8 @implementation LFViewController
      9 
     10 // 字典转模型
     11 - (NSArray *)appList
     12 {
     13     if (!_appList) {
     14         // 1. 从mainBundle加载
     15         NSBundle *bundle = [NSBundle mainBundle];
     16         NSString *path = [bundle pathForResource:@"app.plist" ofType:nil];
     17 //        _appList = [NSArray arrayWithContentsOfFile:path];
     18         
     19         NSArray *array = [NSArray arrayWithContentsOfFile:path];
     20         // 将数组转换成模型,意味着self.appList中存储的是LFAppInfo对象
     21         // 1. 遍历数组,将数组中的字典依次转换成AppInfo对象,添加到一个临时数组
     22         // 2. self.appList = 临时数组
     23 
     24         NSMutableArray *arrayM = [NSMutableArray array];
     25         for (NSDictionary *dict in array) {
     26            //用字典来实例化对象的工厂方法
     27             [arrayM addObject:[LFAppInfo appInfoWithDict:dict]];
     28         }
     29         
     30         _appList = arrayM;
     31     }
     32     return _appList;
     33 }
     34 
     35 - (void)viewDidLoad
     36 {
     37     [super viewDidLoad];
     38     
     39     // 总共有3列
     40     int totalCol = 3;
     41     CGFloat viewW = 80;
     42     CGFloat viewH = 90;
     43     
     44     CGFloat marginX = (self.view.bounds.size.width - totalCol * viewW) / (totalCol + 1);
     45     CGFloat marginY = 10;
     46     CGFloat startY = 20;
     47     
     48     for (int i = 0; i < self.appList.count; i++) {
     49 
     50         int row = i / totalCol;
     51         int col = i % totalCol;
     52         
     53         CGFloat x = marginX + (viewW + marginX) * col;
     54         CGFloat y = startY + marginY + (viewH + marginY) * row;
     55         
     56         UIView *appView = [[UIView alloc] initWithFrame:CGRectMake(x, y, viewW, viewH)];
     57         
     58         [self.view addSubview:appView];
     59         
     60         // 创建appView内部的细节
     61         // 0> 读取数组中的AppInfo
     62 //        NSDictionary *dict = self.appList[i];
     63         LFAppInfo *appInfo = self.appList[i];
     64         
     65         // 1> UIImageView
     66         UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, viewW, 50)];
     67         imageView.image = appInfo.image;
     68         imageView.contentMode = UIViewContentModeScaleAspectFit;
     69         
     70         [appView addSubview:imageView];
     71         
     72         // 2> UILabel
     73         UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0, imageView.bounds.size.height, viewW, 20)];
     74         // 设置文字
     75         label.text = appInfo.name;
     76         label.font = [UIFont systemFontOfSize:12.0];
     77         label.textAlignment = NSTextAlignmentCenter;
     78         
     79         [appView addSubview:label];
     80         
     81         // 3> UIButton
     82         // UIButtonTypeCustom和[[UIButton alloc] init]是等价的
     83         UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
     84         button.frame = CGRectMake(15, 70, viewW - 30, 20);
     85         
     86         [button setTitle:@"下载" forState:UIControlStateNormal];
     87         button.titleLabel.font= [UIFont systemFontOfSize:14.0];
     88         
     89         [button setBackgroundImage:[UIImage imageNamed:@"buttongreen"] forState:UIControlStateNormal];
     90         [button setBackgroundImage:[UIImage imageNamed:@"buttongreen_highlighted"] forState:UIControlStateHighlighted];
     91         
     92         [appView addSubview:button];
     93         button.tag = i;
     94         
     95         [button addTarget:self action:@selector(downloadClick:) forControlEvents:UIControlEventTouchUpInside];
     96     }
     97 }
     98 
     99 - (void)downloadClick:(UIButton *)button
    100 {
    101     NSLog(@"%d", button.tag);
    102     // 实例化一个UILabel显示在视图上,提示用户下载完成
    103     UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(80, 400, 160, 40)];
    104     label.textAlignment = NSTextAlignmentCenter;
    105     label.backgroundColor = [UIColor lightGrayColor];
    106     
    107     LFAppInfo *appInfo = self.appList[button.tag];
    108     label.text = [NSString stringWithFormat:@"下载%@完成", appInfo.name];
    109     label.font = [UIFont systemFontOfSize:13.0];
    110     label.alpha = 1.0;
    111     [self.view addSubview:label];
    112     
    113     // 动画效果
    114     // 动画效果完成之后,将Label从视图中删除
    115     // 首尾式动画,只能做动画,要处理完成后的操作不方便
    116 //    [UIView beginAnimations:nil context:nil];
    117 //    [UIView setAnimationDuration:1.0];
    118 //    label.alpha = 1.0;
    119 //    [UIView commitAnimations];
    120 
    121     // block动画比首尾式动画简单,而且能够控制动画结束后的操作
    122     // 在iOS中,基本都使用首尾式动画
    123     [UIView animateWithDuration:2.0 animations:^{
    124         label.alpha = 0.0;
    125     } completion:^(BOOL finished) {
    126         // 删除label
    127         [label removeFromSuperview];
    128     }];
    129 }
    130 
    131 @end
    复制代码

    模型.h文件代码

    复制代码
     1 #import <Foundation/Foundation.h>
     2 
     3 @interface LFAppInfo : NSObject
     4 
     5 // 应用程序名称
     6 @property (nonatomic, copy) NSString *name;
     7 // 应用程序图标名称
     8 @property (nonatomic, copy) NSString *icon;
     9 
    10 // 图像
    11 // 定义属性时,会生成getter&setter方法,还会生成一个带下划线的成员变量
    12 // 如果是readonly属性,只会生成getter方法,同时没有成员变量
    13 @property (nonatomic, strong, readonly) UIImage *image;
    14 
    15 // instancetype会让编译器检查实例化对象的准确类型
    16 // instancetype只能用于返回类型,不能当做参数使用
    17 
    18 - (instancetype)initWithDict:(NSDictionary *)dict;
    19 /** 工厂方法 */
    20 + (instancetype)appInfoWithDict:(NSDictionary *)dict;
    21 
    22 @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文件

    复制代码
     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 
    17 /** 从plist加载对象数组 */
    18 + (NSArray *)questions;
    19 
    20 @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)在模型内部的数据处理部分,可以使用键值编码来进行处理

    复制代码
     1 - (instancetype)initWithDict:(NSDictionary *)dict
     2 {
     3     self = [super init];
     4     if (self) {
     5 //        self.answer = dict[@"answer"];
     6 //        self.icon = dict[@"icon"];
     7 //        self.title = dict[@"title"];
     8 //        self.options = dict[@"options"];
     9         
    10         // KVC (key value coding)键值编码
    11         // cocoa 的大招,允许间接修改对象的属性值
    12         // 第一个参数是字典的数值
    13         // 第二个参数是类的属性
    14         [self setValue:dict[@"answer"] forKeyPath:@"answer"];
    15         [self setValue:dict[@"icon"] forKeyPath:@"icon"];
    16         [self setValue:dict[@"title"] forKeyPath:@"title"];
    17         [self setValue:dict[@"options"] forKeyPath:@"options"];
    18     }
    19     return self;
    20 }
    复制代码

    (2)setValuesForKeys的使用

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

    复制代码
    1 - (instancetype)initWithDict:(NSDictionary *)dict
    2 {
    3     self = [super init];
    4     if (self) {
    5         // 使用setValuesForKeys要求类的属性必须在字典中存在,可以比字典中的键值多,但是不能少。
    6         [self setValuesForKeysWithDictionary:dict];
    7     }
    8     return self;
    9 }
    复制代码

    三、补充说明

    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的真实类型

  • 相关阅读:
    LeetCode 123. Best Time to Buy and Sell Stock III (stock problem)
    精帖转载(关于stock problem)
    LeetCode 122. Best Time to Buy and Sell Stock II (stock problem)
    LeetCode 121. Best Time to Buy and Sell Stock (stock problem)
    LeetCode 120. Triangle
    基于docker 搭建Elasticsearch5.6.4 分布式集群
    从零开始构建一个centos+jdk7+tomcat7的docker镜像文件
    Harbor实现容器镜像仓库的管理和运维
    docker中制作自己的JDK+tomcat镜像
    docker镜像制作---jdk7+tomcat7基础镜像
  • 原文地址:https://www.cnblogs.com/paranoia/p/5803298.html
Copyright © 2011-2022 走看看