Preface:

I have done the [OC version] and [Swift version] keyboard with mixed text and expression, which is really low, especially the keyboard, the overall effect is only realized without encapsulation, it is difficult to integrate and use! And the attachment used to do not support GIF expression, I tried all kinds of methods, want to achieve similar QQ silky GIF expression experience, really not easy! It turned out that [YYText] was very powerful! After a variety of attempts and efforts to achieve a similar QQ GIF emoticons chat scheme based on YYText, a large number of emoticons will not be stuck! And the keyboard made a more comprehensive package integration is very convenient!


To show you the final result:

Single line input:





Demo. GIF

Multi-line input:





Demo 2. GIF

Keyboard integration method:

self.keyboard = [LiuqsEmoticonKeyBoard showKeyBoardInView:self.view]; 
self.keyboard.delegate = self;Copy the code

Project Github address: LiuqsEmoticonkeyboard

Here are the main classes, including their usage, internal implementation, and some details:

  1. LiuqsEmoticonKeyBoardEmoticon keyboard entity class:
Keyboard proxy:@protocol LiuqsEmotionKeyBoardDelegate <NSObject>
/* * Send button proxy event * argument PlainStr: transcoding the plain string of textView */
- (void)sendButtonEventsWithPlainString:(NSString *)PlainStr;

/* * Proxy method: The proxy event for keyboard changes is used to update the UI of the superview, such as the list height that changes with the keyboard */
- (void)keyBoardChanged;

@endCopy the code
/* * input box, the same input box on topbar */
@property(nonatomic.strong) UITextView *textView;
/* * Top input bar */
@property(nonatomic.strong) LiuqsTopBarView *topicBar;
/* * Input box font, used to calculate the size of the expression */
@property(nonatomic.strong) UIFont *font;
/* * Keyboard proxy */
@property(nonatomic.weak) id <LiuqsEmotionKeyBoardDelegate> delegate;
/* * The way to unpack the keyboard */
- (void)hideKeyBoard;
/* * The initialization method * argument view must be passed to the controller's view * will return a keyboard object * default is given 17 point font */
+ (instancetype)showKeyBoardInView:(UIView *)view;Copy the code

2.LiuqsEmotionPageView keyboard page class used to put the emoticon button, internal processing mainly according to the column position calculation, need to give the current is the page, used to load emoticon:

/* * Current page number */
@property(nonatomic.assign) NSUInteger page;
Button is the object on which the button is clicked */
@property(nonatomic.copy)void (^emotionButtonClick)(LiuqsButton *button);
/* * Keyboard delete button callback event * parameter button is the currently clicked delete button */
@property(nonatomic.copy)void (^deleteButtonClick)(LiuqsButton *button);Copy the code

LiuqsKeyBoardHeader global macro defined classes.

4.LiuqsTopBarView keyboard input box and some toggle button entity class, this can be customized as required:

TopBar's agent:@protocol LiuqsTopBarViewDelegate <NSObject>
/* * Proxy method, click the emoticon button trigger method */
- (void)TopBarEmotionBtnDidClicked:(UIButton *)emotionBtn;
/* * Proxy method, click the numeric keypad to send the event */
- (void)sendAction;
/* * Keyboard changes refresh superview */
- (void)needUpdateSuperView;

@endCopy the code
/* * sehng'mintopbar agent */
@property(assign.nonatomic)id <LiuqsTopBarViewDelegate> delegate;
/* * The input box above topbar */
@property(strong.nonatomic)UITextView *textView;
/* * * emoticons */
@property(nonatomic.strong) UIButton *topBarEmotionBtn;
/* * The height of the current keyboard, to distinguish between text keyboard or emoticon keyboard */
@property(nonatomic.assign) CGFloat CurrentKeyBoardH;
/* * The method used to actively trigger input box changes */
- (void)resetSubsives;Copy the code

5.LiuqsButton buttons on the keyboard are customized for better one-to-one correspondence with pictures and easier to handle.

6.NSAttributedString+LiuqsExtension Rich text classification:

- (NSString *)getPlainString { NSMutableString *plainString = [NSMutableString stringWithString:self.string];
    __block NSUInteger base = 0;
    [self enumerateAttribute:NSAttachmentAttributeName inRange:NSMakeRange(0, self.length)
                     options:0
                  usingBlock:^(LiuqsTextAttachment *value, NSRange range, BOOL *stop) {
                      if (value) {
                          [plainString replaceCharactersInRange:NSMakeRange(range.location + base, range.length)
                                                     withString:value.emojiTag];
                          base += value.emojiTag.length - 1;}}];
    return plainString;
}Copy the code

The getPlainString method obtains plain string encoding by iterating through attachments in rich text (in this case, emoticon images) and replacing them with plain strings (e.g. [laughs]). For example: the converted string looks like this: shy [shy]! The effect is like this:





Sample PNG.

LiuqsTextAttachment custom attachment class, inherited from NSTextAttachment.

These 7 classes are mainly for the keyboard part, or the input part, which is used to take data and interact with other terminals; Next comes the transcoding part or output part, which is responsible for taking the encoding given by others and converting it into rich text for the user to see!


8.LiuqsDecoder transcoding core classes:

Main methods:

/* * Transcoding method that converts a plain string to a rich text string (including image text, etc.) * The argument font is the font size used for display * the argument plainStr is a plain string * Return value: rich text used for display, copied directly to label display */
+ (NSMutableAttributedString *)decodeWithPlainStr:(NSString *)plainStr font:(UIFont *)font;Copy the code

To elaborate on the internal implementation: first, static properties:

// The size of the expression
static CGSize                    _emotionSize;
// Text font
static UIFont                    *_font;
// Color of the text
static UIColor                   *_textColor;
// Result array of matches
static NSArray                   *_matches;
// A common character string to transcode
static NSString                  *_string;
// The comparison table loaded by plist: [shy] <-> shy image
static NSDictionary              *_emojiImages;
// Store the dictionary array corresponding to the image range
static NSMutableArray            *_imageDataArray;
// Global rich text
static NSMutableAttributedString *_attStr;
// The final result is a rich text
static NSMutableAttributedString *_resultStr;Copy the code
+ (NSMutableAttributedString *)decodeWithPlainStr:(NSString *)plainStr font:(UIFont *)font {

    if(! plainStr) {return [[NSMutableAttributedString alloc]initWithString:@ ""]; }else {

        _font      = font;
        _string    = plainStr;
        _textColor = [UIColor blackColor];
        [self initProperty];
        [self executeMatch];
        [self setImageDataArray];
        [self setResultStrUseReplace];
        return_resultStr; }}Copy the code
In this method, the main initialization of the comparison table, and the size of the expression calculated according to the font + (void)initProperty {

    // Read and load the table
    NSString *path = [[NSBundle mainBundle] pathForResource:@"LiuqsEmotions" ofType:@"plist"];
    _emojiImages = [NSDictionary dictionaryWithContentsOfFile:path];
    // Sets the line spacing of the text
    NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc]init];

    [paragraphStyle setLineSpacing:4.0f];

    NSDictionary *dict = @{NSFontAttributeName:_font,NSParagraphStyleAttributeName:paragraphStyle};

    CGSize maxsize = CGSizeMake(1000, MAXFLOAT);
    // Calculate the height of the expression based on the font
    _emotionSize = [@ "/" boundingRectWithSize:maxsize options:NSStringDrawingUsesLineFragmentOrigin attributes:dict context:nil].size;

    _attStr = [[NSMutableAttributedString alloc]initWithString:_string attributes:dict];
}Copy the code
In this method, the rich text in the string + (void)executeMatch {
    // Regular rules
    NSString *regexString = checkStr;

    NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:regexString options:NSRegularExpressionCaseInsensitive error:nil];

    NSRange totalRange = NSMakeRange(0, [_string length]);
    // Save the execution result
    _matches = [regex matchesInString:_string options:0 range:totalRange];
}Copy the code
This method saves the corresponding emoticon imagename and relative range in the dictionary based on the result of the pair (e.g. @{imagename:{).0.4}}) and store these dictionaries in an array, which are then used in 'setResultStrUseReplace' one by one to replace + (void)setImageDataArray {

    NSMutableArray *imageDataArray = [NSMutableArray array];
    // Iterate over the result
    for (int i = (int)_matches.count - 1; i >= 0; i --) {

        NSMutableDictionary *record = [NSMutableDictionary dictionary];

        LiuqsTextAttachment *attachMent = [[LiuqsTextAttachment alloc]init];

        attachMent.bounds = CGRectMake(0, -4, _emotionSize.height, _emotionSize.height);

        NSTextCheckingResult *match = [_matches objectAtIndex:i];

        NSRange matchRange = [match range];

        NSString *tagString = [_string substringWithRange:matchRange];

        NSString *imageName = [_emojiImages objectForKey:tagString];

        if (imageName == nil || imageName.length= =0) continue;

        [record setObject:[NSValue valueWithRange:matchRange] forKey:@"range"];

        [record setObject:imageName forKey:@"imageName"];

        [imageDataArray addObject:record];
    }
    _imageDataArray = imageDataArray;
}Copy the code
This method is the final iteration of the substitution process. Note that:Replace from back to front, otherwise there will be a problem.Cause: the whole range of characters is changed, so the range in the dictionary array is not correct, and may cause an out-of-bounds crash! + (void)setResultStrUseReplace{

    NSMutableAttributedString *result = _attStr;

    for (int i = 0; i < _imageDataArray.count ; i ++) {

        NSRange range = [_imageDataArray[i][@"range"] rangeValue];

        NSDictionary *imageDic = [_imageDataArray objectAtIndex:i];

        NSString *imageName = [imageDic objectForKey:@"imageName"];

        NSString *path = [[NSBundle mainBundle]pathForResource:imageName ofType:@"gif"];

        NSData *data = [NSData dataWithContentsOfFile:path];

        YYImage *image = [YYImage imageWithData:data scale:2];

        image.preloadAllAnimatedImageFrames = YES;

        YYAnimatedImageView *imageView = [[YYAnimatedImageView alloc] initWithImage:image];

        NSMutableAttributedString *attachText = [NSMutableAttributedString yy_attachmentStringWithContent:imageView contentMode:UIViewContentModeCenter attachmentSize:imageView.frame.size alignToFont:_font alignment:YYTextVerticalAlignmentCenter];

        [result replaceCharactersInRange:range withAttributedString:attachText];
    }
    _resultStr = result;
}Copy the code

That’s basically it! YYText has a lot of powerful features that you can extend at will. Here, only the imageView attachment is used. May not be comprehensive enough, specific details can see the project demo! It is hard to write. If it is useful to you, I hope you can support it. Remember to give a STAR! If you have any comments or suggestions, please feel free to contact me at [email protected]