前言:开发一套自界说心情包需求,雷同于小红书的心情键盘,技能点其着实体系键盘和心情键盘的切换、焦点是富文本的处理处罚,包罗文本转心情([哈哈]=>?)和心情转文本(?=>[哈哈])细节许多,坑大概多,工具类实现文件在末了,文中用到了可以查阅
分析下整个需求点和开发过程:
1、心情包JSON数据格式规范
2、服务端下发心情JSON文件,对于心情库的存取利用
3、页面展示文本转化
4、心情键盘以及键盘切换
5、迩来利用心情逻辑开发
6、键盘上心情点击,长按展示浮窗动图、删除键功能
7、复制粘贴功能(包罗粘贴时长度大于字数限定时截取处理处罚)
8、输入框高度有高度限定时的动态变革逻辑
9、输入框输入大概粘贴的时间,上下抖动问题的处理处罚
10、上传服务器
1、心情包JSON数据格式规范
首先我们与服务端要定好心情JSON数据的格式,
{ "emojis": { "[握手]": { "code": "1514202019436744729", "name": "[握手]", "url": "https:\/\/moment-image.oss-cn-hangzhou.aliyuncs.com\/20220413\/1514202017163431937.png", "originUrl": "https:\/\/moment-image.oss-cn-hangzhou.aliyuncs.com\/20220413\/1514202017058574338_B.png" }, "[高兴]": { "code": "1514202019436744732", "name": "[高兴]", "url": "https:\/\/moment-image.oss-cn-hangzhou.aliyuncs.com\/20220413\/1514202015296966658.png", "originUrl": "https:\/\/moment-image.oss-cn-hangzhou.aliyuncs.com\/20220413\/1514202015192109058_B.png" }, "[堕泪]": { "code": "1514202019436744723", "name": "[堕泪]", "url": "https:\/\/moment-image.oss-cn-hangzhou.aliyuncs.com\/20220413\/1514202015972249602.png", "originUrl": "https:\/\/moment-image.oss-cn-hangzhou.aliyuncs.com\/20220413\/1514202015842226177_B.png" }, "[sayhi]": { "code": "1514202019436744725", "name": "[sayhi]", "url": "https:\/\/moment-image.oss-cn-hangzhou.aliyuncs.com\/20220413\/1514202019264778242.png", "originUrl": "https:\/\/moment-image.oss-cn-hangzhou.aliyuncs.com\/20220413\/1514202019143143425_B.png" }, }, "emojiNames": ["[sayhi]", "[握手]", "[堕泪]", "[高兴]"], "emojiVersion":1}注意看,我们定的数据有三个字段,
emojis:是个字典情势,方便我们后续直接通过key来匹配对应的心情模子,而不是数组每次都要循环匹配
emojiNames:由于后台给的字典我们拿到的大概就是 无序 的,也就是说不能包管次序是对的,以是我们必要一个准确的次序,用来在心情键盘中准确的排序
emojiVersion:当运营没有对心情库做任那边理处罚的时间,服务端不必要每次都下发JSON文件给我们,以是emojiVersion我们要生存的当地,每次进入APP调用接口的时间,作为参数给服务器,服务器会判定是否传JSON给我们。
这里必要注意的是,假如通过 [[NSUserDefaults standardUserDefaults] setInteger:[dictionary[@"emojiVersion"] intValue] forKey:kMomentsEmojiVersion];这种方式当地生存的话,背面跟上[[NSUserDefaults standardUserDefaults] synchronize];如许会实时同步,制止短时间再次哀求接口的时间还没有生存起来,造成不须要的贫困
2、服务端下发心情JSON文件,对于心情库的存取利用
存取利用的逻辑是包罗以下几种情况:
1、第一次打开APP,此时 emojiVersion 传值为空,哀求心情接口 假如哀求失败:那么就从当地的JSON文件中拿数据(最初当地会放一份JSON文件做兜底方案)。 假如哀求乐成:那么就把哀求下来的JSON文件,生存到当地,版本号别忘了也要存起来2、假如当地已经存储过之前哀求下来的数据了,那么随后进入APP的时间哀求心情接口 假如哀求失败:拿前次存储下来的JSON文件利用 假如哀求乐成:①库没有变革,服务端没有下发JSON文件,那么还是拿前次存储下来的JSON文件利用。②库变革了,服务端新下发了JSON文件,那么就把原来存储的给更换掉,版本号别忘了也要存起来接下来是代码部分:
//接口哀求部分:JYMomentsEmojiManager 是我写的一个单例工具类,关于心情的一些利用都在这个类里完成//心情包下载- (void)configEmojiData{ NSInteger version = [[NSUserDefaults standardUserDefaults] integerForKey:kMomentsEmojiVersion]; NSDictionary *paramsDict = @{ @"action""******", @"emojiVersion"(version) }; [[JYNetworkManager shareManager] momentsPostApiWithParas:paramsDict success:^(NSDictionary *responseData) { if ([responseData[@"code"] integerValue] == kRequestSuccessCode && responseData[@"data"][@"emojis"]) { //使单利内可以获取到心情json,以便于其他方法匹配 [[JYMomentsEmojiManager sharedInstance] saveEmojiDataWithDictionary:responseData[@"data"]]; }else{ //使单例内可以获取到心情json,以便于其他方法匹配 [[JYMomentsEmojiManager sharedInstance] fetchAllEmojiDictionary]; } } failure:^(NSError *error) { //使单例内可以获取到心情json,以便于其他方法匹配 [[JYMomentsEmojiManager sharedInstance] fetchAllEmojiDictionary]; }];}接下来看看[[JYMomentsEmojiManager sharedInstance] fetchAllEmojiDictionary];做了哪些逻辑
- (NSDictionary *)fetchAllEmojiDictionary{//首先从接口哀求数据的当地存储路径中查找,是否存储过文件,假如有,则从这里拿,假如没有,说明从前还没有哀求接口乐成过,还没有存储到当地数据,这时就用我们自己在项目中放的一份JSON文件 NSFileManager *fileManager = [NSFileManager defaultManager]; NSString *filepath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0]; NSString *newPath = [filepath stringByAppendingPathComponent:storeEmojiPath]; if ([fileManager fileExistsAtPath:newPath]) { NSData *data = [NSData dataWithContentsOfFile:newPath]; NSDictionary *dic = [NSKeyedUnarchiver unarchiveObjectWithData:data]; if (dic && dic.allKeys.count > 0) { self.allEmojiDictionary = dic; return dic; } } //用我们自己在项目中放的一份JSON文件 [self useLocalEmojiData]; return self.allEmojiDictionary;}//利用当地JSON文件- (void)useLocalEmojiData{//这里是组建化的写法,独立项目直接就是[[NSBundle mainBundle] ...],这里是获取项目中JSON文件的路径,要跟存储接口哀求下来的数据路径区分开 NSString *path = [JYMomentsBundle() pathForResource:storeLocalEmojiPath ofType"json"]; NSData *jsonData = [[NSData alloc] initWithContentsOfFile:path]; NSError *error; NSDictionary *jsonObj = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:&error]; if (jsonObj && !error) { self.emojiNames = jsonObj[@"emojiNames"]; [[NSUserDefaults standardUserDefaults] setObject:self.emojiNames forKey:kMomentsEmojiNamesArray]; [[NSUserDefaults standardUserDefaults] synchronize]; self.allEmojiDictionary = [self dictionaryToModelWithDictionary:jsonObj[@"emojis"]]; }}//字典中套的字典转模子,方便后续开发中利用- (NSDictionary *)dictionaryToModelWithDictionaryNSDictionary *)dictionary{ NSMutableDictionary *dict = [NSMutableDictionary dictionary]; for (NSString *key in dictionary.allKeys) { if (key.length > 0 && ![dict.allKeys containsObject:key]) { JYMomentsEmojiModel *model = [JYMomentsEmojiModel mj_objectWithKeyValues:dictionary[key]]; [dict setObject:model forKey:key]; } } return [dict copy];}以上是走失败的方案大概心情库没有更新的逻辑,下面是库更新了,我们更换JSON文件的代码
//存储心情包数据文件- (void)saveEmojiDataWithDictionaryNSDictionary *)dictionary{ NSFileManager *fileManager = [NSFileManager defaultManager]; NSString *filepath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0]; NSString *newPath = [filepath stringByAppendingPathComponent:storeEmojiPath]; if ([fileManager fileExistsAtPath:newPath]) { [fileManager removeItemAtPath:newPath error:nil]; } //存储数据,这里也用到了把字典转model举行存储 self.allEmojiDictionary = [self dictionaryToModelWithDictionary:dictionary[@"emojis"]]; self.emojiNames = dictionary[@"emojiNames"]; //存储心情名字数组 [[NSUserDefaults standardUserDefaults] setObject:self.emojiNames forKey:kMomentsEmojiNamesArray]; [[NSUserDefaults standardUserDefaults] synchronize]; //存储 NSData *data = [NSKeyedArchiver archivedDataWithRootObject:self.allEmojiDictionary]; [data writeToFile:newPath atomically:YES]; //存储版本号 [[NSUserDefaults standardUserDefaults] setInteger:[dictionary[@"emojiVersion"] intValue] forKey:kMomentsEmojiVersion]; [[NSUserDefaults standardUserDefaults] synchronize];}以上就是心情库数据的存储读取逻辑处理处罚
3、页面展示文本转化
注意:我们跟服务端是用 [高兴] 情势交互的,我们给服务端也是这种情势,服务端给我们也是这种,心情的展示完满是我们移动端自己处理处罚的逻辑
对于页面展示文本内容中有心情,比方:今天我很[高兴],我们要做转换处理处罚
//利用工具类里的方法://心情字符串原文本转心情文本 (比方:[OK]了吗 => 了吗) pageid是为了埋点- (NSAttributedString *)fetchEmojiContentWithEmojiStringNSString *)emojiString isWebpBOOL)isWebp fontUIFont *)font pageIdNSString *)pageId;参数表明:emojiString:[OK]了吗 (整个必要转义的文本都放进来)size:心情(实在就是imageview)展示的尺寸大小isWebp:这里我们内部做了是否转换成webp格式的利用,由于webp格式图片会更小,展示更流通font:心情转义后字体大小,跟自己控件中的其他平常文本字体雷同就OKpageId:为了埋点,由于全部页面都会走这个方法转义,要判定是哪个页面看一下这个方法的实现:
//字符串文本 转 心情文本- (NSAttributedString *)fetchEmojiContentWithEmojiStringNSString *)emojiString isWebpBOOL)isWebp fontUIFont *)font pageIdNSString *)pageId{ //通过正则表达式获取到匹配到的range NSArray *matchArray = [self fetchRangeWithContentString:emojiString]; //用来存放字典,字典中存储的是图片和图片对应的位置 NSMutableArray *imageArray = [NSMutableArray arrayWithCapacity:matchArray.count]; //根据匹配范围来用图片举行相应的更换 for(NSTextCheckingResult *match in matchArray) { //获取数组元素中得到range NSRange range = [match range]; //获取原字符串中对应的值 NSString *subStr = [emojiString substringWithRange:range]; //把图片和图片对应的位置存入字典中 NSMutableDictionary *imageDic = [NSMutableDictionary dictionaryWithCapacity:matchArray.count]; [imageDic setObject:subStr forKey"emojiStr"]; [imageDic setObject:[NSValue valueWithRange:range] forKey"range"]; //把字典存入数组中 [imageArray addObject:imageDic]; } return [self fetchEmojiContentWithString:emojiString imageDataArr:imageArray size:size isWebp:isWebp font:font pageId:pageId];}//通过正则表达式匹配到范围- (NSArray *)fetchRangeWithContentString:(NSString *)string{ //正则表达式 NSString * pattern = @"(\\[).*?(\\])"; NSError *error = nil; NSRegularExpression *re = [[NSRegularExpression alloc] initWithPattern:pattern options:0 error:&error]; if (!re) { NSLog(@"%@", [error localizedDescription]); } //通过正则表达式来匹配字符串 return [re matchesInString:string options:0 range:NSMakeRange(0, string.length)];}//从后往前更换字符串- (NSAttributedString *)fetchEmojiContentWithString:(NSString *)content imageDataArr:(NSArray *)imageDataArr size:(CGSize)size isWebp:(BOOL)isWebp font:(UIFont *)font pageId:(NSString *)pageId{ NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:content]; for (long int i = (imageDataArr.count - 1); i >= 0; i --) { NSDictionary *dictionary = [imageDataArr jy_objectAtIndex:i]; NSString *subStr = dictionary[@"emojiStr"]; NSRange range; [dictionary[@"range"] getValue:&range]; if ([self.allEmojiDictionary.allKeys containsObject:subStr]) { JYMomentsEmojiModel *model = self.allEmojiDictionary[subStr]; //这里的尺寸是转webp所必要的尺寸,跟外貌传的尺寸差别 //注意:URL中大概有中文,如许的话图片会失败,必要转一下 NSURL *url = [NSURL URLWithString:[[model.url fetchThumbImageWidth:54 andHeight:54 isWebp:isWebp] stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]]]; UIImageView *insertImageView = [[UIImageView alloc] init]; insertImageView.contentMode = UIViewContentModeScaleAspectFit; insertImageView.tag = [model.code integerValue]; insertImageView.bounds = CGRectMake(0, 0, size.width, size.height); [insertImageView sd_setImageWithURL:url completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) { }]; NSMutableAttributedString *imageStr = [NSMutableAttributedString yy_attachmentStringWithContent:insertImageView contentMode:UIViewContentModeScaleToFill attachmentSize:CGSizeMake(size.width, size.height) alignToFont:font alignment:YYTextVerticalAlignmentCenter]; //举行更换 [attributedString jy_replaceCharactersInRange:range withAttributedString:imageStr]; if (pageId.length > 0) { //埋点利用 } } } return attributedString;}重点注意:转义成富文本插入的时间实在是一个Unicode码占位符\U0000fffc 是1个长度的字符
也就是说 [高兴] 转 实在是4个字符串长度转成1个字符串长度,以是在转的时间会导致整个文本的长度变的,变的话正则匹配到的range也就对不上了,以是更换的时间要注意是从背面往前面更换
重点注意:大概文本中有个别文案要个性化设置(字体颜色改变等),那么我们先处理处罚完我们的心情转义后再去做那些利用,防止原始字符串被粉碎,转义心情的时间受阻!!!
重点注意:富文本设值,就不要用text了,用attributeString设值。一样平常用的是YYLabel而不用UILabel,UILabel展示不出来。输入框编辑可以用YYTextView
4、心情键盘以及键盘切换
看看心情键盘,实在就知道是用UICollectionview做的,有迩来利用心情就用两个区,这个应该不难,有肯定开发根本的都能实现
必要注意的是:单独创建一个UIView(下文成为emojiKeyboardView),UICollectionview作为子视图,由于心情键盘上另有一个删除按钮是作为UIView的子视图固定在右下角呢。
切换心情键盘和体系键盘的方法很简单:我们是把emojiKeyboardView作为textview的inputView,如许的话实在emojiKeyboardView就成了键盘
切换的时间分三步:
1、必要先接纳键盘
2、然后inputview转换成对应的view(心情键盘:emojiKeyboardView!体系键盘:nil)
3、唤起键盘(textView becomeFirstResponder)
代码如下:
//内里一些变量标识可先忽略,关注重要逻辑- (void)changeKeyboardType:(BOOL)isFaceKeyboard{ self.isClickFaceButton = YES;//第一步 [self.tagTextView endEditing:YES]; if (isFaceKeyboard) { self.isFaceKeyboard = YES;//第二步 self.tagTextView.inputView = self.emojiKeyboardView; }else{ self.isFaceKeyboard = NO;//第二步 self.tagTextView.inputView = nil; }//第三步 延时处理处罚是由于,不延时的话,键盘上面的输入框编辑那一栏会抖动,缘故原由不详,延时是能办理问题的 @weakify(self); dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ @strongify(self); [self.tagTextView becomeFirstResponder]; });}注意:self.emojiKeyboardView 心情键盘这个view,高度是自己可以控制的,假如你设置成600那么切换键盘的时间,体系键盘的高度和心情键盘的高度就不一样了,最好是高度和体系键盘的高度一致,那么在切换键盘的时间,顶部的输入框编辑栏就不会上下乱跳,(固然小红书的心情键盘高度是比着体系键盘很低,大概就是固定死高度)不管你体系键盘高度多少(体系键盘和搜狗键盘高度还不一样),心情键盘不绝稳固。如许逻辑上就比力简单,
假如想要做成跟体系键盘高度一样,做法是监听体系键盘弹起和收起的方法,获取到键盘高度,然后赋值给心情键盘
注意注意注意:假如你心情键盘初始化的时间高度设置是300,体系键盘比如说是400,那么在切换键盘的时间,监听的键盘弹起接纳方法中获取的高度也是会变的,也就是说你酿成心情键盘的时间,你这个心情面板view就是键盘,以是要想设置心情面板高度成为体系键盘高度,就要在成为体系键盘的时间纪录下体系键盘的高度,然后在切换的时间,重新设置一下心情面板的高度,这个机会就是体系键盘接纳的时间,这时间我们拿着体系键盘的高度,重新初始化一下我们的emojiKeyboardView,高度设置成体系键盘以后,我们在切换成心情键盘,弹起的时间高度就是准确的高度了。
代码如下:
监听键盘的两个关照 [[NSNotificationCenter defaultCenter] addObserver:self selectorselector(keyboardWillShow name:UIKeyboardWillShowNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selectorselector(keyboardWillhide name:UIKeyboardWillHideNotification object:nil];- (void)keyboardWillShow:(NSNotification *)notification{ NSDictionary *userInfo = notification.userInfo; double duration = [userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue]; CGRect keyboardF = [userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue]; CGFloat keyboardH = keyboardF.size.height; [UIView animateWithDuration:duration animations:^{ self.bottomEditView.height = 53 + keyboardH; self.bottomEditView.bottom = self.view.bottom; } completion:^(BOOL finished) { }];}- (void)keyboardWillhide:(NSNotification *)notification{ NSDictionary *userInfo = notification.userInfo; double duration = [userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue]; CGRect keyboardF = [userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue]; CGFloat keyboardH = keyboardF.size.height; if (!self.isFaceKeyboard) { [self initFacekeyboardWithHeight:keyboardH]; } [UIView animateWithDuration:duration animations:^{ if (!self.isClickFaceButton) { self.bottomEditView.bottom = self.view.bottom+keyboardH+53; } } completion:^(BOOL finished) { self.isClickFaceButton = NO; }];}- (void)initFacekeyboardWithHeight:(CGFloat)viewHeight{ if (self.emojiKeyboardView) { [self.emojiKeyboardView removeFromSuperview]; self.emojiKeyboardView = nil; } self.emojiKeyboardView = [[JYMomentsEmojiKeyboardView alloc] initWithFrame:CGRectMake(0, self.view.height - [UIDevice safeAreaInsetsBottomHeight], kScreenWidth, viewHeight) pageType:JYUseKeyBoardPageTypePublishFeed]; self.emojiKeyboardView.delegate = self;}注意注意注意:键盘上的编辑栏,包管视觉上编辑栏底部挨着键盘顶部就可以了,假如如许的话就要在键盘底部单独再加一个遮罩,笔者这里是直接把编辑栏做成高度是键盘高度+视觉的编辑栏高度(由于在切换键盘的时间键盘接纳弹起过程中,假如下面没有遮罩,编辑栏又要停留在原处的时间,会袒露底部控制器的试图),不管怎么做,在切换键盘的时间不要去掉这个遮罩,等真正终极接纳键盘的时间肯定要注意这个遮罩的处理处罚,要否则就遮罩停留在那边,就成了bug
注意注意注意:有个问题大概会被忽略,就是键盘弹起状态时,假如体系截图,会出现”寻求资助“悬浮按钮,点击这个按钮,再次返来的时间,就会出现键盘接纳了遮罩还在的情况。对于这种键盘非常接纳的情况,办理方法是viewWillAppear中设置[textview becomeFirstResponder]
5、迩来利用心情逻辑开发
可以看出迩来利用心情是体系键盘弹出时带的,
实现的方式有两种:
1、作为独立的自界说随着体系键盘一起弹起打消,要做一些判定是否打消弹起的逻辑
2、作为键盘的辅助试图,也就是TextView.inputAccessoryView,如许的话在切换键盘的时间只要设置TextView.inputAccessoryView为nil大概自界说view即可,假如不遇到奇葩问题的话,这种方式会少许多判定逻辑
自界说迩来利用心情面板
需求是固定7个心情匀称分摊屏幕宽度,为了扩展性,利用UIview上面加一个UIScrollview,利用for循环添加UIImageView,由于UIImageView必要做点击大概长按利用,必要关联着心情model,以是我们自界说一个UIImageView,添加属性心情model和一个位置属性bigImageViewMinX(用于长按的时间预览试图添加位置定位)
预览试图也是自界说一个UIview,子视图是一个底图容器UIImageView和大心情容器UIImageView以及一个心情名字label
//自界说图片NS_ASSUME_NONNULL_BEGIN@interface JYMomentsEmojiDiyImageView : UIImageView@property (nonatomic,strong) JYMomentsEmojiModel *model;@property (nonatomic,assign) CGFloat bigImageViewMinX;@endNS_ASSUME_NONNULL_END//自界说迩来利用心情面板 .h文件NS_ASSUME_NONNULL_BEGIN@interface JYMomentsEmojiRecentlyUseView : UIView///心情点击利用@property (nonatomic,copy) void(^tapAction)(JYMomentsEmojiModel *model);//刷新迩来利用心情列表- (void)updateUI;@endNS_ASSUME_NONNULL_END//自界说迩来利用心情面板 .m文件@interface JYMomentsEmojiRecentlyUseView()@property (nonatomic,strong) UIScrollView *bgScrollView;@property (nonatomic,strong) JYMomentsEmojiBigBackGroundView *bigbgView;@end@implementation JYMomentsEmojiRecentlyUseView#pragma mark --lifeCycle- (instancetype)initWithFrame:(CGRect)frame{ if (self = [super initWithFrame:frame]) { [self setupViews]; [self updateUI]; } return self;}#pragma mark --private method- (void)setupViews{ self.backgroundColor = [UIColor colorWithHexString"FFFFFF"]; [self addSubview:self.bgScrollView]; [self.bgScrollView mas_makeConstraints:^(MASConstraintMaker *make) { make.edges.mas_equalTo(0); }];}#pragma mark --public method- (void)updateUI{ [self.bgScrollView removeAllSubviews]; NSArray *emojis = [[JYMomentsEmojiManager sharedInstance] fetchRecentlyEmojiArr]; NSMutableArray *emojisDicArray = [NSMutableArray arrayWithArray:emojis]; if (emojisDicArray.count < 7) { NSDictionary *allEmojiDic = [[JYMomentsEmojiManager sharedInstance] fetchAllEmojiDictionary]; NSArray *names = [[JYMomentsEmojiManager sharedInstance] fetchAllEmojiNamesArray]; for (NSString *name in names) { JYMomentsEmojiModel *model = allEmojiDic[name]; BOOL isHave = NO; for (JYMomentsEmojiModel *item in emojis) { if ([item.code isEqualToString:model.code]) { isHave = YES; break; } } if (!isHave) { [emojisDicArray addObject:model]; } if (emojisDicArray.count >= 7) { break; } } } for (int i = 0; i < emojisDicArray.count; i ++) { JYMomentsEmojiModel *model = [emojisDicArray jy_objectAtIndex:i]; CGFloat leftSpace = 12; //kScreenWidth - 24 - 34 * 7 CGFloat space = (kScreenWidth - 262) / 6; JYMomentsEmojiDiyImageView *diyImagView = [[JYMomentsEmojiDiyImageView alloc] initWithFrame:CGRectMake(leftSpace + (i * (34 + space)), 0, 34, 34)]; diyImagView.model = model; diyImagView.bigImageViewMinX = leftSpace + (i * (34 + space)) - 8; [diyImagView sd_setImageWithURL:[NSURL URLWithString:[model.url stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]]]]; diyImagView.userInteractionEnabled = YES; @weakify(self) //点击变乱 UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithActionBlock:^(id _Nonnull sender) { @strongify(self) if (self.tapAction) { [JYTracker clickEventTrackWithPageId"community_new_momentpage" categoryId"alpha" buttonName:@"community_HAHA_choice" businessProperties:@{@"numHaha":model.code}]; self.tapAction(model); } }]; [diyImagView addGestureRecognizer:tap]; //长按变乱 UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPressClick]; longPress.minimumPressDuration = 0.5; [diyImagView addGestureRecognizer:longPress]; [self.bgScrollView addSubview:diyImagView]; }}- (void)longPressClick:(UIGestureRecognizer *)gesture{//获取到被长按的试图,拿到试图后,通过之前纪录的bigImageViewMinX属性就可以准确的定位添加预览图了 JYMomentsEmojiDiyImageView *diyView = (JYMomentsEmojiDiyImageView *)gesture.view; [self addSubview:self.bigbgView]; [self.bigbgView configViewWithModel:diyView.model]; [self.bigbgView mas_makeConstraints:^(MASConstraintMaker *make) { make.left.equalTo(@(diyView.bigImageViewMinX)); make.bottom.equalTo(@(-12)); make.width.mas_equalTo(50); make.height.mas_equalTo(93); }]; if (gesture.state == UIGestureRecognizerStateEnded) { if (self.bigbgView.superview) { [self.bigbgView removeFromSuperview]; self.bigbgView = nil; } }}#pragma mark --lazyload- (UIScrollView *)bgScrollView{ if (!_bgScrollView) { _bgScrollView = [[UIScrollView alloc] initWithFrame:CGRectZero]; _bgScrollView.backgroundColor = [UIColor whiteColor]; _bgScrollView.contentSize = CGSizeMake(kScreenWidth, 45); } return _bgScrollView;}- (JYMomentsEmojiBigBackGroundView *)bigbgView{ if (!_bigbgView) { _bigbgView = [[JYMomentsEmojiBigBackGroundView alloc] initWithFrame:CGRectZero]; } return _bigbgView;}@end上面是UI方面的处理处罚,下面是功能逻辑的处理处罚
功能逻辑
点击心情键盘上的心情,大概迩来利用面板心情,会利用工具类中的方法先纪录到当地
//批评模块中点击心情后暂时存储迩来利用心情排序处理处罚(不存储)- (void)temporaryStorageEmoji:(JYMomentsEmojiModel *)emojiModel;等键盘终极接纳的时间,做一次当地存储
//存储迩来利用心情数组- (void)saveRecentlyEmojiArr;固然我们在构建迩来利用心情模块面板以及更新面板的时间必要获取当地最新存储的迩来利用心情数据
//获取迩来利用心情数组- (NSArray<JYMomentsEmojiModel *> *)fetchRecentlyEmojiArr;逻辑是:点击心情的时间,工具类中用一个暂时数组来纪录这些点击过的心情model,假如点击的是同一个model,就会删除原来的,然后把最新的添加到数组的第一个位置,也就是说在一个暂时存放到当地存储的的周期内,暂时数组会自己判重,没有的话添加,有的话就移动到第一位
接纳键盘利用后(切换键盘的过程接纳键盘不算,这就必要自己去判定是否是终极接纳键盘)存储暂时数组内里的model,这里还必要拿之前存储的迩来利用心情数据来和暂时数组里的model判重,终极将判重后的心情model数组存储到当地。model数组存储也必要转成NSData范例,存储后把暂时数组置空。
以上三个接口代码实现:
//批评模块中点击后暂时存储迩来利用心情- (void)temporaryStorageEmoji:(JYMomentsEmojiModel *)emojiModel{ BOOL isHave = NO; NSInteger index = 0; for (JYMomentsEmojiModel *model in self.tempArray) { //根据心情名字判定是否是同一个心情去重处理处罚 if ([emojiModel.code isEqualToString:model.code]) { isHave = YES; index = [self.tempArray indexOfObject:model]; break; } } if (!isHave) { [self.tempArray jy_insertObject:emojiModel atIndex:0]; }else{ [self.tempArray jy_removeObjectAtIndex:index]; [self.tempArray jy_insertObject:emojiModel atIndex:0]; } //截取前7个存储 if (self.tempArray.count > kMomentsEmojiRecentlyNumber) { self.tempArray = [[self.tempArray jy_subarrayWithRange:NSMakeRange(0, kMomentsEmojiRecentlyNumber)] mutableCopy]; }}//存储迩来利用心情数组- (void)saveRecentlyEmojiArr{ BOOL isHave = NO; NSInteger index = 0; NSArray *dataArr = [self fetchRecentlyEmojiArr]; NSMutableArray *arr = [NSMutableArray arrayWithArray:dataArr]; for (JYMomentsEmojiModel *item in self.tempArray) { for (JYMomentsEmojiModel *model in dataArr) { if ([item.code isEqualToString:model.code]) { isHave = YES; index = [dataArr indexOfObject:model]; break; } } if (arr.count > index && isHave) { [arr removeObjectAtIndex:index]; } } [self.tempArray addObjectsFromArray:arr]; //截取前7个存储 if (self.tempArray.count > kMomentsEmojiRecentlyNumber) { self.tempArray = [[self.tempArray jy_subarrayWithRange:NSMakeRange(0, kMomentsEmojiRecentlyNumber)] mutableCopy]; } if (self.tempArray.count > 0) { NSMutableArray *arr = [NSMutableArray array]; for (JYMomentsEmojiModel *model in self.tempArray) { NSData *data = [NSKeyedArchiver archivedDataWithRootObject:model]; [arr addObject:data]; } [[NSUserDefaults standardUserDefaults] setObject:arr forKey:kMomentsEmojiRecentlyUseArray]; [[NSUserDefaults standardUserDefaults] synchronize]; //生存完一波后清空 [self.tempArray removeAllObjects]; }}//获取迩来利用心情数组- (NSArray<JYMomentsEmojiModel *> *)fetchRecentlyEmojiArr{ NSMutableArray *arr = [NSMutableArray new]; NSArray *dataArr = [[NSUserDefaults standardUserDefaults] objectForKey:kMomentsEmojiRecentlyUseArray]; for (NSData *data in dataArr) { [arr addObject:[NSKeyedUnarchiver unarchiveObjectWithData:data]]; } return arr;}注意:当点击雷同心情的时间,判重直接通过数组中是否包罗model对象的话,大概会有问题,会鉴别成两个model,以是准确的处理处罚是拿model的唯一标识来判重,如许的话无非是在深一层去判定
6、键盘上心情点击,长按展示浮窗动图、删除键功能
点击变乱也就是心情键盘中didselect署理方法以及迩来利用心情view中的点击变乱,长按方法在上面迩来利用心情一栏中已经讲到,心情键盘中的方式也雷同,乃至更简单一点,获取到对应的位置,然后添加预览试图,下面是在心情键盘中的长按利用
- (void)longpressAction:(NSIndexPath *)indexPath{ JYMomentsDefaultEmojiCell *cell = (JYMomentsDefaultEmojiCell *)[self.keyboardView cellForItemAtIndexPath:indexPath]; JYMomentsEmojiModel *model = nil; if (indexPath.section == 0) { model = self.recentlyEmojiArr[indexPath.item]; }else{ NSString *name = self.allEmojiNamesArray[indexPath.item]; model = self.allEmojiDictionary[name]; } //长按心情利用 UIWindow *window = [[[UIApplication sharedApplication] windows] lastObject]; CGRect rect = [cell.imageView convertRect:cell.imageView.bounds toView:window]; self.bigbgView.frame = CGRectMake(rect.origin.x - 8, rect.origin.y-93+34, 50, 93); [self.bigbgView configViewWithModel:model]; [window addSubview:self.bigbgView];}- (void)cancelLonepressAction:(NSIndexPath *)indexPath{ if (self.bigbgView.superview) { [self.bigbgView removeFromSuperview]; self.bigbgView = nil; }}点击变乱:- (void)addEmojiStringWithModel:(JYMomentsEmojiModel *_Nullable)emojiModel{ NSRange range = [self selectedRange]; NSMutableAttributedString *mutableAttri = [[NSMutableAttributedString alloc] initWithAttributedString:self.tagTextView.attributedText]; NSAttributedString *attributedString = [[JYMomentsEmojiManager sharedInstance] imageStringFromModel:emojiModel isWebp:YES font:[UIFont f15]]; [mutableAttri insertAttributedString:attributedString atIndex:range.location]; mutableAttri.yy_font = [UIFont f15]; mutableAttri.yy_color = [UIColor color_333333]; self.tagTextView.attributedText = [mutableAttri copy]; self.tagTextView.selectedRange = NSMakeRange(range.location+1, 0); [self.tagTextView scrollRangeToVisible:self.tagTextView.selectedRange];}//删除变乱- (void)subtractEmojiString{//体系键盘的删除变乱处理处罚 [self.tagTextView deleteBackward]; self.tagTextView.selectedRange = [self selectedRange]; [self.tagTextView scrollRangeToVisible:self.tagTextView.selectedRange];}注意:由于添加心情的时间是整个富文本更换原来的富文本,此时假如不设置TextView的selectRange那么就有问题,也就是光标会有问题,要设置准确的光标很告急,设置完准确的光标位置,然后[self.tagTextView scrollRangeToVisible:self.tagTextView.selectedRange];利用这句代码可以让输入框不抖动
7、复制粘贴功能(包罗粘贴时长度大于字数限定时截取处理处罚)
更改可粘贴富文本的属性
_tagTextView.allowsPasteAttributedString = YES;
默认复制富文本的属性是yes,粘贴富文本属性为NO,假如这里不设置,那么就粘贴不外来,只能粘贴的是平常文本
自界说TextView继承与YYTextView,用于hook复制粘贴方法
假如直接利用YYTextView,是利用体系的复制粘贴功能,那么假如复制的心情过多的话,会卡主线程3s左右,体验很差
假如继承YYTextView,在复制方法里,将富文本转成平常字符串(=>[高兴])存在粘贴板里,剪切的方法跟复制雷同,只是要将剪切那部分给删掉,粘贴的时间再将平常文本转成心情文本([高兴]=>)
自界说TextView代码如下:
.h文件#import <YYText/YYText.h>NS_ASSUME_NONNULL_BEGIN@interface JYMomentsSubmitTextView : YYTextView- (instancetype)initWithFrame:(CGRect)frame font:(UIFont *)font color:(UIColor *)color;@endNS_ASSUME_NONNULL_END.m文件//// JYMomentsSubmitTextView.m// JYMomentsModule//// Created by huorui16130 on 2022/4/12.//#import "JYMomentsSubmitTextView.h"#import "JYMomentsEmojiManager.h"@interface JYMomentsSubmitTextView()@property (nonatomic,strong) UIFont *textFont;@property (nonatomic,strong) UIColor *contentColor;@end@implementation JYMomentsSubmitTextView/*// Only override drawRect: if you perform custom drawing.// An empty implementation adversely affects performance during animation.- (void)drawRect:(CGRect)rect { // Drawing code}*/- (instancetype)initWithFrame:(CGRect)frame font:(UIFont *)font color:(UIColor *)color{ if (self = [super initWithFrame:frame]) {// self.interactiveSuperScrollView = self.textFont = font; self.contentColor = color; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(willShowEditMenu name:UIMenuControllerWillShowMenuNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didHideEditMenu name:UIMenuControllerDidHideMenuNotification object:nil]; } return self;}- (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self];}-(void)willShowEditMenu:(id)sender{ self.canCancelContentTouches = NO; self.delaysContentTouches = NO;}-(void)didHideEditMenu:(NSNotification *)notifi{ self.canCancelContentTouches = YES; self.delaysContentTouches = YES;}- (BOOL)canBecomeFirstResponder { return YES;}- (void)cut:(id)sender{ NSAttributedString *attri = [[JYMomentsEmojiManager sharedInstance] exchangeTextFromEmojiTextView:self textRange:self.selectedRange]; UIPasteboard *pastboard = [UIPasteboard generalPasteboard]; pastboard.string = attri.string; NSMutableAttributedString *mutableAttri = [[NSMutableAttributedString alloc] initWithAttributedString:self.attributedText]; NSRange insertRange = self.selectedRange; [mutableAttri jy_replaceCharactersInRange:self.selectedRange withString:@""]; self.attributedText = [mutableAttri copy]; self.selectedRange = NSMakeRange(insertRange.location, 0); [self scrollRangeToVisible:NSMakeRange(insertRange.location, 0)];}- (void)copy:(id)sender { NSAttributedString *attri = [[JYMomentsEmojiManager sharedInstance] exchangeTextFromEmojiTextView:self textRange:self.selectedRange]; UIPasteboard *pastboard = [UIPasteboard generalPasteboard]; pastboard.string = attri.string;}- (void)paste:(id)sender{ UIPasteboard *pastboard = [UIPasteboard generalPasteboard]; NSAttributedString *attri = [[JYMomentsEmojiManager sharedInstance] fetchEmojiContentWithEmojiString:pastboard.string isWebp:YES font:[UIFont f18] pageId:@""]; NSMutableAttributedString *mutableAtt = [[NSMutableAttributedString alloc] initWithAttributedString:self.attributedText]; NSRange insertRange = self.selectedRange; if (self.tag == 300) {//批评输入框 NSAttributedString *att = [[JYMomentsEmojiManager sharedInstance] exchangeTextFromEmojiTextView:self]; if (att.length + pastboard.string.length > 140) { NSString *insertString = [pastboard.string substringToIndex:140-att.length]; NSAttributedString *endStr = [[NSAttributedString alloc] initWithString:insertString]; [mutableAtt jy_replaceCharactersInRange:insertRange withAttributedString:endStr]; }else{ [mutableAtt jy_replaceCharactersInRange:insertRange withAttributedString:attri]; } }else{ [mutableAtt jy_replaceCharactersInRange:insertRange withAttributedString:attri]; } mutableAtt.yy_font = self.textFont; mutableAtt.yy_color = self.contentColor; self.attributedText = [mutableAtt copy]; self.selectedRange = NSMakeRange(insertRange.location+attri.length, 0); [self scrollRangeToVisible:self.selectedRange];}@end注意注意注意:键盘添加心情大概字符的时间我们可以通过TextView署理方法来获取长度,判定是否凌驾字数限定,是否可以再添加
粘贴的话,可以通过这个粘贴的方法,算一下字符串长度是否到达最大,多的话,我们也可以截图,通常我们长度是按照转成平常文本([高兴])来算的,以是盘算的时间要注意,假如用心情的字符长度盘算没有凌驾最大字数限定,但是现实上转成平常文本情势已经远远凌驾字数限定就会出现问题
8、输入框高度有高度限定时的动态变革逻辑
#pragma mark - textView高度- (void)configTextViewHeightWithAnimation:(BOOL)animation { @try{ CGFloat textViewHeight = [self getTopicTextHeight]; if (textViewHeight < kMinTextViewHeight) { [self.tagTextView mas_updateConstraints:^(MASConstraintMaker *make) { make.height.mas_equalTo(kMinTextViewHeight); }]; } else if (textViewHeight > kMaxTextViewHeight) { [self.tagTextView mas_updateConstraints:^(MASConstraintMaker *make) { make.height.mas_equalTo(kMaxTextViewHeight); }]; } else { if (textViewHeight - self.tagTextView.height != self.textViewFont.lineHeight) { if (!animation) { [self.tagTextView mas_updateConstraints:^(MASConstraintMaker *make) { make.height.mas_equalTo(textViewHeight + self.textViewFont.lineHeight); }]; } else { [UIView animateWithDuration:0.5 animations:^{ [self.tagTextView mas_updateConstraints:^(MASConstraintMaker *make) { make.height.mas_equalTo(textViewHeight + self.textViewFont.lineHeight); }]; [self.tagTextView.superview layoutIfNeeded]; }]; } } } [self.tagTextView scrollRangeToVisible:self.tagTextView.selectedRange]; }@catch (NSException *exception) { NSLog(@"moments catched a exception: %@",exception.description); } }- (CGFloat)getTopicTextHeight { CGRect rect = [kSafeStr(self.tagTextView.attributedText.string) boundingRectWithSize:CGSizeMake(kScreenWidth - 20, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName : [UIFont systemFontOfSize:15]} context:nil]; return rect.size.height;}9、输入框输入大概粘贴的时间,上下抖动问题的处理处罚
现实上在输入心情大概删除,复制粘贴等利用时,利用scrollRangeToVisible方法是可以办理抖动问题的,假如还抖大概就是由于,我们在添加完字符后,利用完scrollRangeToVisible方法后,又盘算TextView高度动态改变了高度这时就又有了弊端,以是在盘算和设置TextView的高度的方法末了再加一次这个代码,就能办理掉
10、上传服务器
末了的步调就是将输入框中的文本,上传服务器
比方输入框中是:了,出不了门,玩玩游戏,还是挺的
我们通过方法
[[JYMomentsEmojiManager sharedInstance] exchangeTextFromEmojiTextView:self.textView];
获取到富文本字符串:[下雨]了,出不了门,玩玩游戏,还是挺[开心]的
然后就可以上传服务器了
增补:假如TextView是独立的在VC上而不是在键盘顶部,比如发布页页面层级
VC--->UIScrollView--->TextView,大概我们在复制大概剪切的时间,拖动选中地域,跟其他滑动手势会辩论导致划不动,可以如下处理处罚,代码如下:
- (void)viewDidLoad { [super viewDidLoad]; [self addNotification]; [self keepGestureRecognizer];}- (void)addNotification{//监听UIMenuController的表现和隐藏 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(willShowEditMenu name:UIMenuControllerWillShowMenuNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didHideEditMenu name:UIMenuControllerDidHideMenuNotification object:nil];}-(void)willShowEditMenu:(id)sender{ self.mainScrollView.canCancelContentTouches = NO; self.mainScrollView.delaysContentTouches = NO;}-(void)didHideEditMenu:(NSNotification *)notifi{ self.mainScrollView.canCancelContentTouches = YES; self.mainScrollView.delaysContentTouches = YES;}#pragma mark - 包管scrollview的手势状态和textview手势状态保持一致- (void)keepGestureRecognizer { __weak typeof(self) weakSelf = self; [self.tagTextView addObserverBlockForKeyPath:@"panGestureRecognizer.enabled" block:^(id _Nonnull obj, id _Nonnull oldVal, id _Nonnull newVal) { weakSelf.mainScrollView.panGestureRecognizer.enabled = weakSelf.tagTextView.panGestureRecognizer.enabled; }];}- (void)dealloc {//记得删除观察着 [self.tagTextView removeObserver:self forKeyPath:@"panGestureRecognizer.enabled"];}将工具类文件放到末了,方便团体预览
这里是一个工具类,通过对外提供接口,可以知道我们必要什么样的功能
#import <Foundation/Foundation.h>@class JYMomentsEmojiModel;NS_ASSUME_NONNULL_BEGIN@interface JYMomentsEmojiManager : NSObject//暂时迩来利用的心情数组@property (nonatomic,copy) NSArray *recentlyEmojiArray;+ (instancetype)sharedInstance;//********************存储心情模块**********************************//存储JSON文件- (void)saveEmojiDataWithDictionary:(NSDictionary *)dictionary;//利用当地JSON文件- (void)useLocalEmojiData;//获取全部心情- (NSDictionary *)fetchAllEmojiDictionary;//获取全部心情name数组- (NSArray *)fetchAllEmojiNamesArray;//********************迩来利用心情模块**********************************//批评模块中点击心情后暂时存储迩来利用心情排序处理处罚(不存储)- (void)temporaryStorageEmoji:(JYMomentsEmojiModel *)emojiModel;//存储迩来利用心情数组- (void)saveRecentlyEmojiArr;//获取迩来利用心情数组- (void)fetchRecentlyEmojiArr;- (NSMutableArray<JYMomentsEmojiModel *> *)getRecentlyEmojiArray;//********************转换心情模块**********************************//心情字符串原文本转心情文本 (比方:[OK]了吗 => ??了吗) pageid是为了埋点- (NSAttributedString *)fetchEmojiContentWithEmojiString:(NSString *)emojiString isWebp:(BOOL)isWebp font:(UIFont *)font pageId:(NSString *)pageId;//通过 心情模子 获取 心情文本字符串(比方:?? => [OK])- (NSAttributedString *)imageStringFromModel:(JYMomentsEmojiModel *)model isWebp:(BOOL)isWebp font:(UIFont *)font;//通过输入框心情文本转换成字符串文本输出(比方:??了吗 => [OK]了吗)- (NSAttributedString *)exchangeTextFromEmojiTextView:(YYTextView *)textView;- (NSAttributedString *)exchangeTextFromEmojiTextView:(YYTextView *)textView textRange:(NSRange)textRange;@endNS_ASSUME_NONNULL_END#import "JYMomentsEmojiManager.h"#import "JYMomentsEmojiModel.h"#import <SDWebImage/UIImageView+WebCache.h>#import <YYImage/YYImage.h>static NSString *const storeLocalEmojiPath = @"emoji";static NSString *const storeEmojiPath = @"/hellobike_moments_emojiDictionary";@interface JYMomentsEmojiManager()//心情字典@property (nonatomic,copy) NSDictionary *allEmojiDictionary;@property (nonatomic,strong) NSMutableArray *recentlyEmojiArr;@property (nonatomic,strong) NSArray *emojiNames;@end@implementation JYMomentsEmojiManager+ (instancetype)sharedInstance{ static JYMomentsEmojiManager *instance; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ instance = [[JYMomentsEmojiManager alloc] init]; }); return instance;}#pragma mark ---public Method//利用当地JSON文件- (void)useLocalEmojiData{ NSString *path = [JYMomentsBundle() pathForResource:storeLocalEmojiPath ofType:@"json"]; NSData *jsonData = [[NSData alloc] initWithContentsOfFile:path]; NSError *error; NSDictionary *jsonObj = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:&error]; if (jsonObj && !error) { self.emojiNames = jsonObj[@"emojiNames"]; [[NSUserDefaults standardUserDefaults] setObject:self.emojiNames forKey:kMomentsEmojiNamesArray]; [[NSUserDefaults standardUserDefaults] synchronize]; self.allEmojiDictionary = [self dictionaryToModelWithDictionary:jsonObj[@"emojis"]]; }}//存储心情包数据文件- (void)saveEmojiDataWithDictionary:(NSDictionary *)dictionary{ NSFileManager *fileManager = [NSFileManager defaultManager]; NSString *filepath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0]; NSString *newPath = [filepath stringByAppendingPathComponent:storeEmojiPath]; if ([fileManager fileExistsAtPath:newPath]) { [fileManager removeItemAtPath:newPath error:nil]; } //存储数据 self.allEmojiDictionary = [self dictionaryToModelWithDictionary:dictionary[@"emojis"]]; self.emojiNames = dictionary[@"emojiNames"]; [[NSUserDefaults standardUserDefaults] setObject:self.emojiNames forKey:kMomentsEmojiNamesArray]; [[NSUserDefaults standardUserDefaults] synchronize]; NSData *data = [NSKeyedArchiver archivedDataWithRootObject:self.allEmojiDictionary]; [data writeToFile:newPath atomically:YES]; //存储版本号 [[NSUserDefaults standardUserDefaults] setInteger:[dictionary[@"emojiVersion"] intValue] forKey:kMomentsEmojiVersion]; [[NSUserDefaults standardUserDefaults] synchronize];}//批评模块中点击后暂时存储迩来利用心情- (void)temporaryStorageEmoji:(JYMomentsEmojiModel *)emojiModel{ BOOL isHave = NO; NSInteger index = 0; for (JYMomentsEmojiModel *model in self.recentlyEmojiArr) { //根据心情名字判定是否是同一个心情去重处理处罚 if ([emojiModel.code isEqualToString:model.code]) { isHave = YES; index = [self.recentlyEmojiArr indexOfObject:model]; break; } } if (!isHave) { [self.recentlyEmojiArr jy_insertObject:emojiModel atIndex:0]; }else{ [self.recentlyEmojiArr jy_removeObjectAtIndex:index]; [self.recentlyEmojiArr jy_insertObject:emojiModel atIndex:0]; } //截取前7个存储 if (self.recentlyEmojiArr.count > kMomentsEmojiRecentlyNumber) { self.recentlyEmojiArr = [[self.recentlyEmojiArr jy_subarrayWithRange:NSMakeRange(0, kMomentsEmojiRecentlyNumber)] mutableCopy]; }}//存储迩来利用心情数组- (void)saveRecentlyEmojiArr{ if (self.recentlyEmojiArr.count > 0) { NSMutableArray *arr = [NSMutableArray array]; for (JYMomentsEmojiModel *model in self.recentlyEmojiArr) { NSData *data = [NSKeyedArchiver archivedDataWithRootObject:model]; [arr addObject:data]; } [[NSUserDefaults standardUserDefaults] setObject:arr forKey:kMomentsEmojiRecentlyUseArray]; [[NSUserDefaults standardUserDefaults] synchronize]; }}- (NSArray *)recentlyEmojiArray{ return [self.recentlyEmojiArr copy];}//JSON文件更新同步更新迩来利用心情的model- (void)refreshRecentlyEmojiArrWithArray:(NSArray *)array{ if (array.count > 0) { self.recentlyEmojiArr = [array mutableCopy]; NSMutableArray *arr = [NSMutableArray array]; for (JYMomentsEmojiModel *model in array) { NSData *data = [NSKeyedArchiver archivedDataWithRootObject:model]; [arr addObject:data]; } [[NSUserDefaults standardUserDefaults] setObject:arr forKey:kMomentsEmojiRecentlyUseArray]; [[NSUserDefaults standardUserDefaults] synchronize]; }}//获取迩来利用心情数组- (void)fetchRecentlyEmojiArr{ self.recentlyEmojiArr = [self getRecentlyEmojiArray];}- (NSMutableArray<JYMomentsEmojiModel *> *)getRecentlyEmojiArray{ NSMutableArray *arr = [NSMutableArray new]; NSArray *dataArr = [[NSUserDefaults standardUserDefaults] objectForKey:kMomentsEmojiRecentlyUseArray]; for (NSData *data in dataArr) { [arr addObject:[NSKeyedUnarchiver unarchiveObjectWithData:data]]; } return arr;}//字符串文本 转 心情文本- (NSAttributedString *)fetchEmojiContentWithEmojiString:(NSString *)emojiString isWebp:(BOOL)isWebp font:(UIFont *)font pageId:(NSString *)pageId{ NSArray *matchArray = [self fetchRangeWithContentString:emojiString]; //用来存放字典,字典中存储的是图片和图片对应的位置 NSMutableArray *imageArray = [NSMutableArray arrayWithCapacity:matchArray.count]; //根据匹配范围来用图片举行相应的更换 for(NSTextCheckingResult *match in matchArray) { //获取数组元素中得到range NSRange range = [match range]; //获取原字符串中对应的值 NSString *subStr = [emojiString substringWithRange:range]; //把图片和图片对应的位置存入字典中 NSMutableDictionary *imageDic = [NSMutableDictionary dictionaryWithCapacity:matchArray.count]; [imageDic setObject:subStr forKey:@"emojiStr"]; [imageDic setObject:[NSValue valueWithRange:range] forKey:@"range"]; //把字典存入数组中 [imageArray addObject:imageDic]; } return [self fetchEmojiContentWithString:emojiString imageDataArr:imageArray isWebp:isWebp font:font pageId:pageId];}//通过 心情模子 获取 心情文本字符串- (NSAttributedString *)imageStringFromModel:(JYMomentsEmojiModel *)model isWebp:(BOOL)isWebp font:(UIFont *)font{ NSURL *url = [NSURL URLWithString:[[model.url fetchThumbImageWidth:kEmojiWidth andHeight:kEmojiWidth isWebp:isWebp] stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]]];// YYAnimatedImageView *insertImageView = [[YYAnimatedImageView alloc] init]; UIImageView *insertImageView = [[UIImageView alloc] init]; insertImageView.contentMode = UIViewContentModeScaleAspectFit; insertImageView.tag = [model.code integerValue]; insertImageView.bounds = CGRectMake(0, 0, font.lineHeight, font.lineHeight); [insertImageView sd_setImageWithURL:url completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) { }]; NSMutableAttributedString *imageStr = [NSMutableAttributedString yy_attachmentStringWithContent:insertImageView contentMode:UIViewContentModeScaleToFill attachmentSize:CGSizeMake(font.lineHeight, font.lineHeight) alignToFont:font alignment:YYTextVerticalAlignmentCenter]; return imageStr;}//心情文本转换成字符串文本- (NSAttributedString *)exchangeTextFromEmojiTextView:(YYTextView *)textView{ NSArray *arr = textView.textLayout.attachments; NSArray<NSValue *> *values = textView.textLayout.attachmentRanges; NSMutableAttributedString *mutableAttributedString = [[NSMutableAttributedString alloc] initWithAttributedString:textView.attributedText]; for (long int i = arr.count-1; i >= 0; i --) { NSValue *value = [values jy_objectAtIndex:i]; NSRange range = value.rangeValue; YYTextAttachment *attachment = [arr jy_objectAtIndex:i]; UIImageView *imageView = attachment.content; NSString *replaceStr = @""; for (NSString *key in self.allEmojiDictionary.allKeys) { JYMomentsEmojiModel *model = self.allEmojiDictionary[key]; if ([model.code integerValue] == imageView.tag) { replaceStr = model.name; break; } } [mutableAttributedString jy_replaceCharactersInRange:range withString:replaceStr]; } return [mutableAttributedString copy];}//心情文本转换成字符串文本- (NSAttributedString *)exchangeTextFromEmojiTextView:(YYTextView *)textView textRange:(NSRange)textRange{ YYTextView *tempTextView = [[YYTextView alloc] init]; tempTextView.attributedText = textView.attributedText;; NSArray *arr = textView.textLayout.attachments; NSArray<NSValue *> *values = textView.textLayout.attachmentRanges; NSMutableAttributedString *mutableAttributedString = [[NSMutableAttributedString alloc] initWithAttributedString:tempTextView.attributedText]; for (long int i = arr.count-1; i >= 0; i --) { NSValue *value = [values jy_objectAtIndex:i]; NSRange range = value.rangeValue; if (textRange.location <= range.location && textRange.location + textRange.length >= range.location + range.length) { YYTextAttachment *attachment = [arr jy_objectAtIndex:i]; UIImageView *imageView = attachment.content; NSString *replaceStr = @""; for (NSString *key in self.allEmojiDictionary.allKeys) { JYMomentsEmojiModel *model = self.allEmojiDictionary[key]; if ([model.code integerValue] == imageView.tag) { replaceStr = model.name; break; } } [mutableAttributedString jy_replaceCharactersInRange:range withString:replaceStr]; } } NSInteger replaceStringLength = mutableAttributedString.string.length - textView.attributedText.string.length + textRange.length; NSRange copyRange = NSMakeRange(textRange.location, replaceStringLength); NSAttributedString *attString = [mutableAttributedString jy_attributedSubstringFromRange:copyRange]; return attString;}- (NSDictionary *)fetchAllEmojiDictionary{ NSFileManager *fileManager = [NSFileManager defaultManager]; NSString *filepath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0]; NSString *newPath = [filepath stringByAppendingPathComponent:storeEmojiPath]; if ([fileManager fileExistsAtPath:newPath]) { NSData *data = [NSData dataWithContentsOfFile:newPath]; NSDictionary *dic = [NSKeyedUnarchiver unarchiveObjectWithData:data]; if (dic && dic.allKeys.count > 0) { self.allEmojiDictionary = dic; return dic; } } [self useLocalEmojiData]; return self.allEmojiDictionary;}//获取全部心情name数组- (NSArray *)fetchAllEmojiNamesArray{ return [[NSUserDefaults standardUserDefaults] objectForKey:kMomentsEmojiNamesArray];}#pragma mark --private Method//从后往前更换字符串- (NSAttributedString *)fetchEmojiContentWithString:(NSString *)content imageDataArr:(NSArray *)imageDataArr isWebp:(BOOL)isWebp font:(UIFont *)font pageId:(NSString *)pageId{ NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:content]; for (long int i = (imageDataArr.count - 1); i >= 0; i --) { NSDictionary *dictionary = [imageDataArr jy_objectAtIndex:i]; NSString *subStr = dictionary[@"emojiStr"]; NSRange range; [dictionary[@"range"] getValue:&range]; if ([self.allEmojiDictionary.allKeys containsObject:subStr]) { JYMomentsEmojiModel *model = self.allEmojiDictionary[subStr]; NSURL *url = [NSURL URLWithString:[[model.url fetchThumbImageWidth:kEmojiWidth andHeight:kEmojiWidth isWebp:isWebp] stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]]]; UIImageView *insertImageView = [[UIImageView alloc] init]; insertImageView.contentMode = UIViewContentModeScaleAspectFit; insertImageView.tag = [model.code integerValue]; insertImageView.bounds = CGRectMake(0, 0, font.lineHeight, font.lineHeight); [insertImageView sd_setImageWithURL:url completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) { }]; NSMutableAttributedString *imageStr = [NSMutableAttributedString yy_attachmentStringWithContent:insertImageView contentMode:UIViewContentModeScaleToFill attachmentSize:CGSizeMake(font.lineHeight, font.lineHeight) alignToFont:font alignment:YYTextVerticalAlignmentCenter]; //举行更换 [attributedString jy_replaceCharactersInRange:range withAttributedString:imageStr]; if (pageId.length > 0) { JYTrackExposeInfo *exposeinfo = [[JYTrackExposeInfo alloc] init]; exposeinfo.categoryId = @"alpha"; exposeinfo.pageId = kSafeStr(pageId); exposeinfo.moduleId = @"community_HAHA_exposure"; exposeinfo.contentId = @"community_HAHA_exposure"; exposeinfo.exposureTimes = @"1"; [JYTracker exposeEventTrackWithExposeInfo:exposeinfo businessInfo:@{@"numHaha":model.code}]; } } } return attributedString;}//通过正则表达式匹配到范围- (NSArray *)fetchRangeWithContentString:(NSString *)string{ //正则表达式 NSString * pattern = @"(\\[).*?(\\])"; NSError *error = nil; NSRegularExpression *re = [[NSRegularExpression alloc] initWithPattern:pattern options:0 error:&error]; if (!re) { NSLog(@"%@", [error localizedDescription]); } //通过正则表达式来匹配字符串 return [re matchesInString:string options:0 range:NSMakeRange(0, string.length)];}//字典中套的字典转模子- (NSDictionary *)dictionaryToModelWithDictionary:(NSDictionary *)dictionary{ __block NSMutableArray *muteablArr = [NSMutableArray arrayWithArray:self.recentlyEmojiArr]; NSMutableDictionary *dict = [NSMutableDictionary dictionary]; for (NSString *key in dictionary.allKeys) { if (key.length > 0 && ![dict.allKeys containsObject:key]) { JYMomentsEmojiModel *model = [JYMomentsEmojiModel mj_objectWithKeyValues:dictionary[key]]; [self.recentlyEmojiArr enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { JYMomentsEmojiModel *item = (JYMomentsEmojiModel *)obj; if ([model.name isEqualToString:item.name]) { [muteablArr jy_replaceObjectAtIndex:idx withObject:model]; *stop = YES; } }]; [dict setObject:model forKey:key]; } } [self refreshRecentlyEmojiArrWithArray:muteablArr]; return [dict copy];}#pragma mark --LazyLoad- (NSMutableArray *)recentlyEmojiArr{ if (!_recentlyEmojiArr) { _recentlyEmojiArr = [NSMutableArray array]; } return _recentlyEmojiArr;}@end |