这里做一个类似于下面界面的小案例
1.创建一个空的布局文件 .xib
new File -->User Interface -->选择View
创建一个空的view ,会自动生成一个 .xib的文件
2.设置我们自己需要经常复用的界面
注意:记得设置"Custom Class"中的 Class属性 与我们的代码文件 .h .m (Cocoa Touch Class文件 )相关联
3.创建我们的Cocoa文件(Cocoa Touch Class文件)
继承 <UIKit/UIKit.h> 包种的 UIView类
.h文件
#import <UIKit/UIKit.h>
@class CZApp; @interface CZAppView : UIView @property (nonatomic,strong) CZApp *model; //为自定义view封装一个类方法,这个类方法的作用就是创建一个view对象
+(instancetype) appView; @end
.m文件
#import "CZAppView.h"
#import "CZApp.h"
@interface CZAppView()
@property (weak, nonatomic) IBOutlet UIImageView *imgViewIcon;
@property (weak, nonatomic) IBOutlet UILabel *lblName;
@property (weak, nonatomic) IBOutlet UIButton *btnDownload;
- (IBAction)btnDownloadClick:(UIButton *)sender; @end
@implementation CZAppView
+(instancetype) appView{
//1.通过xib创建每一个应用(UIView)
//通过动态加载xib文件创建里面的view
//1.1>找到应用的根目录
NSBundle *rootBundle =[NSBundle mainBundle];//NSLog(@"%@",[mainBundle bundlePath]);
//1.2>在应用程序根目录下取搜索对应的Xib(nib)文件
return [[rootBundle loadNibNamed:@"CZAppView" owner:nil options:nil]lastObject];
}
//重写model属性的set方法
-(void)setModel:(CZApp *)model{
//先赋值
_model = model; //解析模型数据,把模型数据赋值给UIView中的各个子控件
self.imgViewIcon.image = [UIImage imageNamed:model.icon];
self.lblName.text=model.name;
}
//下载按钮的单击事件
- (IBAction)btnDownloadClick:(UIButton *)sender {
//1.禁用当前被点击的按钮
sender.enabled = NO; //2.弹出一个消息提醒框(这个消息提醒框其实就是一个UILable)
UILabel *lblMsg = [[UILabel alloc]init];
//2.1 设置lblMsg的显示文字
lblMsg.text=@"正在下载....";
//2.2 设置lblMsg的背景色
lblMsg.backgroundColor = [UIColor blackColor];
//2.3设置lblMsg的frame
CGFloat viewW = self.superview.frame.size.width;
CGFloat viewH = self.superview.frame.size.height;
CGFloat msgW = ;
CGFloat msgH = ;
CGFloat msgX= (viewW -msgW)/;
CGFloat msgY = (viewH - msgH)*0.5;
lblMsg.frame=CGRectMake(msgX, msgY, msgW, msgH);
//2.4设置label的文字颜色
lblMsg.textColor = [UIColor redColor];
//2.5设置label居中显示
lblMsg.textAlignment = NSTextAlignmentCenter;
//2.6设置文字粗体
lblMsg.font = [UIFont boldSystemFontOfSize:];
//2.7设置label的透明度
lblMsg.alpha = 0.0;//一开始把透明度设置为0 ,然后通过动画的方式慢慢的改变透明度
//2.8 设置Label为"圆角"
//设置四个角的"半径"
lblMsg.layer.cornerRadius =;
//把多余的部分裁剪掉
lblMsg.layer.masksToBounds =YES;
//2.9通过动画的方式来显示Label
// [UIView animateWithDuration:2.0 animations:^{
// lblMsg.alpha =0.6;
// }]; //开启一个动画,这个动画要执行1.5秒
[UIView animateWithDuration:1.5 animations:^{
//动画代码:将透明度变为0.6
lblMsg.alpha = 0.6;
} completion:^(BOOL finished) {
if(finished)
{
//这个代码的含义是,隔一段时间后再启动另外一个动画
//这个动画的执行时间是1.5秒,但是这个动画会在1.0秒之后再开始执行
//UIViewAnimationOptionCurveLinear表示是均速执行动画
[UIView animateWithDuration:1.5 delay:1.0 options:UIViewAnimationOptionCurveLinear animations:^{
//这个动画的代码
lblMsg.alpha = ;
} completion:^(BOOL finished) {
if(finished){
//当Label的透明度变为0以后,再把这个Label从view中移除
[lblMsg removeFromSuperview];
}
}];
}
}];
//3.把lblMsg加到控制器所管理的那个view上
[self.superview addSubview:lblMsg]; }
@end
这样我们的自定义控件就算完成了,接下来要做得就是引用我们自己所做的界面
4.由于我们加载的图片和名称都是来源 .plist文件
所以我们设置一个"模型" 来导入.plist文件
.h文件
#import <Foundation/Foundation.h> @interface CZApp : NSObject
@property(nonatomic,copy) NSString *name;
@property(nonatomic,copy) NSString *icon; -(instancetype)initWithDict:(NSDictionary *)dict;
+(instancetype)appWithDict:(NSDictionary *)dict;
@end
.m文件
#import "CZApp.h" @implementation CZApp
-(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
5.接下来我们就在控制器ViewController中开始使用我们自定义的控件
#import "ViewController.h"
#import "CZApp.h"
#import "CZAppView.h" @interface ViewController () //用来保存所有应用的数据
@property(nonatomic,strong)NSArray *apps; @end @implementation ViewController //重写apps属性的get方法,进行懒加载数据
-(NSArray *)apps{
if(_apps == nil){
//加载数据
//1.获取app.plist文件在手机上的路径
NSString *path = [[NSBundle mainBundle]pathForResource:@"app.plist" ofType:nil]; //2.根据路径加载数据
NSArray *arrayDict = [NSArray arrayWithContentsOfFile:path]; //3.创建一个可变数据用来保存一个一个的模型对象
NSMutableArray *arrayModels = [NSMutableArray array]; //一个空的可变数组 //4.循环字典数组,把每个字典对象转换成一个模型对象
for(NSDictionary *dict in arrayDict){
//创建一个模型
CZApp *model = [CZApp appWithDict:dict]; //把模型加到arrayModels中
[arrayModels addObject:model];
}
_apps = arrayModels;
}
return _apps;
} - (void)viewDidLoad {
[super viewDidLoad]; //假设每行的应用的个数
int columns = ; //获取控制器所管理的view的宽度
CGFloat viewWidth = self.view.frame.size.width; //每个应用的宽和高
CGFloat appW = ;
CGFloat appH = ;
CGFloat marginTop =;//第一行距离顶部的距离
CGFloat marginX = (viewWidth - columns *appW)/(columns+);
CGFloat marginY = marginX;//假设每行之间的间距与marginX相等 for(int i=;i<self.apps.count;i++){
//获取当前这个应用的数据字典
CZApp *appModel = self.apps[i]; //创建view
CZAppView *appView = [CZAppView appView]; //2.2 设置appView 的fram属性
//计算每个单元格所在的列的索引
int colIdx = i%columns;
//计算每个单元格的行索引
int rowIdx = i/columns; CGFloat appX = marginX + colIdx *(appW +marginX);
CGFloat appY = marginTop + rowIdx *(appH + marginY);
appView.frame = CGRectMake(appX, appY, appW, appH); //3.将appView加到self.view(控制器所管理的那个view)
[self.view addSubview:appView]; //设置数据
//把模型数据设置给"自定义view"的model属性
//然后重写model属性的set方法,在set方法中解析模型对象中的属性,并把属性值设置给自定义view的各个子控件
appView.model = appModel; } } - (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
} @end
再制作一个自动猜图的小案例
这里主要的是动态生成按钮,以及修改launchScreen的启动界面
效果图:
1.修改启动的launchscreen 以及应用小图标icon
简单来说只需要把我们制作的界面放入Images.xcassets中覆盖原来系统的LaunchImage和AppIcon就可以
主要注意的是关于尺寸的选择:
然后修改项目General中的APP Icons and Launch Image
1.先创建模型用于导入 .plist数据
.h
#import <Foundation/Foundation.h> @interface CZQuestion : NSObject @property (nonatomic, copy) NSString *answer;
@property (nonatomic, copy) NSString *icon;
@property (nonatomic, copy) NSString *title;
@property (nonatomic, strong) NSArray *options; - (instancetype)initWithDict:(NSDictionary *)dict;
+ (instancetype)questionWithDict:(NSDictionary *)dict; @end
.m
#import "CZQuestion.h" @implementation CZQuestion - (instancetype)initWithDict:(NSDictionary *)dict
{
if (self = [super init]) {
self.answer = dict[@"answer"];
self.title = dict[@"title"];
self.icon = dict[@"icon"];
self.options = dict[@"options"];
}
return self;
} + (instancetype)questionWithDict:(NSDictionary *)dict
{
return [[self alloc]initWithDict:dict];
}
@end
2.控制器ViewController.m
#import "ViewController.h"
#import "CZQuestion.h" @interface ViewController () <UIAlertViewDelegate> // 所有问题的数据都在这个数组中
@property (nonatomic, strong) NSArray *questions; // 控制题目索引, 类型的int类型属性, 默认没有赋值一开始就是0
@property (nonatomic, assign) int index; // 记录头像按钮原始的frame
@property (nonatomic, assign) CGRect iconFrame; @property (weak, nonatomic) IBOutlet UILabel *lblIndex;
@property (weak, nonatomic) IBOutlet UIButton *btnScore;
@property (weak, nonatomic) IBOutlet UILabel *lblTitle;
@property (weak, nonatomic) IBOutlet UIButton *btnIcon;
@property (weak, nonatomic) IBOutlet UIButton *btnNext;
@property (weak, nonatomic) IBOutlet UIView *answerView;
@property (weak, nonatomic) IBOutlet UIView *optionsView; // 用来引用那个“阴影”按钮的属性
@property (weak, nonatomic) UIButton *cover; - (IBAction)btnNextClick; - (IBAction)bigImage:(id)sender; // 头像按钮的单击事件
- (IBAction)btnIconClick:(id)sender; // 提示
- (IBAction)btnTipClick; @end @implementation ViewController // 懒加载数据
- (NSArray *)questions
{
if (_questions == nil) {
// 加载数据
NSString *path = [[NSBundle mainBundle] pathForResource:@"questions.plist" ofType:nil];
NSArray *arrayDict = [NSArray arrayWithContentsOfFile:path];
NSMutableArray *arrayModel = [NSMutableArray array]; // 遍历把字典转模型
for (NSDictionary *dict in arrayDict) {
CZQuestion *model = [CZQuestion questionWithDict:dict];
[arrayModel addObject:model];
}
_questions = arrayModel;
}
return _questions;
} // 改变状态栏的文字颜色为白色
- (UIStatusBarStyle)preferredStatusBarStyle
{
return UIStatusBarStyleLightContent;
} // 隐藏状态栏
- (BOOL)prefersStatusBarHidden
{
return YES;
} - (void)viewDidLoad {
[super viewDidLoad]; // 初始化显示第一题
self.index = -;
[self nextQuestion]; } - (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
} // 点击下一题
- (IBAction)btnNextClick { // 移动到下一题
[self nextQuestion];
} // 显示大图
- (IBAction)bigImage:(id)sender { // 记录一下头像按钮的原始frame
self.iconFrame = self.btnIcon.frame; // 1.创建大小与self.view一样的按钮, 把这个按钮作为一个"阴影"
UIButton *btnCover = [[UIButton alloc] init];
// 设置按钮大小
btnCover.frame = self.view.bounds;
// 设置按钮背景色
btnCover.backgroundColor = [UIColor blackColor];
// 设置按钮透明度
btnCover.alpha = 0.0; // 把按钮加到self.view中
[self.view addSubview:btnCover]; // 为阴影按钮注册一个单击事件
[btnCover addTarget:self action:@selector(samllImage) forControlEvents:UIControlEventTouchUpInside]; // 2. 把图片设置到阴影的上面
// 把self.view中的所有子控件中, 只把self.btnIcon显示到最上层
[self.view bringSubviewToFront:self.btnIcon]; // 通过self.cover来引用btnCover
self.cover = btnCover; // 3. 通过动画的方式把图片变大
CGFloat iconW = self.view.frame.size.width;
CGFloat iconH = iconW;
CGFloat iconX = ;
CGFloat iconY = (self.view.frame.size.height - iconH) * 0.5; [UIView animateWithDuration:0.7 animations:^{
// 设置按钮透明度
btnCover.alpha = 0.6; // 设置图片的新的frame
self.btnIcon.frame = CGRectMake(iconX, iconY, iconW, iconH);
}];
} // 头像按钮的单击事件
- (IBAction)btnIconClick:(id)sender {
if (self.cover == nil) {
// 显示大图
[self bigImage:nil];
} else {
[self samllImage];
}
} // 点击"提示"按钮
- (IBAction)btnTipClick {
// 1. 分数-1000
[self addScore:-]; // 2. 把所有的答案按钮"清空"(其实这里的"清空"最好是调用每个答案按钮的单击事件)
for (UIButton *btnAnswer in self.answerView.subviews) {
// 让每个答案按钮点击一下
[self btnAnswerClick:btnAnswer];
} // 3. 根据当前的索引, 从数据数组中(self.questions)中找到对应的数据模型
// 从数据模型中获取正确答案的第一个字符, 把待选按钮中和这个字符相等的那个按钮点击一下
CZQuestion *model = self.questions[self.index];
//截取正确答案中的第一个字符"字符串"
NSString *firstChar = [model.answer substringToIndex:]; // 根据firstChar在option按钮中找到对应的option 按钮, 让这个按钮点击一下
for (UIButton *btnOpt in self.optionsView.subviews) {
if ([btnOpt.currentTitle isEqualToString:firstChar]) {
[self optionButtonClick:btnOpt]; // 设置某个option 按钮点击一下
break;
}
}
} // "阴影"的单击事件
- (void)samllImage
{
[UIView animateWithDuration:0.7 animations:^{
// 1. 设置btnIcon(头像)按钮的frame还原
self.btnIcon.frame = self.iconFrame;
// 2. 让"阴影"按钮的透明度变成0
self.cover.alpha = 0.0;
} completion:^(BOOL finished) {
if (finished) {
// 移出"阴影"按钮
[self.cover removeFromSuperview];
// 当头像图片变成小图以后, 再把self.cover设置成nil
self.cover = nil;
}
}];
} // 实现UIAlertView的代理方法
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
// NSLog(@"%ld", buttonIndex);
if (buttonIndex == ) {
// 让程序在回到第0个问题
self.index = -;
[self nextQuestion];
}
} // 下一题
- (void)nextQuestion
{
// 1. 让索引++
self.index++; // 判断当前索引是否越界, 入股索引越界, 则提示用户
if (self.index == self.questions.count) {
//NSLog(@"答题完毕!!!!"); // 弹出一个对话框
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"操作提示" message:@"恭喜通关!" delegate:self cancelButtonTitle:@"确定" otherButtonTitles:nil]; // 显示对话框
[alertView show];
return;
} // 2. 根据索引获取当前的模型数据
CZQuestion *model = self.questions[self.index]; // 3. 根据模型设置数据
[self settingData:model]; // 4. 动态创建"答案按钮"
[self makeAnswerButtons:model]; // 5. 动态创建"待选按钮"
[self makeOptionsButton:model]; } // 创建待选按钮
- (void)makeOptionsButton:(CZQuestion *)model
{
// 0. 设置option view 可以与用户交互
self.optionsView.userInteractionEnabled = YES; // 1. 清除待选按钮的view中的所有子控件
[self.optionsView.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)]; // 2. 获取当前题目的待选文字的数组
NSArray *words = model.options; // 3. 根据待选文字循环来创建按钮 // 指定每个待选按钮的大小
CGFloat optionW = ;
CGFloat optionH = ;
// 指定每个按钮之间的间距
CGFloat margin = ;
// 指定每行有多少个按钮
int columns = ;
// 计算出每行第一个按钮距离左边的距离
CGFloat marginLeft = (self.optionsView.frame.size.width - columns * optionW - (columns - ) * margin) / ; for (int i = ; i < words.count; i++) {
// 创建一个按钮
UIButton *btnOpt = [[UIButton alloc] init]; // 给每个option 按钮一个唯一的tag值
btnOpt.tag = i; // 设置按钮背景
[btnOpt setBackgroundImage:[UIImage imageNamed:@"btn_option"] forState:UIControlStateNormal];
[btnOpt setBackgroundImage:[UIImage imageNamed:@"btn_option_highlighted"] forState:UIControlStateHighlighted]; // 设置按钮文字
[btnOpt setTitle:words[i] forState:UIControlStateNormal]; // 设置文字颜色为黑色
[btnOpt setTitleColor:[UIColor blackColor] forState:UIControlStateNormal]; // 计算当前按钮的列的索引和行的索引
int colIdx = i % columns;
int rowIdx = i / columns; CGFloat optionX = marginLeft + colIdx * (optionW + margin);
CGFloat optionY = + rowIdx * (optionH + margin); // 设置按钮frame
btnOpt.frame = CGRectMake(optionX, optionY, optionW, optionH); // 把按钮添加到optionsView中
[self.optionsView addSubview:btnOpt]; // 为待选按钮注册单击事件
[btnOpt addTarget:self action:@selector(optionButtonClick:) forControlEvents:UIControlEventTouchUpInside];
} } // 待选按钮的单击事件
- (void)optionButtonClick:(UIButton *)sender
{
// 1. 隐藏当前被点击的按钮
sender.hidden = YES; // 2. 把当前被点击的按钮的文字显示到第一个为空的"答案按钮"上
//NSString *text = [sender titleForState:UIControlStateNormal]; // 获取按钮指定状态下的文字
NSString *text = sender.currentTitle; // 获取按钮当前状态下的文字 // 2.1 把文字显示到答案按钮上 // 遍历每一个答案按钮
for (UIButton *answerBtn in self.answerView.subviews) {
// 判断每个"答案按钮"上的文字是否为nil
if (answerBtn.currentTitle == nil) { // 把当前点击的待选按钮的文字设置给对应的答案按钮
[answerBtn setTitle:text forState:UIControlStateNormal];
// 把当前点击的待选按钮的tag值也设置给对应的答案按钮
answerBtn.tag = sender.tag; break;
}
} // 3. 判断答案按钮是否已经填满了
// 一开始假设答案按钮是填满的
BOOL isFull = YES;
// 声明一个用来保存用户输入的答案的字符串
NSMutableString *userInput = [NSMutableString string]; for (UIButton *btnAnswer in self.answerView.subviews) {
if (btnAnswer.currentTitle == nil) {
isFull = NO;
break;
} else {
// 如果当前答案按钮上面有文字, 那么就把这个文字拼接起来
[userInput appendString:btnAnswer.currentTitle];
}
} // 如果已经填满, 则禁止option view 控件与用户的交互
if (isFull) {
// 禁止"待选按钮"被点击
self.optionsView.userInteractionEnabled = NO; // 获取当前题目的正确答案
CZQuestion *model = self.questions[self.index]; // 4. 如果答案按钮被填满了, 那么就判断用户点击输入的答案是否与标准答案一致,
if ([model.answer isEqualToString:userInput]) {
// 如果一致, 则设置答案按钮的文字颜色为蓝色, 同时在0.5秒之后跳转下一题
// 0. 如果正确+100分
[self addScore:];
//1. 设置所有的答案按钮的文字颜色为 蓝色
[self setAnswerButtonsTitleColor:[UIColor blueColor]]; // 延迟0.5秒后, 跳转到下一题
[self performSelector:@selector(nextQuestion) withObject:nil afterDelay:0.5]; } else {
// 如果答案不一致(答案错误), 设置答案按钮的文字颜色为红色
// 设置所有的答案按钮的文字颜色为 红色
[self setAnswerButtonsTitleColor:[UIColor redColor]];
}
}
} // 根据指定的分数, 来对界面上的按钮进行加分和减分
- (void)addScore:(int)score
{
// 1. 获取按钮上现在分值
NSString *str = self.btnScore.currentTitle; // 2. 把这个分值转换成数字类型
int currentScore = str.intValue; // 3. 对这个分数进行操作
currentScore = currentScore + score; // 4. 把新的分数设置给按钮
[self.btnScore setTitle:[NSString stringWithFormat:@"%d", currentScore] forState:UIControlStateNormal];
} // 统一设置答案按钮的文字颜色
- (void)setAnswerButtonsTitleColor:(UIColor *)color
{
// 遍历每一个答案按钮, 设置文字颜色
for (UIButton *btnAnswer in self.answerView.subviews) {
[btnAnswer setTitleColor:color forState:UIControlStateNormal];
}
} // 加载数据, 把模型数据设置到界面的控件上
- (void)settingData:(CZQuestion *)model
{
// 3. 把模型数据设置到界面对应的控件上
self.lblIndex.text = [NSString stringWithFormat:@"%d / %ld", (self.index + ), self.questions.count];
self.lblTitle.text = model.title;
[self.btnIcon setImage:[UIImage imageNamed:model.icon] forState:UIControlStateNormal]; // 4. 设置到达最后一题以后, 禁用"下一题按"钮
self.btnNext.enabled = (self.index != self.questions.count - );
} // 创建答案按钮
- (void)makeAnswerButtons:(CZQuestion *)model
{
// 这句话的意思:让subviews这个数组中的每个对象, 分别调用一次removeFromSuperview方法, 内部执行了循环,无需我们自己来些循环
[self.answerView.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)]; // 5.1 获取当前答案的文字的个数
NSInteger len = model.answer.length;
// 设置按钮的frame
CGFloat margin = ; // 假设每个按钮之间的间距都是10
CGFloat answerW = ;
CGFloat answerH = ;
CGFloat answerY = ;
CGFloat marginLeft = (self.answerView.frame.size.width - (len * answerW) - (len - ) * margin) / ; // 5.2 循环创建答案按钮, 有几个文字就创建几个按钮
for (int i = ; i < len; i++) {
// 创建按钮
UIButton *btnAnswer = [[UIButton alloc] init];
// 设置按钮的背景图
[btnAnswer setBackgroundImage:[UIImage imageNamed:@"btn_answer"] forState:UIControlStateNormal];
[btnAnswer setBackgroundImage:[UIImage imageNamed:@"btn_answer_highlighted"] forState:UIControlStateHighlighted]; // 计算按钮的x值
CGFloat answerX = marginLeft + i * (answerW + margin);
btnAnswer.frame = CGRectMake(answerX, answerY, answerW, answerH); // 设置答案按钮的文字颜色
[btnAnswer setTitleColor:[UIColor blackColor] forState:UIControlStateNormal]; // 把按钮加到answerView中
[self.answerView addSubview:btnAnswer]; // 为答案按钮注册单击事件
[btnAnswer addTarget:self action:@selector(btnAnswerClick:) forControlEvents:UIControlEventTouchUpInside]; }
} // 参数sender, 就表示当前点击的答案按钮
- (void)btnAnswerClick:(UIButton *)sender
{
// 0. 启用option view与用户的交互
self.optionsView.userInteractionEnabled = YES; // 1. 设置所有的答案按钮的文字颜色为黑色
[self setAnswerButtonsTitleColor:[UIColor blackColor]]; // 2. 在"待选按钮"中找到与当前被点击的答案按钮文字相同待选按钮,设置该按钮显示出来。
for (UIButton *optBtn in self.optionsView.subviews) {
// 比较判断待选按钮的文字是否与当前被点击的答案按钮的文字一致
// if ([sender.currentTitle isEqualToString:optBtn.currentTitle]) {
// optBtn.hidden = NO;
// break;
// } if (sender.tag == optBtn.tag) {
optBtn.hidden = NO;
break;
}
} // 1. 清空当前被点击的答案按钮的文字
[sender setTitle:nil forState:UIControlStateNormal]; }
@end