您的当前位置:首页UITextView自适应高度,同时可以设置最大高度, 类似于Q

UITextView自适应高度,同时可以设置最大高度, 类似于Q

2024-12-13 来源:哗拓教育

需求:

  • 在textView输入的内容的时候,需要textView根据内容自适应高度;
  • 同时设置最大高度, 当textView高度大于设置的最大高度的时候, 设置textView的高度为最大高度;
  • textView高度小于最大高度的时候不允许让其内容滑动, 否则让其内容可以滑动
  • 可以输入表情, 同时也可以将输入框的带有表情的内容转换成字符串;

自适应高度:

在UITextView的代理方法- (void)textViewDidChange:(UITextView *)textView;中实现

- (void)textViewDidChange:(UITextView *)textView {
    ///> 计算文字高度
    CGFloat height = ceilf([textView sizeThatFits:CGSizeMake(textView.frame.size.width, MAXFLOAT)].height);
    if (height >= 100) {
        textView.scrollEnabled = YES;   ///> 当textView高度大于等于最大高度的时候让其可以滚动
        height = 100;                   ///> 设置最大高度
    }
    ///> 重新设置frame, textView往上增长高度
    textView.frame = CGRectMake(0, CGRectGetMaxY(textView.frame) - height, self.view.bounds.size.width, height);
    [textView layoutIfNeeded];
}

这样就可以实现textView高度的自适应了, 同时高度一直小于100

输入表情(不支持拷贝粘贴)

创建表情按钮, 这里创建了一个view, 添加了四个表情按钮, 作为键盘的辅助视图

///> 创建键盘辅助视图
- (void)creatKeyBordInputAccessoryView {
    UIView *inputAccessoryView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, 40)];
    inputAccessoryView.backgroundColor = [UIColor redColor];
    ///> 循环创建按钮
    for (int i = 0; i < 4; i ++) {
        UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem];
        button.tag = 2000 + i;
        ///> 设置表情图片作为按钮的背景图片
        [button setBackgroundImage:[self imageWithName:[NSString stringWithFormat:@"02%d@2x",i]] forState:UIControlStateNormal];
        button.frame = CGRectMake(i * (inputAccessoryView.frame.size.width / 4.0) + inputAccessoryView.frame.size.width / 8.0 - 20, 0, 40, 40);
        [button addTarget:self action:@selector(handleButtonAction:) forControlEvents:UIControlEventTouchUpInside];
        [inputAccessoryView addSubview:button];
    }
    self.textView.inputAccessoryView = inputAccessoryView;
}
///> 根据表情名字, 返回对应的表情图片
- (UIImage *)imageWithName:(NSString *)name {
    ///> 获取表情包路径
    NSBundle *bundle = [NSBundle bundleWithPath:[[NSBundle mainBundle] pathForResource:@"EmoticonQQ" ofType:@"bundle"]];
    ///> 获取name对应的表情图片路径
    NSString *imagePath = [bundle pathForResource:name ofType:@"png"];
    ///> 获取name对应的表情图片
    UIImage *image = [UIImage imageWithContentsOfFile:imagePath];
    return image;
}

使用NSTextAttachment在文字中插入图片, 进入系统API会发现NSTextAttachment有一个image属性, 还有一个bounds属性

8B890AF5-3ACD-42D0-AF9A-6017C12E289A.png
image就是用来设置图片的, bounds用来设置大小及位置, 接下来就要用到属性字符串NSAttributedString系统对它提供了一个类方法, 参数是NSTextAttachment类型的, 不多说上代码
///> 辅助视图上按钮事件, 向textView中插入表情图片
- (void)handleButtonAction:(UIButton *)sender {
    // YBTextAttachment是继承自NSTextAttachment的一个类, 添加了一个emojName属性
    // 可以在这个类里边重写- (CGRect)attachmentBoundsForTextContainer: proposedLineFragment: glyphPosition: characterIndex: 方法可实现图片和文字大小一致
    ///> 设置装载图片的控件(在这里理解成一个图片容器控件,这样好理解)
    YBTextAttachment *attachment = [[YBTextAttachment alloc] init];
    ///> 将按钮上的图片插入到属性字符串中
    attachment.image = sender.currentBackgroundImage;
    ///> 设置表情图片控件bounds
    attachment.bounds = CGRectMake(0, 0, 20, 20);
    ///> 设置表情文字, 在表情转换字符串的时候要用
    switch (sender.tag) {
        case 2000: attachment.emojName = @"[/色]";     break;
        case 2001: attachment.emojName = @"[/害羞]";    break;
        case 2002: attachment.emojName = @"[/得意]";    break;
        case 2003: attachment.emojName = @"[/吐]";     break;
        default:
            break;
    }
    ///> 创建属性字符串, 使用装载有表情图片NSTextAttachment对象初始化创建属性字符串
    NSAttributedString *attrinbutedStr = [NSAttributedString attributedStringWithAttachment:attachment];
    ///> 在光标位置插入带有图片的属性字符串
    [self.textView.textStorage insertAttributedString:attrinbutedStr atIndex:self.textView.selectedRange.location];
    ///> 设置光标位置, 将光标向后移动
    self.textView.selectedRange = NSMakeRange(self.textView.selectedRange.location + 1, self.textView.selectedRange.length);
    ///> 输入的时候调用textView代理方法, 更新textView以及文字内容
    [self textViewDidChange:self.textView];
}

这样在点击键盘辅助视图上的表情按钮的时候就可以在输入框里边显示表情了

将输入框带有表情的内容转换成字符串

对textView的textStorage进行遍历, 找到NSTextAttachment对象进行替换,然后将替换结果显示在label上

///> 按钮的点击方法, 将textView上的文字和表情以文本字符串的形式显示在label上
- (void)getTextViewString:(UIButton *)sender {
    ///> self.textView.textStorage.string是只读的, 在这里进行拷贝一下
    NSMutableString *planString = [self.textView.textStorage.string mutableCopy];
    ///> 在block里边修改局部变量需要使用__block修饰
    __block NSUInteger base = 0;
    // 遍历的是 NSTextAttachment对象, 找到以后进行替换
    [self.textView.textStorage enumerateAttribute:NSAttachmentAttributeName
                                          inRange:NSMakeRange(0, self.textView.textStorage.length)
                                          options:0
                                       usingBlock:^(YBTextAttachment *value, NSRange range, BOOL * _Nonnull stop) {
                                           ///> 注意这里的range是遍历value在原来字符串的range, 替换过后, range要相应的修改
                                           if (value) {
                                               [planString replaceCharactersInRange:NSMakeRange(range.location + base, range.length) withString:value.emojName];
                                               ///> 输入表情的时候设置的表情名字在这里用到, 表情的长度为1 
                                               base = base + value.emojName.length - 1;
                                           }
                                       }];

    self.label.text = planString;
}

将带有表情文字的字符串转换成属性字符串

这里将label上的内容转换成属性字符串, 显示在textView上, 如果有表情文字,使其显示对应的表情图片, 使用正则表达式将表情匹配出来进行替换, 废话不多说, 上代码:

- (void)getLabelTextToTextView:(UIButton *)sender {
    ///> 使用正则将表情文字匹配出来, 然后将其替换成表情图片
    NSString *str = self.label.text;
    NSMutableAttributedString *attrinbutedStr = [[NSMutableAttributedString alloc] initWithString:str];
    NSString *regexStr = @"\\[/\\w+\\]";
    NSError *error = nil;
    NSRegularExpression *regExp = [[NSRegularExpression alloc] initWithPattern:regexStr options:NSRegularExpressionCaseInsensitive error:&error];
    if (!error && str != nil && str.length != 0) {
        //> 对str的匹配结果
        NSArray *resultArr = [regExp matchesInString:str options:0 range:NSMakeRange(0, str.length)];
        NSUInteger base = 0;
        //> 遍历匹配结果进行替换
        for (NSTextCheckingResult *result in resultArr) {
            ///> 要替换的字符串
            NSString *emojStr = [str substringWithRange:result.range];
            ///> 判断表情包里边是否包含该表情
            BOOL isContain = [self isContainObjectWith:emojStr];
            if (isContain) {    ///> 如果表情包里边有该表情, 则进行替换, 如果没有不进行操作
                YBTextAttachment *attachment = [YBTextAttachment new];
                ///> 根据表情文字获取对应表情图片
                attachment.image = [self imageWithName:self.emojDict[emojStr]];
                attachment.bounds = CGRectMake(0, 0, 20, 20);
                ///> 设置表情对应的文字
                attachment.emojName = emojStr;
                ///> 创建包含表情图片的属性字符串
                NSAttributedString *tempAttributedStr = [NSAttributedString attributedStringWithAttachment:attachment];
                ///> 将包含表情图片的属性字符串将表情文字替换掉, 这里要注意range, 表情的长度为1
                [attrinbutedStr replaceCharactersInRange:NSMakeRange(result.range.location - base, result.range.length) withAttributedString:tempAttributedStr];
                base = base + emojStr.length - 1;
            }
        }
    }
    self.textView.attributedText = attrinbutedStr;
    [self textViewDidChange:self.textView];
}
///> 判断字典(表情包)所有key中是否含有相同的字符串(表情文字)
- (BOOL)isContainObjectWith:(NSString *)str {
    BOOL isContain = NO;
    for (NSString *obj in self.emojDict.allKeys) {
        if ([obj isEqualToString:str]) {
            isContain = YES;
            break;  ///> 如果有则停止循环
        }
    }
    return isContain;
}

这样就可以实现基本的效果了, 这样会发现输入的文字大小或许不是我们想要的, 那么在UITextView代理方法- (void)textViewDidChange:(UITextView *)textView中添加如下代码, 这里注意, 下边代码要写在计算高度之前, 不然计算textView高度会有错误;

    NSRange wholeRange = NSMakeRange(0, textView.textStorage.length);
    [textView.textStorage removeAttribute:NSFontAttributeName range:wholeRange];
    ///> 这里将字体大小设置为20, 和表情的高度一样, 这样看起来就好多了
    [textView.textStorage addAttribute:NSFontAttributeName value:[UIFont systemFontOfSize:20.0] range:wholeRange];

最终效果:

效果图.gif
显示全文