[iOS]聊天表情键盘实现

作者: 沵可安好 | 来源:发表于2020-11-20 10:51 被阅读0次

    前言

    好久没有更新iOS相关文章了,今天得空,写一个表情键盘的demo,先上效果图和链接。
    链接:https://github.com/pizazzsy/FaceKeyboard.git
    效果图:

    0AAF1A4E-1895-4F69-9DBD-BACB7FFFC38D.png
    基本思路

    首先,表情包的图片是用bundle的形式组织的,用来存放表情包,用Emotions.plist作为配置文件,存储表情包的信息。
    ChatEmojiManager类主要负责数据部分,用单例的形式,这样可以在初始化的时候只会读取一次plist文件中的所有表情信息;同时我们把输入框内容发到服务端以及从服务端请求到的都是纯文本的,比如会把 "🤣" 转成 "[笑哭]" 这样的纯文本,而不是直接把表情图片直接发到服务端,ChatEmojiManager类提供匹配某段纯文本中的表情,并把文本替换为图片的功能。

    ChatEmojiManager.h类的头文件如下:
    
    @interface ChatEmojiManager : NSObject
    + (instancetype)sharedManager;
    - (UIImage *)imageForEmotionPNGName:(NSString *)pngName;
    /**
    将表情文本转为属性字符串
    
    @param text 表情文本
    @param font 文本d大小
    @return 属性字符串
    */
    - (NSMutableAttributedString *)convertTextEmotionToAttachment:(NSString *)text font:(UIFont *)font;
    /**
    将已进行过格式处理的 NSMutableAttributedString 类型的表情文本转为属性字符串
    
    @param attributeString 表情文本
    @param font 文本d大小
    @return 属性字符串
    */
    - (NSMutableAttributedString *)convertTextEmotionToAttachmentWithAttributedString:(NSMutableAttributedString *)attributeString
                                                                                 font:(UIFont *)font;
    /// 表情字典数据
    @property (nonatomic) NSMutableDictionary *emotionDictionary;
    
    /// 表情模型数据
    @property (nonatomic) NSMutableArray<ChatEmojiModel *> *allEmojiModels;
    @property (nonatomic, strong) NSBundle *emotionBundle;
    @property (nonatomic, strong) NSRegularExpression *regularExpression;
    
    
    @end
    
    ChatEmojiManager.m类的头文件如下:
    #import "ChatEmojiManager.h"
    #import "ChatEmojiModel.h"
    @implementation ChatEmojiManager
    + (instancetype)sharedManager {
        static ChatEmojiManager *_instance;
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            _instance = [[ChatEmojiManager alloc] init];
        });
        return _instance;
    }
    - (UIImage *)imageForEmotionPNGName:(NSString *)pngName {
        return [UIImage imageNamed:pngName inBundle:self.emotionBundle compatibleWithTraitCollection:nil];
    }
    - (NSBundle *)emotionBundle {
        if (!_emotionBundle) {
            _emotionBundle = [NSBundle bundleWithPath:[[NSBundle mainBundle] pathForResource:@"Emotion" ofType:@"bundle"]];
        }
        return _emotionBundle;
    }
    - (NSRegularExpression *)regularExpression {
        if (!_regularExpression) {
            _regularExpression = [NSRegularExpression regularExpressionWithPattern:@"\\[[^\\[]{1,5}\\]" options:kNilOptions error:nil];
        }
        return _regularExpression;
    }
    - (NSMutableDictionary *)emotionDictionary {
        if (!_emotionDictionary) {
            [self loadEmotions];
        }
        return _emotionDictionary;
    }
    - (NSMutableArray<ChatEmojiModel *> *)allEmojiModels {
        if (!_allEmojiModels) {
            [self loadEmotions];
        }
        return _allEmojiModels;
    }
    - (NSMutableAttributedString *)convertTextEmotionToAttachment:(NSString *)text font:(UIFont *)font {
        NSMutableAttributedString *attributeString = [[NSMutableAttributedString alloc] initWithString:text attributes:@{NSFontAttributeName:font}];
        return [self convertTextEmotionToAttachmentWithAttributedString:attributeString font:font];
    }
    - (NSMutableAttributedString *)convertTextEmotionToAttachmentWithAttributedString:(NSMutableAttributedString *)attributeString
                                                                                 font:(UIFont *)font {
        NSArray<NSTextCheckingResult *> *matchArray = [self.regularExpression matchesInString:attributeString.string options:kNilOptions range:NSMakeRange(0, attributeString.length)];
        NSUInteger offset = 0;
        for (NSTextCheckingResult *result in matchArray) {
            NSRange range = NSMakeRange(result.range.location - offset, result.range.length);
            NSTextAttachment *attachMent = [[NSTextAttachment alloc] init];
            NSString *imageText = [attributeString.string substringWithRange:NSMakeRange(range.location, range.length)];
            NSString *imageName = self.emotionDictionary[imageText];
            UIImage *image = [self imageForEmotionPNGName:imageName];
            attachMent.image = image;
            attachMent.bounds = CGRectMake(0, font.descender, font.lineHeight, font.lineHeight);
            NSAttributedString *emojiAttrStr = [NSAttributedString attributedStringWithAttachment:attachMent];
            [attributeString replaceCharactersInRange:range withAttributedString:emojiAttrStr];
            offset += result.range.length - emojiAttrStr.length;
        }
        return attributeString;
    }
    - (void)loadEmotions {
        self.allEmojiModels = [NSMutableArray array];
        self.emotionDictionary = [NSMutableDictionary dictionary];
        NSString *path = [[NSBundle mainBundle] pathForResource:@"Emotions" ofType:@"plist"];
        NSArray<NSDictionary *> *groups = [NSArray arrayWithContentsOfFile:path];  // 获取plist文件内容
        for (NSDictionary *group in groups) {
            if ([group[@"type"] isEqualToString:@"emoji"]) {
                NSArray<NSDictionary *> *items = group[@"items"];
                for (NSDictionary *item in items) {
                    ChatEmojiModel *model = [ChatEmojiModel modelWithDictionary:item];
                    [self.allEmojiModels addObject:model];
                    self.emotionDictionary[model.text] = model.imagePNG;
                }
            }
        }
    }
    @end
    
    接下来是表情键盘ChatExpressionView.h
    
    #import <UIKit/UIKit.h>
    #import "ChatEmojiModel.h"
    @protocol ChatExpressionViewDelegate
    // 选择表情
    - (void)selectedEmoji:(ChatEmojiModel *_Nullable)emoji;
    @required
    // 删除事件
    - (void)deleteEvent;
    
    @end
    NS_ASSUME_NONNULL_BEGIN
    
    @interface ChatExpressionView : UIView
    @property (nonatomic,strong)UICollectionView *myCollectionView;
    @property (nonatomic, weak) id<ChatExpressionViewDelegate> delegate;
    @property (nonatomic, strong) NSMutableArray *allEmojiModels;
    
    @end
    
    NS_ASSUME_NONNULL_END
    
    
    ChatExpressionView.m实现代码
    #import "ChatExpressionView.h"
    #import "ChatExpressionCell.h"
    #import "ChatEmojiManager.h"
    #import "HLHorizontalPageLayout.h"
    #define rowNum 21   //3行7列
    #define TopAndBottom 5  //行间距
    #define LeftAndRight 15 //列间距
    
    @interface ChatExpressionView ()<UICollectionViewDataSource,UICollectionViewDelegate>
    {
        UIPageControl*pageControlBottom;
    }
    @property (nonatomic, strong) NSBundle *emotionBundle;
    @end
    @implementation ChatExpressionView
    - (id)initWithFrame:(CGRect)frame
    {
        self = [super initWithFrame:frame];
        if (self) {
            self.backgroundColor = [UIColor whiteColor];
            //横向排列
            CGFloat width = self.bounds.size.width;
            NSInteger col = 7; // 列数
            self.allEmojiModels=[ChatEmojiManager sharedManager].allEmojiModels;
            HLHorizontalPageLayout *layout = [[HLHorizontalPageLayout alloc]init];
            layout.sectionInset = UIEdgeInsetsMake(15, 15, 15, 15);
            layout.minimumInteritemSpacing = LeftAndRight;
            layout.minimumLineSpacing = TopAndBottom;
            CGFloat itemWidth = (width - LeftAndRight * (col-1) - layout.sectionInset.left - layout.sectionInset.right) / col;
            layout.itemSize = CGSizeMake( itemWidth, itemWidth);
            self.myCollectionView = [[UICollectionView alloc] initWithFrame:CGRectMake(0, 0, width, frame.size.height-20) collectionViewLayout:layout];
            self.myCollectionView.dataSource = self;
            self.myCollectionView.delegate = self;
            self.myCollectionView.pagingEnabled = YES;
            self.myCollectionView.showsVerticalScrollIndicator = NO;
            self.myCollectionView.showsHorizontalScrollIndicator=NO;
            [self.myCollectionView registerNib:[UINib nibWithNibName:@"ChatExpressionCell" bundle:nil] forCellWithReuseIdentifier:@"ChatExpressionCell"];
            self.myCollectionView.backgroundColor = UIColor.whiteColor;
            pageControlBottom = [[UIPageControl alloc]initWithFrame:CGRectMake(0, frame.size.height-20, self.frame.size.width, 20)];
            pageControlBottom.pageIndicatorTintColor=[UIColor grayColor];
            pageControlBottom.currentPageIndicatorTintColor=[UIColor orangeColor];
            pageControlBottom.numberOfPages=(self.allEmojiModels.count/rowNum)+(self.allEmojiModels.count%rowNum==0?0:1);
            [self addSubview:self.myCollectionView];
            [self addSubview:pageControlBottom];
        }
        return self;
    }
    -(UIImage *)imageForEmotionPNGName:(NSString *)pngName{
         return [UIImage imageNamed:pngName inBundle:[ChatEmojiManager sharedManager].emotionBundle compatibleWithTraitCollection:nil];
    }
    -(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
    {
        return self.allEmojiModels.count;
    }
    #pragma mark cell的视图
    - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
    {
        NSString *cellIdentifier = @"ChatExpressionCell";
        ChatExpressionCell * cell = [collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier forIndexPath:indexPath];
        ChatEmojiModel *model = [self.allEmojiModels objectAtIndex:indexPath.item];
        cell.showImg.image =[[ChatEmojiManager sharedManager] imageForEmotionPNGName:model.imagePNG];
        return cell;
    }
    - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
    {
        NSInteger index = indexPath.item;
        ChatEmojiModel *model = [self.allEmojiModels objectAtIndex:index];
        if (_delegate) {
            [_delegate selectedEmoji:model];
        }
    }
    - (NSBundle *)emotionBundle {
        if (!_emotionBundle) {
            _emotionBundle = [NSBundle bundleWithPath:[[NSBundle mainBundle] pathForResource:@"Emotion" ofType:@"bundle"]];
        }
        return _emotionBundle;
    }
    -(void)scrollViewDidScroll:(UIScrollView *)scrollView{
        CGFloat contenOffset = scrollView.contentOffset.x;
        int page = contenOffset/scrollView.bounds.size.width+((int)contenOffset%(int)scrollView.bounds.size.width==0?0:1);
        pageControlBottom.currentPage = page;
    }
    @end
    
    附:

    比较懒,只给出核心代码,有需要的小伙伴可以下载demo体验下,或者直接在链接里面看具体实现。

    相关文章

      网友评论

        本文标题:[iOS]聊天表情键盘实现

        本文链接:https://www.haomeiwen.com/subject/swcdiktx.html