上面是文件的组织结构,下面为了更为直观的了解我们想要的效果,下面先看几张截图,来直观的感受一下运行效果,上面是竖屏的显示效果,下面是横 屏的显示效果。因为在封装自定义键盘中用到了自动布局所以横屏显示或者在更大的屏幕上显示是没问题的,常用表情是用户用过的表情,然后存在Sqlite 中,显示时并按时间降序排列。more是用来扩展功能用的接口。话不多说,来的代码才是实在的。
一.View(自定义视图)
View文件夹下存放的时我们自定义的视图组件,因为是自定义的组件所以storyboard我们就用不了啦,所有的代码都必须手写,这样 才能保证组件使用的灵活性和减少各个组件之间的耦合性,更利于团队之间的合作。在封装组件时要预留好外界可能使用到的接口,和返回该返回的数据。好啦,废话少说,来点干货吧!
1、FaceView组件的封装:FaceView即负责显示一个个的头像。在使用该组件时要传入要显示的图片和图片对应的文字(如【哈 哈】),当点击图片的时候,会通过block回调的形式把该图片的image以及图片文字返回到使用的组件中去,下面是关键代码:
FaceView.h中的代码如下(下面代码是定义啦相应的Block类型和对外的接口):
#import //声明表情对应的block,用于把点击的表情的图片和图片信息传到上层视图typedef void (^FaceBlock) (UIImage *image, NSString *imageText); @interface FaceView : UIView //图片对应的文字@property (nonatomic, strong) NSString *imageText;//表情图片@property (nonatomic, strong) UIImage *headerImage; //设置block回调-(void)setFaceBlock:(FaceBlock)block; //设置图片,文字-(void)setImage:(UIImage *) image ImageText:(NSString *) text; @endFaceView.m中的代码如下
// FaceView.m// MyKeyBoard//// Created by 青玉伏案 on 14-9-16.// Copyright (c) 2014年 Mrli. All rights reserved.// #import "FaceView.h" @interface FaceView ()@property(strong, nonatomic) FaceBlock block;@property (strong, nonatomic) UIImageView *imageView;@end @implementation FaceView //初始化图片- (id)initWithFrame:(CGRect)frame{ //face的大小 frame.size.height = 30; frame.size.width = 30; self = [super initWithFrame:frame]; if (self) { self.imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 30, 30)]; [self addSubview:self.imageView]; } return self;} -(void) setFaceBlock:(FaceBlock)block{ self.block = block;}-(void) setImage:(UIImage *)image ImageText:(NSString *)text{ //显示图片 [self.imageView setImage:image];//把图片存储起来 self.headerImage = image;self.imageText = text;} //点击时回调-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{ UITouch *touch = [touches anyObject]; CGPoint point = [touch locationInView:self]; //判断触摸的结束点是否在图片中 if (CGRectContainsPoint(self.bounds, point)) { //回调,把该头像的信息传到相应的controller中 self.block(self.headerImage, self.imageText); }} @end代码说明:
// FunctionView.h// MyKeyBoard//// Created by 青玉伏案 on 14-9-16.// Copyright (c) 2014年 Mrli. All rights reserved.// #import //定义对应的block类型,用于数据的交互typedef void (^FunctionBlock) (UIImage *image, NSString *imageText); @interface FunctionView : UIView//资源文件名@property (nonatomic, strong) NSString *plistFileName;//接受block块-(void)setFunctionBlock:(FunctionBlock) block; @endFunctionView.m中的代码如下,常用表情是在sqlite中获取的,而全部表情是通过plist文件的信息在Face文件中加载的:
// FunctionView.m// MyKeyBoard//// Created by 青玉伏案 on 14-9-16.// Copyright (c) 2014年 Mrli. All rights reserved.// #import "FunctionView.h"#import "FaceView.h"#import "ImageModelClass.h"#import "HistoryImage.h" @interface FunctionView() @property (strong, nonatomic) FunctionBlock block;//暂存表情组件回调的表情和表情文字@property (strong, nonatomic) UIImage *headerImage;@property (strong, nonatomic) NSString *imageText; //display我们的表情图片@property (strong, nonatomic) UIScrollView *headerScrollView; //定义数据模型用于获取历史表情@property (strong, nonatomic) ImageModelClass *imageModel; @end@implementation FunctionView- (id)initWithFrame:(CGRect)frame{ self = [super initWithFrame:frame]; if (self) {//实例化数据模型 self.imageModel =[[ImageModelClass alloc] init];//实例化下面的button UIButton *faceButton = [[UIButton alloc] initWithFrame:CGRectZero]; faceButton.backgroundColor = [UIColor grayColor];[faceButton setTitle:@"全部表情" forState:UIControlStateNormal]; [faceButton setShowsTouchWhenHighlighted:YES]; [faceButton addTarget:self action:@selector(tapButton1:) forControlEvents:UIControlEventTouchUpInside]; [self addSubview:faceButton]; //实例化常用表情按钮 UIButton *moreButton = [[UIButton alloc] initWithFrame:CGRectZero]; moreButton.backgroundColor = [UIColor orangeColor]; [moreButton setTitle:@"常用表情" forState:UIControlStateNormal]; [moreButton setShowsTouchWhenHighlighted:YES]; [moreButton addTarget:self action:@selector(tapButton2:) forControlEvents:UIControlEventTouchUpInside]; [self addSubview:moreButton];//给按钮添加约束 faceButton.translatesAutoresizingMaskIntoConstraints = NO; moreButton.translatesAutoresizingMaskIntoConstraints = NO; //水平约束 NSArray *buttonH = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[faceButton][moreButton(==faceButton)]|" options:0 metrics:0 views:NSDictionaryOfVariableBindings(faceButton,moreButton)]; [self addConstraints:buttonH];//垂直约束 NSArray *button1V = [NSLayoutConstraint constraintsWithVisualFormat:@"V:[faceButton(44)]|" options:0 metrics:0 views:NSDictionaryOfVariableBindings(faceButton)]; [self addConstraints:button1V];NSArray *button2V = [NSLayoutConstraint constraintsWithVisualFormat:@"V:[moreButton(44)]|" options:0 metrics:0 views:NSDictionaryOfVariableBindings(moreButton)]; [self addConstraints:button2V];//默认显示表情图片 [self tapButton1:nil];} return self;} //接受回调-(void)setFunctionBlock:(FunctionBlock)block{ self.block = block;} //点击全部表情按钮回调方法-(void)tapButton1: (id) sender{ // 从plist文件载入资源 NSBundle *bundle = [NSBundle mainBundle]; NSString *path = [bundle pathForResource:self.plistFileName ofType:@"plist"]; NSArray *headers = [NSArray arrayWithContentsOfFile:path];if (headers.count == 0) { NSLog(@"访问的plist文件不存在"); } else { //调用headers方法显示表情 [self header:headers]; }} //点击历史表情的回调方法-(void) tapButton2: (id) sender{ //从数据库中查询所有的图片 NSArray *imageData = [self.imageModel queryAll]; //解析请求到的数据 NSMutableArray *headers = [NSMutableArray arrayWithCapacity:imageData.count];//数据实体,相当于javaBean的东西 HistoryImage *tempData;for (int i = 0; i < imageData.count; i ++) { tempData = imageData[i];//解析数据,转换成函数headers要用的数据格式 NSMutableDictionary *dic = [NSMutableDictionary dictionaryWithCapacity:2]; [dic setObject:tempData.imageText forKey:@"chs"]; UIImage *image = [UIImage imageWithData:tempData.headerImage]; [dic setObject:image forKey:@"png"];[headers addObject:dic]; }[self header:headers]; }//负责把查出来的图片显示-(void) header:(NSArray *)headers{ [self.headerScrollView removeFromSuperview]; self.headerScrollView = [[UIScrollView alloc] initWithFrame:CGRectZero]; [self addSubview:self.headerScrollView];//给scrollView添加约束 self.headerScrollView.translatesAutoresizingMaskIntoConstraints = NO; //水平约束 NSArray *scrollH = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|-10-[_headerScrollView]-10-|" options:0 metrics:0 views:NSDictionaryOfVariableBindings(_headerScrollView)]; [self addConstraints:scrollH];//垂直约束 NSArray *scrolV = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|-10-[_headerScrollView]-50-|" options:0 metrics:0 views:NSDictionaryOfVariableBindings(_headerScrollView)]; [self addConstraints:scrolV];CGFloat scrollHeight = (self.frame).size.height-60;//根据图片量来计算scrollView的Contain的宽度 CGFloat width = (headers.count/(scrollHeight/30))*30; self.headerScrollView.contentSize = CGSizeMake(width, scrollHeight); self.headerScrollView.pagingEnabled = YES; //图片坐标 CGFloat x = 0; CGFloat y = 0;//往scroll上贴图片 for (int i = 0; i < headers.count; i ++) { //获取图片信息 UIImage *image; if ([headers[i][@"png"] isKindOfClass:[NSString class]]) { image = [UIImage imageNamed:headers[i][@"png"]]; } else { image = headers[i][@"png"]; }NSString *imageText = headers[i][@"chs"];//计算图片位置 y = (i%(int)(scrollHeight/30)) * 30; x = (i/(int)(scrollHeight/30)) * 30;FaceView *face = [[FaceView alloc] initWithFrame:CGRectMake(x, y, 0, 0)]; [face setImage:image ImageText:imageText];//face的回调,当face点击时获取face的图片 __weak __block FunctionView *copy_self = self; [face setFaceBlock:^(UIImage *image, NSString *imageText) { copy_self.block(image, imageText); }];[self.headerScrollView addSubview:face]; }[self.headerScrollView setNeedsDisplay]; } @end代码说明:
// ToolView.h// MyKeyBoard//// Created by 青玉伏案 on 14-9-16.// Copyright (c) 2014年 Mrli. All rights reserved.// /***************** 封装下面的工具条组件 *****************/#import //定义block块变量类型,用于回调,把本View上的按钮的index传到Controller中typedef void (^ToolIndex) (NSInteger index); @interface ToolView : UIView //块变量类型的setter方法-(void)setToolIndex:(ToolIndex) toolBlock; @endToolView.m的代码实现:// ToolView.m// MyKeyBoard//// Created by 青玉伏案 on 14-9-16.// Copyright (c) 2014年 Mrli. All rights reserved.// #import "ToolView.h" @interface ToolView () //定义ToolIndex类型的block,用于接受外界传过来的block@property (nonatomic, strong) ToolIndex myBlock; @end@implementation ToolView - (id)initWithFrame:(CGRect)frame{ self = [super initWithFrame:frame]; if (self) {//1初始化表情按钮 UIButton *faceButton = [[UIButton alloc] initWithFrame:CGRectZero]; faceButton.backgroundColor = [UIColor orangeColor]; [faceButton setTitle:@"表情" forState:UIControlStateNormal]; [faceButton setShowsTouchWhenHighlighted:YES]; [faceButton addTarget:self action:@selector(tapFaceButton:) forControlEvents:UIControlEventTouchUpInside]; [self addSubview:faceButton]; //初始化更多按钮 UIButton *moreButton = [[UIButton alloc] initWithFrame:CGRectZero]; moreButton.backgroundColor = [UIColor grayColor]; [moreButton setTitle:@"More" forState:UIControlStateNormal]; [moreButton setShowsTouchWhenHighlighted:YES]; [moreButton addTarget:self action:@selector(tapMoreButton:) forControlEvents:UIControlEventTouchUpInside]; [self addSubview:moreButton]; //给我们的按钮添加约束来让按钮来占满toolView; faceButton.translatesAutoresizingMaskIntoConstraints = NO; moreButton.translatesAutoresizingMaskIntoConstraints = NO;//添加水平约束 NSArray *buttonH = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[faceButton][moreButton(==faceButton)]|" options:0 metrics:0 views:NSDictionaryOfVariableBindings(faceButton,moreButton)]; [self addConstraints:buttonH];//添加垂直约束 NSArray *button1V = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|[faceButton]|" options:0 metrics:0 views:NSDictionaryOfVariableBindings(faceButton)]; [self addConstraints:button1V];NSArray *button2V = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|[moreButton]|" options:0 metrics:0 views:NSDictionaryOfVariableBindings(moreButton)]; [self addConstraints:button2V];} return self;} //接受传入的回调-(void) setToolIndex:(ToolIndex)toolBlock{ self.myBlock = toolBlock;} //点击表情按钮要回调的方法-(void) tapFaceButton: (id) sender{ self.myBlock(1);} //点击more要回调的方法-(void) tapMoreButton: (id) sender{ self.myBlock(2);} @end代码说明:
二. Mode部分的内容
1.先定义我们要使用的数据模型,数据模型如下,time是使用表情的时间,用于排序。
2.下面编写我们的ImageModelClass类,里面封装了我们操作数据要用的方法
ImageModelClass.h的代码如下,主要是预留的对外的接口:
//// ImageModelClass.h// MyKeyBoard//// Created by 青玉伏案 on 14-9-16.// Copyright (c) 2014年 Mrli. All rights reserved.// #import #import #import "HistoryImage.h" @interface ImageModelClass : NSObject//保存数据-(void)save:(NSData *) image ImageText:(NSString *) imageText;//查询所有的图片-(NSArray *) queryAll;@endImageModelClass.m的代码如下,主要是用CoreData对sqlite的操作:
// ImageModelClass.m// MyKeyBoard//// Created by 青玉伏案 on 14-9-16.// Copyright (c) 2014年 Mrli. All rights reserved.// #import "ImageModelClass.h" @interface ImageModelClass () @property (nonatomic, strong) NSManagedObjectContext *manager; @end @implementation ImageModelClass- (instancetype)init{ self = [super init]; if (self) { //通过上下文获取manager UIApplication *application = [UIApplication sharedApplication]; id delegate = application.delegate; self.manager = [delegate managedObjectContext]; } return self;} -(void)save:(NSData *)image ImageText:(NSString *)imageText{ if (image != nil) { NSArray *result = [self search:imageText];HistoryImage *myImage;if (result.count == 0) { myImage = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([HistoryImage class]) inManagedObjectContext:self.manager]; myImage.imageText = imageText; myImage.headerImage = image; myImage.time = [NSDate date]; } else { myImage = result[0]; myImage.time = [NSDate date]; }//存储实体 NSError *error = nil; if (![self.manager save:&error]) { NSLog(@"保存出错%@", [error localizedDescription]); }} }//查找-(NSArray *)search:(NSString *) image{ NSArray *result;//新建查询条件 NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:NSStringFromClass([HistoryImage class])];//添加谓词 NSPredicate *predicate = [NSPredicate predicateWithFormat:@"imageText=%@",image];//把谓词给request [fetchRequest setPredicate:predicate];//执行查询 NSError *error = nil; result = [self.manager executeFetchRequest:fetchRequest error:&error]; if (error) { NSLog(@"查询错误:%@", [error localizedDescription]); } return result;} //查询所有的-(NSArray *) queryAll{ //新建查询条件 NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:NSStringFromClass([HistoryImage class])];//添加排序规则 //定义排序规则 NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"time" ascending:NO];//添加排序规则 [fetchRequest setSortDescriptors:@[sortDescriptor]]; //执行查询 NSError *error = nil; NSArray *result = [self.manager executeFetchRequest:fetchRequest error:&error]; if (error) { NSLog(@"查询错误:%@", [error localizedDescription]); }return result;} @end代码说明:
@interface MainViewController () //自定义组件@property (nonatomic, strong) ToolView *toolView; @property (nonatomic, strong) FunctionView *functionView; @property (nonatomic, strong) MoreView *moreView; //系统组件@property (strong, nonatomic) IBOutlet UITextView *myTextView; @property (strong, nonatomic) NSDictionary *keyBoardDic; @property (strong, nonatomic) IBOutlet UIImageView *imageView; @property (strong, nonatomic) NSString *sendString; //数据model@property (strong, nonatomic) ImageModelClass *imageMode; @property (strong, nonatomic)HistoryImage *tempImage; @end2.在viewDidLoad中进行组件的初始化和实现组件的Block回调,代码如下
- (void)viewDidLoad{ [super viewDidLoad];//从sqlite中读取数据 self.imageMode = [[ImageModelClass alloc] init]; //实例化FunctionView self.functionView = [[FunctionView alloc] initWithFrame:CGRectMake(0, 0, 320, 216)]; self.functionView.backgroundColor = [UIColor blackColor];//设置资源加载的文件名 self.functionView.plistFileName = @"emoticons";__weak __block MainViewController *copy_self = self; //获取图片并显示 [self.functionView setFunctionBlock:^(UIImage *image, NSString *imageText) { NSString *str = [NSString stringWithFormat:@"%@%@",copy_self.myTextView.text, imageText];copy_self.myTextView.text = str; copy_self.imageView.image = image;//把使用过的图片存入sqlite NSData *imageData = UIImagePNGRepresentation(image); [copy_self.imageMode save:imageData ImageText:imageText]; }]; //实例化MoreView self.moreView = [[MoreView alloc] initWithFrame:CGRectMake(0, 0, 0, 0)]; self.moreView.backgroundColor = [UIColor blackColor]; [self.moreView setMoreBlock:^(NSInteger index) { NSLog(@"MoreIndex = %d",index); }];//进行ToolView的实例化 self.toolView = [[ToolView alloc] initWithFrame:CGRectZero]; self.toolView.backgroundColor = [UIColor blackColor]; [self.view addSubview:self.toolView];//给ToolView添加约束 //开启自动布局 self.toolView.translatesAutoresizingMaskIntoConstraints = NO;//水平约束 NSArray *toolHConstraint = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[_toolView]|" options:0 metrics:0 views:NSDictionaryOfVariableBindings(_toolView)]; [self.view addConstraints:toolHConstraint];//垂直约束 NSArray *toolVConstraint = [NSLayoutConstraint constraintsWithVisualFormat:@"V:[_toolView(44)]|" options:0 metrics:0 views:NSDictionaryOfVariableBindings(_toolView)]; [self.view addConstraints:toolVConstraint]; //回调toolView中的方法 [self.toolView setToolIndex:^(NSInteger index) { NSLog(@"%d", index);switch (index) { case 1: [copy_self changeKeyboardToFunction]; break; case 2: [copy_self changeKeyboardToMore]; break; default: break; }}];//当键盘出来的时候通过通知来获取键盘的信息 //注册为键盘的监听着 NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; [center addObserver:self selector:@selector(keyNotification:) name:UIKeyboardWillChangeFrameNotification object:nil]; //给键盘添加dan //TextView的键盘定制回收按钮 UIToolbar * toolBar = [[UIToolbar alloc]initWithFrame:CGRectMake(0, 0, 320, 30)];UIBarButtonItem * item1 = [[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(tapDone:)]; UIBarButtonItem * item2 = [[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil]; UIBarButtonItem * item3 = [[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil]; toolBar.items = @[item2,item1,item3];self.myTextView.inputAccessoryView =toolBar; }3.当横竖屏幕切换时设置自定义键盘的高度
-(void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration{ //纵屏 if (UIInterfaceOrientationIsPortrait(toInterfaceOrientation)) { CGRect frame = self.functionView.frame; frame.size.height = 216; self.functionView.frame = frame; self.moreView.frame = frame;} //横屏 if (UIInterfaceOrientationIsLandscape(toInterfaceOrientation)) { CGRect frame = self.functionView.frame; frame.size.height = 150; self.functionView.frame = frame; self.moreView.frame = frame; }}4.当键盘出来的时候,改变toolView的位置,通过键盘的通知来实现。当横屏的时候键盘的坐标系和我们当前的Frame的坐标系不一样所以当横屏时得做一坐标系的转换,代码如下:
//当键盘出来的时候改变toolView的位置(接到键盘出来的通知要做的方法)-(void) keyNotification : (NSNotification *) notification{ NSLog(@"%@", notification.userInfo);self.keyBoardDic = notification.userInfo; //获取键盘移动后的坐标点的坐标点 CGRect rect = [self.keyBoardDic[@"UIKeyboardFrameEndUserInfoKey"] CGRectValue];//把键盘的坐标系改成当前我们window的坐标系 CGRect r1 = [self.view convertRect:rect fromView:self.view.window];[UIView animateWithDuration:[self.keyBoardDic[UIKeyboardAnimationDurationUserInfoKey] floatValue] animations:^{//动画曲线 [UIView setAnimationCurve:[self.keyBoardDic[UIKeyboardAnimationCurveUserInfoKey] doubleValue]];CGRect frame = self.toolView.frame;frame.origin.y = r1.origin.y - frame.size.height;//根据键盘的高度来改变toolView的高度 self.toolView.frame = frame; }];}5.系统键盘和自定义键盘切换的代码如下:
//切换键盘的方法-(void) changeKeyboardToFunction{ if ([self.myTextView.inputView isEqual:self.functionView]) { self.myTextView.inputView = nil; [self.myTextView reloadInputViews]; } else { self.myTextView.inputView = self.functionView; [self.myTextView reloadInputViews]; }if (![self.myTextView isFirstResponder]) { [self.myTextView becomeFirstResponder]; }}以上就是上面展示效果的核心代码了,在做的时候感觉难点在于如何进行屏幕适配,尤其是当屏幕横过来的时候键盘的坐标系和我们frame的坐标系不同,得做 一个转换。发表文章的目的是想起到抛砖引玉的左右,有好的东西希望大家相互交流一下。