[iOS基础控件 - 5.5] 代理设计模式 (基于”APP列表"练习)
A.概述
在"[iOS基础控件 - 4.4] APP列表 进一步封装,初见MVC模式”上进一步改进,给“下载”按钮加上效果、功能
1.按钮点击后,显示为“已下载”,并且不可以再按
2.在屏幕中间弹出一个消息框,通知消息“xx已经被安装”,慢慢消失
3.消息框样式为圆角半透明
B.不使用代理模式,使用app空间组和主View之间的父子View关系
1.在主View中创建一个消息框
主View控制器:ViewController.m
1 // 创建下载成功消息框 2 CGFloat labelWidth = 200; 3 CGFloat labelHeight = 50; 4 CGFloat labelX = (self.view.frame.size.width - labelWidth) / 2; 5 CGFloat labelY = (self.view.frame.size.height - labelHeight) / 2; 6 UILabel *successMsgLabel = [[UILabel alloc] initWithFrame:CGRectMake(labelX, labelY, labelWidth, labelHeight)]; 7 8 // 设置圆角矩形样式 9 successMsgLabel.layer.cornerRadius = 10.0; 10 successMsgLabel.layer.masksToBounds = YES; 11 12 // 设置全透明隐藏 13 successMsgLabel.alpha = 0; 14 15 successMsgLabel.textColor = [UIColor whiteColor]; 16 successMsgLabel.backgroundColor = [UIColor grayColor]; 17 [successMsgLabel setTextAlignment:NSTextAlignmentCenter]; 18 successMsgLabel.tag = 10; 19 20 [self.view addSubview:successMsgLabel];
2.直接在app空间组的控制器中操作消息框,加入到父控件(主View)
app控件组View:AppView.m
1 // 点击下载按钮 2 - (IBAction)onDownloadButtonClick { 3 // 更改“下载”按钮样式 4 [self.downloadButton setTitle:@"已下载" forState:UIControlStateDisabled]; 5 self.downloadButton.enabled = NO; 6 7 // 拿到消息框 8 UILabel *successMsgLabel = [self.superview viewWithTag:10]; 9 successMsgLabel.text = [NSString stringWithFormat:@"成功安装了%@", self.appData.name]; 10 successMsgLabel.alpha = 0.7; 11 12 // 使用动画 13 [UIView beginAnimations:nil context:nil]; 14 [UIView setAnimationDuration:2]; 15 successMsgLabel.alpha = 0; 16 [UIView commitAnimations]; 17 }
缺点:view的父子关系规定了这两个view的层次关系,依赖性、耦合性太强,导致各个view都不能自由修改
C.不使用代理模式,在app控件组的view中将主View作为一个成员
1.在控件组AppView中创建主View的引用
控件组 AppView.h
1 // 存储主View的引用 2 @property (nonatomic, weak) UIView *vcView;
2.在主View逐个加载控件组的时候,设置主View的引用
ViewController.m
1 for (int index=0; index<self.apps.count; index++) { 2 App *appData = self.apps[index]; 3 4 // 1.创建View 5 AppView *appView = [AppView appViewWithApp:appData]; 6 7 // 2.定义每个app的位置、尺寸 8 CGFloat appX = marginX + column * (marginX + APP_WIDTH); 9 CGFloat appY = marginY + row * (marginY + APP_HEIGHT); 10 appView.frame = CGRectMake(appX, appY, APP_WIDTH, APP_HEIGHT); 11 12 // 设置每个app控件view的主view引用 13 appView.vcView = self.view; 14 15 // 3.加入此app信息到总view 16 [self.view addSubview:appView]; 17 18 column++; 19 if (column == appColumnCount) { 20 column = 0; 21 row++; 22 }
3.控件组AppView使用主View引用代替父控件引用
AppView.m
1 // 点击下载按钮 2 - (IBAction)onDownloadButtonClick { 3 // 更改“下载”按钮样式 4 [self.downloadButton setTitle:@"已下载" forState:UIControlStateDisabled]; 5 self.downloadButton.enabled = NO; 6 7 // 创建消息框 8 UILabel *successMsgLabel = [self.vcView viewWithTag:10]; 9 successMsgLabel.text = [NSString stringWithFormat:@"成功安装了%@", self.appData.name]; 10 successMsgLabel.alpha = 0.7; 11 12 // 使用动画 13 [UIView beginAnimations:nil context:nil]; 14 [UIView setAnimationDuration:2]; 15 successMsgLabel.alpha = 0; 16 [UIView commitAnimations]; 17 }
缺点:控件组AppView的下载事件中取得控制器来弹出消息框,还是依赖于主View,耦合性强
D.暴露控件组AppView的“下载”按钮,在主控制器中编写“下载”事件方法,绑定方法
1.AppView暴露“下载”按钮控件给外部
AppView.h
1 // 将“下载”按钮控件移到 .h 文件中暴露 2 @property (weak, nonatomic) IBOutlet UIButton *downloadButton;
2.在控制器中编写“下载”单击事件方法
ViewController.m
1 // 控制器创建“下载”按钮点击事件 2 - (IBAction)onAppViewDownloadButtonClick:(UIButton *) downloadButton { 3 // 更改“下载”按钮样式 4 [downloadButton setTitle:@"已下载" forState:UIControlStateDisabled]; 5 downloadButton.enabled = NO; 6 7 // 创建消息框 8 UILabel *successMsgLabel = [self.view viewWithTag:99]; 9 10 App *app = self.apps[downloadButton.tag]; 11 successMsgLabel.text = [NSString stringWithFormat:@"成功安装了%@", app.name]; 12 successMsgLabel.alpha = 0.7; 13 14 // 使用动画 15 [UIView beginAnimations:nil context:nil]; 16 [UIView setAnimationDuration:2]; 17 successMsgLabel.alpha = 0; 18 [UIView commitAnimations]; 19 } 20 21 @end
3.给每个AppView的“下载”按钮绑定方法
1 for (int index=0; index<self.apps.count; index++) { 2 App *appData = self.apps[index]; 3 4 // 1.创建View 5 AppView *appView = [AppView appViewWithApp:appData]; 6 7 // 2.定义每个app的位置、尺寸 8 CGFloat appX = marginX + column * (marginX + APP_WIDTH); 9 CGFloat appY = marginY + row * (marginY + APP_HEIGHT); 10 appView.frame = CGRectMake(appX, appY, APP_WIDTH, APP_HEIGHT); 11 12 // 存储每个AppView对应的AppData数据索引在tag中 13 appView.downloadButton.tag = index; 14 15 // 绑定每个AppView中的“下载”按钮点击事件 16 [appView.downloadButton addTarget:self action:@selector(onAppViewDownloadButtonClick:) forControlEvents:UIControlEventTouchUpInside]; 17 18 // 3.加入此app信息到总view 19 [self.view addSubview:appView]; 20 21 column++; 22 if (column == appColumnCount) { 23 column = 0; 24 row++; 25 } 26 }
缺点:依赖于AppView暴露的“下载”按钮,不能被修改
E.代理模式
1.原则:谁拥有资源,谁调用
添加label到控制器的逻辑:控制器来做
当点击“下载”按钮的时候,控件组AppView的按钮点击事件应该通知控制器,要执行添加label到控制器view的操作
控制器监听控件组AppView的下载按钮的点击
1.声明代理
AppView.h
1 // 定义代理的协议 2 @protocol AppViewDelegate <NSObject> 3 // “下载”按钮被点击事件 4 @optional 5 - (void) appViewClickedDownloadButton:(AppView *) appView; 6 @end
2.在AppView中创建代理引用
AppView.h
1 @interface AppView : UIView 2 // 代理 3 @property(nonatomic, weak) id<AppViewDelegate> delegate; 4 ... 5 @end
3.控制器遵守AppViewDelegate,使其拥有称为代理的资格
1 ViewController.m 2 @interface ViewController () <AppViewDelegate> 3 ... 4 @end
4.实现代理方法
ViewController.m
1 // “下载”按钮点击的代理方法 2 - (void)appViewClickedDownloadButton:(AppView *)appView { 3 // 创建下载成功消息框 4 CGFloat labelWidth = 200; 5 CGFloat labelHeight = 50; 6 CGFloat labelX = (self.view.frame.size.width - labelWidth) / 2; 7 CGFloat labelY = (self.view.frame.size.height - labelHeight) / 2; 8 UILabel *successMsgLabel = [[UILabel alloc] initWithFrame:CGRectMake(labelX, labelY, labelWidth, labelHeight)]; 9 10 // 设置圆角矩形样式 11 successMsgLabel.layer.cornerRadius = 10.0; 12 successMsgLabel.layer.masksToBounds = YES; 13 14 // 设置全透明隐藏 15 successMsgLabel.alpha = 0; 16 17 successMsgLabel.textColor = [UIColor whiteColor]; 18 successMsgLabel.backgroundColor = [UIColor grayColor]; 19 [successMsgLabel setTextAlignment:NSTextAlignmentCenter]; 20 21 successMsgLabel.text = [NSString stringWithFormat:@"成功安装了%@", appView.appData.name]; 22 successMsgLabel.alpha = 0.7; 23 24 // 使用动画 25 [UIView beginAnimations:nil context:nil]; 26 [UIView setAnimationDuration:2]; 27 successMsgLabel.alpha = 0; 28 [UIView commitAnimations]; 29 30 [self.view addSubview:successMsgLabel]; 31 }
5.给每一个AppView设置代理
ViewController.m
1 for (int index=0; index<self.apps.count; index++) { 2 App *appData = self.apps[index]; 3 4 // 1.创建View 5 AppView *appView = [AppView appViewWithApp:appData]; 6 7 // 2.定义每个app的位置、尺寸 8 CGFloat appX = marginX + column * (marginX + APP_WIDTH); 9 CGFloat appY = marginY + row * (marginY + APP_HEIGHT); 10 appView.frame = CGRectMake(appX, appY, APP_WIDTH, APP_HEIGHT); 11 12 // 设置代理 13 appView.delegate = self; 14 15 // 3.加入此app信息到总view 16 [self.view addSubview:appView]; 17 18 column++; 19 if (column == appColumnCount) { 20 column = 0; 21 row++; 22 } 23 }
6.点击“下载”按钮的时候,通知代理
AppView.m
1 // 点击下载按钮 2 - (IBAction)onDownloadButtonClick { 3 // 更改“下载”按钮样式 4 [self.downloadButton setTitle:@"已下载" forState:UIControlStateDisabled]; 5 self.downloadButton.enabled = NO; 6 7 // 通知代理 8 // 检查是否实现了代理方法 9 if ([self.delegate respondsToSelector:@selector(appViewClickedDownloadButton:)]) { 10 [self.delegate appViewClickedDownloadButton:self]; 11 } 12 }
主要代码:
Model:
1 App.h 2 // 3 // App.h 4 // 01-应用管理 5 // 6 // Created by hellovoidworld on 14/11/25. 7 // Copyright (c) 2014年 hellovoidworld. All rights reserved. 8 // 9 10 #import <Foundation/Foundation.h> 11 12 @interface App : NSObject 13 14 /** 15 copy : NSString 16 strong: 一般对象 17 weak: UI控件 18 assign: 基本数据类型 19 */ 20 21 /** 22 名称 23 */ 24 @property(nonatomic, copy) NSString *name; 25 26 /** 27 图标 28 */ 29 @property(nonatomic, copy) NSString *icon; 30 31 /** 32 自定义构造方法 33 通过字典来初始化模型对象 34 */ 35 - (instancetype) initWithDictionary:(NSDictionary *) dictionary; 36 37 + (instancetype) appWithDictionary:(NSDictionary *) dictionary; 38 39 @end
App.m
1 // 2 // App.m 3 // 01-应用管理 4 // 5 // Created by hellovoidworld on 14/11/25. 6 // Copyright (c) 2014年 hellovoidworld. All rights reserved. 7 // 8 9 #import "App.h" 10 11 #define ICON_KEY @"icon" 12 #define NAME_KEY @"name" 13 14 @implementation App 15 16 - (instancetype) initWithDictionary:(NSDictionary *) dictionary { 17 if (self = [super init]) { 18 self.name = dictionary[NAME_KEY]; 19 self.icon = dictionary[ICON_KEY]; 20 } 21 22 return self; 23 } 24 25 26 + (instancetype) appWithDictionary:(NSDictionary *) dictionary { 27 // 使用self代表类名代替真实类名,防止子类调用出错 28 return [[self alloc] initWithDictionary:dictionary]; 29 } 30 31 @end
View:
1 AppView.h 2 // 3 // AppView.h 4 // 01-应用管理 5 // 6 // Created by hellovoidworld on 14/11/25. 7 // Copyright (c) 2014年 hellovoidworld. All rights reserved. 8 // 9 10 #import <UIKit/UIKit.h> 11 12 @class App, AppView; 13 14 // 定义代理的协议 15 @protocol AppViewDelegate <NSObject> 16 // “下载”按钮被点击事件 17 @optional 18 - (void) appViewClickedDownloadButton:(AppView *) appView; 19 @end 20 21 @interface AppView : UIView 22 23 // 代理 24 @property(nonatomic, weak) id<AppViewDelegate> delegate; 25 26 // 在Controller和View之间传输的Model数据 27 @property(nonatomic, strong) App *appData; 28 29 30 // 自定义将Model数据加载到View的构造方法 31 - (instancetype) initWithApp:(App *) appData; 32 // 自定义构造的类方法 33 + (instancetype) appViewWithApp:(App *) appData; 34 // 返回一个不带Model数据的类构造方法 35 + (instancetype) appView; 36 37 38 @end
AppView.m
1 // 2 // AppView.m 3 // 01-应用管理 4 // 5 // Created by hellovoidworld on 14/11/25. 6 // Copyright (c) 2014年 hellovoidworld. All rights reserved. 7 // 8 9 #import "AppView.h" 10 #import "App.h" 11 12 // 封装私有属性 13 @interface AppView() 14 15 // 封装View中的控件,只允许自己访问 16 @property (weak, nonatomic) IBOutlet UIImageView *iconView; 17 @property (weak, nonatomic) IBOutlet UILabel *nameLabel; 18 @property (weak, nonatomic) IBOutlet UIButton *downloadButton; 19 20 21 22 - (IBAction)onDownloadButtonClick; 23 24 @end 25 26 @implementation AppView 27 28 - (void)setAppData:(App *)appData { 29 // 1.赋值Medel成员 30 _appData = appData; 31 32 // 2.设置图片 33 self.iconView.image = [UIImage imageNamed:appData.icon]; 34 // 3.设置名字 35 self.nameLabel.text = appData.name; 36 } 37 38 // 自定义将Model数据加载到View的构造方法 39 - (instancetype) initWithApp:(App *) appData { 40 // 1.从NIB取得控件 41 UINib *nib = [UINib nibWithNibName:@"app" bundle:[NSBundle mainBundle]]; 42 NSArray *viewArray = [nib instantiateWithOwner:nil options:nil]; 43 AppView *appView = [viewArray lastObject]; 44 45 // 2.加载Model 46 appView.appData = appData; 47 48 return appView; 49 } 50 51 // 自定义构造的类方法 52 + (instancetype) appViewWithApp:(App *) appData { 53 return [[self alloc] initWithApp:appData]; 54 } 55 56 // 返回一个不带Model数据的类构造方法 57 + (instancetype) appView { 58 return [self appViewWithApp:nil]; 59 } 60 61 // 点击下载按钮 62 - (IBAction)onDownloadButtonClick { 63 // 更改“下载”按钮样式 64 [self.downloadButton setTitle:@"已下载" forState:UIControlStateDisabled]; 65 self.downloadButton.enabled = NO; 66 67 // 通知代理 68 // 检查是否实现了代理方法 69 if ([self.delegate respondsToSelector:@selector(appViewClickedDownloadButton:)]) { 70 [self.delegate appViewClickedDownloadButton:self]; 71 } 72 } 73 74 @end
Controller:
ViewController.m
1 // 2 // ViewController.m 3 // 01-应用管理 4 // 5 // Created by hellovoidworld on 14/11/24. 6 // Copyright (c) 2014年 hellovoidworld. All rights reserved. 7 // 8 9 #import "ViewController.h" 10 #import "App.h" 11 #import "AppView.h" 12 13 #define ICON_KEY @"icon" 14 #define NAME_KEY @"name" 15 #define APP_WIDTH 85 16 #define APP_HEIGHT 90 17 #define MARGIN_HEAD 20 18 #define ICON_WIDTH 50 19 #define ICON_HEIGHT 50 20 #define NAME_WIDTH APP_WIDTH 21 #define NAME_HEIGHT 20 22 #define DOWNLOAD_WIDTH (APP_WIDTH - 20) 23 #define DOWNLOAD_HEIGHT 20 24 25 @interface ViewController () <AppViewDelegate> 26 27 /** 存放应用信息 */ 28 @property(nonatomic, strong) NSArray *apps; // 应用列表 29 30 @end 31 32 @implementation ViewController 33 34 - (void)viewDidLoad { 35 [super viewDidLoad]; 36 // Do any additional setup after loading the view, typically from a nib. 37 38 [self loadApps]; 39 } 40 41 - (void)didReceiveMemoryWarning { 42 [super didReceiveMemoryWarning]; 43 // Dispose of any resources that can be recreated. 44 } 45 46 #pragma mark 取得应用列表 47 - (NSArray *) apps { 48 if (nil == _apps) { 49 // 1.获得plist的全路径 50 NSString *path = [[NSBundle mainBundle] pathForResource:@"app.plist" ofType:nil]; 51 52 // 2.加载数据 53 NSArray *dictArray = [NSArray arrayWithContentsOfFile:path]; 54 55 // 3.将dictArray里面的所有字典转成模型,放到新数组中 56 NSMutableArray *appArray = [NSMutableArray array]; 57 for (NSDictionary *dict in dictArray) { 58 // 3.1创建模型对象 59 App *app = [App appWithDictionary:dict]; 60 61 // 3.2 添加到app数组中 62 [appArray addObject:app]; 63 } 64 65 _apps = appArray; 66 } 67 68 return _apps; 69 } 70 71 #pragma mark 加载全部应用列表 72 - (void) loadApps { 73 int appColumnCount = [self appColumnCount]; 74 int appRowCount = [self appRowCount]; 75 76 CGFloat marginX = (self.view.frame.size.width - APP_WIDTH * appColumnCount) / (appColumnCount + 1); 77 CGFloat marginY = (self.view.frame.size.height - APP_HEIGHT * appRowCount) / (appRowCount + 1) + MARGIN_HEAD; 78 79 int column = 0; 80 int row = 0; 81 for (int index=0; index<self.apps.count; index++) { 82 App *appData = self.apps[index]; 83 84 // 1.创建View 85 AppView *appView = [AppView appViewWithApp:appData]; 86 87 // 2.定义每个app的位置、尺寸 88 CGFloat appX = marginX + column * (marginX + APP_WIDTH); 89 CGFloat appY = marginY + row * (marginY + APP_HEIGHT); 90 appView.frame = CGRectMake(appX, appY, APP_WIDTH, APP_HEIGHT); 91 92 // 设置代理 93 appView.delegate = self; 94 95 // 3.加入此app信息到总view 96 [self.view addSubview:appView]; 97 98 column++; 99 if (column == appColumnCount) { 100 column = 0; 101 row++; 102 } 103 } 104 105 } 106 107 108 #pragma mark 计算列数 109 - (int) appColumnCount { 110 int count = 0; 111 count = self.view.frame.size.width / APP_WIDTH; 112 113 if ((int)self.view.frame.size.width % (int)APP_WIDTH == 0) { 114 count--; 115 } 116 117 return count; 118 } 119 120 #pragma mark 计算行数 121 - (int) appRowCount { 122 int count = 0; 123 count = (self.view.frame.size.height - MARGIN_HEAD) / APP_HEIGHT; 124 125 if ((int)(self.view.frame.size.height - MARGIN_HEAD) % (int)APP_HEIGHT == 0) { 126 count--; 127 } 128 129 return count; 130 } 131 132 // “下载”按钮点击的代理方法 133 - (void)appViewClickedDownloadButton:(AppView *)appView { 134 // 创建下载成功消息框 135 CGFloat labelWidth = 200; 136 CGFloat labelHeight = 50; 137 CGFloat labelX = (self.view.frame.size.width - labelWidth) / 2; 138 CGFloat labelY = (self.view.frame.size.height - labelHeight) / 2; 139 UILabel *successMsgLabel = [[UILabel alloc] initWithFrame:CGRectMake(labelX, labelY, labelWidth, labelHeight)]; 140 141 // 设置圆角矩形样式 142 successMsgLabel.layer.cornerRadius = 10.0; 143 successMsgLabel.layer.masksToBounds = YES; 144 145 // 设置全透明隐藏 146 successMsgLabel.alpha = 0; 147 148 successMsgLabel.textColor = [UIColor whiteColor]; 149 successMsgLabel.backgroundColor = [UIColor grayColor]; 150 [successMsgLabel setTextAlignment:NSTextAlignmentCenter]; 151 152 successMsgLabel.text = [NSString stringWithFormat:@"成功安装了%@", appView.appData.name]; 153 successMsgLabel.alpha = 0.7; 154 155 // 使用动画 156 [UIView beginAnimations:nil context:nil]; 157 [UIView setAnimationDuration:2]; 158 successMsgLabel.alpha = 0; 159 [UIView commitAnimations]; 160 161 [self.view addSubview:successMsgLabel]; 162 } 163 164 @end