详解iOS应用UI开发中的九宫格坐标计算与字典转换模型

时间:2022-01-02 08:27:36

九宫格坐标计算

一、要求

完成下面的布局

详解iOS应用UI开发中的九宫格坐标计算与字典转换模型

二、分析

寻找左边的规律,每一个uiview的x坐标和y坐标。

详解iOS应用UI开发中的九宫格坐标计算与字典转换模型

三、实现思路

(1)明确每一块用得是什么view

(2)明确每个view之间的父子关系,每个视图都只有一个父视图,拥有很多的子视图。

(3)可以先尝试逐个的添加格子,最后考虑使用for循环,完成所有uiview的创建

(4)加载app数据,根据数据长度创建对应个数的格子

(5)添加格子内部的子控件

(6)给内部的子控件装配数据

四、代码示例

复制代码 代码如下:


//
//  yyviewcontroller.m
//  九宫格练习
//
//  created by 孔医己 on 14-5-22.
//  copyright (c) 2014年 itcast. all rights reserved.
//

 

#import "yyviewcontroller.h"

@interface yyviewcontroller ()
@property(nonatomic,strong)nsarray *apps;
@end

 

复制代码 代码如下:


@implementation yyviewcontroller
//1.加载数据
- (nsarray *)apps
{
    if (!_apps) {
        nsstring *path=[[nsbundle mainbundle]pathforresource:@"app.plist" oftype:nil];
        _apps=[nsarray arraywithcontentsoffile:path];
    }
    return _apps;
}

 

- (void)viewdidload
{
    [super viewdidload];
    nslog(@"%d",self.apps.count);
   
    //2.完成布局设计
   
    //三列
    int totalloc=3;
    cgfloat appvieww=80;
    cgfloat appviewh=90;
   
    cgfloat margin=(self.view.frame.size.width-totalloc*appvieww)/(totalloc+1);
    int count=self.apps.count;
    for (int i=0; i<count; i++) {
        int row=i/totalloc;//行号
        //1/3=0,2/3=0,3/3=1;
        int loc=i%totalloc;//列号
       
        cgfloat appviewx=margin+(margin+appvieww)*loc;
        cgfloat appviewy=margin+(margin+appviewh)*row;
       
       
        //创建uiview控件
        uiview *appview=[[uiview alloc]initwithframe:cgrectmake(appviewx, appviewy, appvieww, appviewh)];
        //[appview setbackgroundcolor:[uicolor purplecolor]];
        [self.view addsubview:appview];
       
       
        //创建uiview控件中的子视图
        uiimageview *appimageview=[[uiimageview alloc]initwithframe:cgrectmake(0, 0, 80, 50)];
        uiimage *appimage=[uiimage imagenamed:self.apps[i][@"icon"]];
        appimageview.image=appimage;
        [appimageview setcontentmode:uiviewcontentmodescaleaspectfit];
       // nslog(@"%@",self.apps[i][@"icon"]);
        [appview addsubview:appimageview];
       
        //创建文本标签
        uilabel *applable=[[uilabel alloc]initwithframe:cgrectmake(0, 50, 80, 20)];
        [applable settext:self.apps[i][@"name"]];
        [applable settextalignment:nstextalignmentcenter];
        [applable setfont:[uifont systemfontofsize:12.0]];
        [appview addsubview:applable];
       
        //创建按钮
        uibutton *appbtn=[uibutton buttonwithtype:uibuttontypecustom];
        appbtn.frame= cgrectmake(10, 70, 60, 20);
        [appbtn setbackgroundimage:[uiimage imagenamed:@"buttongreen"] forstate:uicontrolstatenormal];
        [appbtn setbackgroundimage:[uiimage imagenamed:@"buttongreen_highlighted"] forstate:uicontrolstatehighlighted];
        [appbtn settitle:@"下载" forstate:uicontrolstatenormal];
        appbtn.titlelabel.font=[uifont systemfontofsize:12.0];
        [appview addsubview:appbtn];
       
        [appbtn addtarget:self action:@selector(click) forcontrolevents:uicontroleventtouchupinside];
    }

}

-(void)click
{
    //动画标签
    uilabel *animalab=[[uilabel alloc]initwithframe:cgrectmake(self.view.center.x-100, self.view.center.y+20, 200, 40)];
    [animalab settext:@"下载成功"];
    animalab.font=[uifont systemfontofsize:12.0];
    [animalab setbackgroundcolor:[uicolor browncolor]];
    [animalab setalpha:0];
    [self.view addsubview:animalab];
   
//    [uiview beginanimations:nil context:nil];
//    [animalab setalpha:1];
//    [uiview setanimationduration:4.0];
//    [uiview commitanimations];
   
    //执行完之后,还得把这给删除了,推荐使用block动画
   
    [uiview animatewithduration:4.0 animations:^{
    [animalab setalpha:1];
    } completion:^(bool finished) {
        //[self.view re];
    }];
}

- (void)didreceivememorywarning
{
    [super didreceivememorywarning];
}

@end


执行效果:

 

详解iOS应用UI开发中的九宫格坐标计算与字典转换模型

 

字典转模型

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

1.从plist中加载的数据

详解iOS应用UI开发中的九宫格坐标计算与字典转换模型

2.实现的代码

复制代码 代码如下:


//
//  lfviewcontroller.m
//  03-应用管理
//
//  created by apple on 14-5-22.
//  copyright (c) 2014年 heima. all rights reserved.
//

 

#import "lfviewcontroller.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];
       
        nslog(@"%@", _applist);
    }
    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> 读取数组中的字典
        nsdictionary *dict = self.applist[i];
       
        // 1> uiimageview
        uiimageview *imageview = [[uiimageview alloc] initwithframe:cgrectmake(0, 0, vieww, 50)];
        imageview.image = [uiimage imagenamed:dict[@"icon"]];
        imageview.contentmode = uiviewcontentmodescaleaspectfit;
        [appview addsubview:imageview];
       
        // 2> uilabel
        uilabel *label = [[uilabel alloc] initwithframe:cgrectmake(0, imageview.bounds.size.height, vieww, 20)];
        // 设置文字
        label.text = dict[@"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];
        // *** 不能使用如下代码直接设置title
//        button.titlelabel.text = @"下载";
        // @property中readonly表示不允许修改对象的指针地址,但是可以修改对象的属性
        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];
    }
}

@end


3.实现效果

 

详解iOS应用UI开发中的九宫格坐标计算与字典转换模型

4.代码问题

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

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

复制代码 代码如下:


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

 

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


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

 

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

二、字典转模型

1.字典转模型介绍

示意图:

详解iOS应用UI开发中的九宫格坐标计算与字典转换模型

字典转模型的好处:

(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文件数据处理代码

复制代码 代码如下:


#import "lfappinfo.h"

 

@interface lfappinfo()
{
    uiimage *_imageabc;
}
@end

 

复制代码 代码如下:


@implementation lfappinfo

 

- (instancetype)initwithdict:(nsdictionary *)dict
{
    self = [super init];
    if (self) {
        self.name = dict[@"name"];
        self.icon = dict[@"icon"];
    }
    return self;
}

+ (instancetype)appinfowithdict:(nsdictionary *)dict
{
    return [[self alloc] initwithdict:dict];
}

- (uiimage *)image
{
    if (!_imageabc) {
        _imageabc = [uiimage imagenamed:self.icon];
    }
    return _imageabc;
}

@end


3.代码示例(二)

 

数据信息:plist文件

详解iOS应用UI开发中的九宫格坐标计算与字典转换模型

字典转模型(初步)

模型.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;
@end


模型.m文件

复制代码 代码如下:


#import "lfquestion.h"

 

@implementation lfquestion

+ (instancetype)questionwithdict:(nsdictionary *)dict
{
    return [[self alloc] initwithdict:dict];
}

- (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"];

        [self setvaluesforkeyswithdictionary:dict];
    }
    return self;
}

viewcontroller.m文件中的数据处理

- (nsarray *)questions
{
    if (!_questions) {
   
        nsarray *array = [nsarray arraywithcontentsoffile:[[nsbundle mainbundle] pathforresource:@"questions.plist" oftype:nil]];
       
        nsmutablearray *arraym = [nsmutablearray array];
       
        for (nsdictionary *dict in array) {
            [arraym addobject:[lfquestion questionwithdict:dict]];
        }
        _questions=arraym;
    }
    return _questions;
}


字典转模型(优化)

 

上面代码可以做进一步的优化,从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文件

复制代码 代码如下:


#import "lfquestion.h"

 

@implementation lfquestion

+ (instancetype)questionwithdict:(nsdictionary *)dict
{
    return [[self alloc] initwithdict:dict];
}

- (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"];
       
        [self setvaluesforkeyswithdictionary:dict];
    }
    return self;
}


+ (nsarray *)questions
{
    nsarray *array = [nsarray arraywithcontentsoffile:[[nsbundle mainbundle] pathforresource:@"questions.plist" oftype:nil]];
   
    nsmutablearray *arraym = [nsmutablearray array];
   
    for (nsdictionary *dict in array) {
        [arraym addobject:[lfquestion questionwithdict:dict]];
    }
   
    return arraym;
}
@end


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

复制代码 代码如下:

- (nsarray *)questions
{
    if (!_questions) {
        _questions = [lfquestion questions];
    }
    return _questions;
}


补充内容:(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的真实类型