美文网首页
iOS 文本添加“更多”样式

iOS 文本添加“更多”样式

作者: JasonFive | 来源:发表于2021-01-13 17:47 被阅读0次

在网上找了很多有关计算文本文字和文本高度的方法,但是显示在textview上时,计算的文本每一行的文字和实际显示的不一样,在此记录一下这几天踩的坑。主要还是在textview的设置上有一些影响因素

在这儿也分享一篇对我有帮助的文章 UITextView输入时高度自适应&文本边距的设置

先看任务

/**
 * 目的:给文本添加更多,点击更多,显示更多内容
 *
 * 操作:一段文本,首次显示,最多出现3行。若超过3行,则出现更多按钮,点击更多,显示全部内容。
 * 如果内容超过10行,则显示10行,其余内容"..."处理。显示更多内容后,“更多”按钮变为“收起”!
 *
 * 思考:
 * 1、如果小于3行,直接显示内容
 * 2、如果大于3行,显示3行,则需要显示“更多按钮”
 * 3、如果大于3行,小于10行,显示最多10行,直接添加“收起”按钮即可
 * 4、如果大于10行,显示最多10行,需要添加“...”和“收起”按钮
 */

效果


更多.png 收起.png

相关控件

#import "ViewController.h"
#import <CoreText/CoreText.h>

@interface ViewController ()<UITextViewDelegate>

@property (nonatomic, strong) UITextView *textView;
@property (nonatomic, copy)   NSString *topicString;
@property (nonatomic, copy)   NSString *textString;
@property (nonatomic, assign) BOOL showMore;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    self.view.backgroundColor = [UIColor blackColor];
    
    CGFloat screenSizeH = [UIScreen mainScreen].bounds.size.height;
    CGFloat screenSizeW = [UIScreen mainScreen].bounds.size.width;
    
    self.textView.frame = CGRectMake(15, screenSizeH-300, screenSizeW-110, 250);
    [self.view addSubview:self.textView];
    
    [self showTestText:_showMore];
}

关键方法

/**
 * 目的:给文本添加更多,点击更多,显示更多内容
 *
 * 操作:一段文本,首次显示,最多出现3行。若超过3行,则出现更多按钮,点击更多,显示全部内容。
 * 如果内容超过10行,则显示10行,其余内容"..."处理。显示更多内容后,“更多”按钮变为“收起”!
 *
 * 思考:
 * 1、如果小于3行,直接显示内容
 * 2、如果大于3行,显示3行,则需要显示“更多按钮”
 * 3、如果大于3行,小于10行,显示最多10行,直接添加“收起”按钮即可
 * 4、如果大于10行,显示最多10行,需要添加“...”和“收起”按钮
 */
- (void)showTestText:(BOOL)showMore {
    
    NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
    [paragraphStyle setLineSpacing:4]; // 设置行间距
    
    // 需要显示的富文本,用这个文本去计算每行需要显示的内容和需要的frame
    NSMutableAttributedString *contentAttributeString = [[NSMutableAttributedString alloc] init];
    
    // 标题富文本
    NSMutableAttributedString *topciAttributeString = [[NSMutableAttributedString alloc] initWithString:self.topicString];
    [topciAttributeString addAttributes:@{
        NSParagraphStyleAttributeName:paragraphStyle,
        NSFontAttributeName:[UIFont systemFontOfSize:16 weight:UIFontWeightBold],
        NSForegroundColorAttributeName:[UIColor orangeColor],
        NSLinkAttributeName:@"topic://",
    } range:NSMakeRange(0, topciAttributeString.length)];
    [contentAttributeString appendAttributedString:topciAttributeString];
    
    // 内容富文本
    NSMutableAttributedString *textAttributeString = [[NSMutableAttributedString alloc] initWithString:self.textString];
    [textAttributeString addAttributes:@{
        NSParagraphStyleAttributeName:paragraphStyle,
        NSFontAttributeName:[UIFont systemFontOfSize:16 weight:UIFontWeightRegular],
        NSForegroundColorAttributeName:[UIColor grayColor],
    } range:NSMakeRange(0, textAttributeString.length)];
    [contentAttributeString appendAttributedString:textAttributeString];
    
    // 根据textview的宽度或者指定的宽度计算需要显示的高度
    CGSize contentSize = [contentAttributeString boundingRectWithSize:CGSizeMake(self.textView.bounds.size.width, 1000)
                                                              options:NSStringDrawingTruncatesLastVisibleLine
                                                                    | NSStringDrawingUsesLineFragmentOrigin
                                                                    | NSStringDrawingUsesFontLeading
                                                              context:nil].size;
    NSString *originString = [NSString stringWithFormat:@"%@%@",self.topicString,self.textString];
    NSString *memoString = [self getSeparatedLinesFromText:originString
                                                  maxLines:showMore ? 10 : 3
                                                largeLines:10
                                                    attStr:contentAttributeString
                                                      rect:CGRectMake(0, 0, contentSize.width, 10000)];
    
    NSMutableAttributedString *memoAttributeString = [[NSMutableAttributedString alloc] initWithString:memoString];
    
    NSRange topicRange = [memoString rangeOfString:self.topicString];
    [memoAttributeString addAttributes:@{
        NSParagraphStyleAttributeName:paragraphStyle,
        NSFontAttributeName:[UIFont systemFontOfSize:16 weight:UIFontWeightBold],
        NSForegroundColorAttributeName:[UIColor orangeColor],
        NSLinkAttributeName:@"topic://",
    } range:topicRange];
    NSRange textRange = NSMakeRange(topicRange.length, memoString.length-topicRange.length);
    [memoAttributeString addAttributes:@{
        NSParagraphStyleAttributeName:paragraphStyle,
        NSFontAttributeName:[UIFont systemFontOfSize:16 weight:UIFontWeightRegular],
        NSForegroundColorAttributeName:[UIColor grayColor],
    } range:textRange];
    // 然后再 根据textview的宽度或者指定的宽度 重新 计算 计算出的字符 需要显示的高度
    contentSize = [memoAttributeString boundingRectWithSize:CGSizeMake(self.textView.bounds.size.width, 1000)
                                                       options:NSStringDrawingTruncatesLastVisibleLine
                                                            | NSStringDrawingUsesLineFragmentOrigin
                                                            | NSStringDrawingUsesFontLeading
                                                       context:nil].size;
    
    if (contentSize.height > 23 * 3 || ![memoString isEqualToString:originString]) {
        
        NSMutableAttributedString *moreAttributeString = [[NSMutableAttributedString alloc] initWithString:showMore ? @"收起" : @"更多"];
        [moreAttributeString addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:NSMakeRange(0, moreAttributeString.length)];
        [moreAttributeString addAttribute:NSFontAttributeName value:[UIFont systemFontOfSize:16 weight:UIFontWeightRegular]
                                     range:NSMakeRange(0, moreAttributeString.length)];
        [moreAttributeString addAttribute:NSForegroundColorAttributeName value:[UIColor orangeColor] range:NSMakeRange(0, moreAttributeString.length)];
        [moreAttributeString addAttribute:NSLinkAttributeName value:@"more://" range:NSMakeRange(0, moreAttributeString.length)];
        [memoAttributeString appendAttributedString:moreAttributeString];
        
        NSTextAttachment *textAttachment = [[NSTextAttachment alloc] init];
        textAttachment.image = [UIImage imageNamed:showMore ? @"ic_select_pre" : @"ic_select_nor"];
        textAttachment.bounds = CGRectMake(0, 0, 13, 13);
        NSAttributedString *attacAttibuteString = [NSAttributedString attributedStringWithAttachment:textAttachment];
        NSMutableAttributedString *attacMutableAttibuteString = [[NSMutableAttributedString alloc] initWithAttributedString:attacAttibuteString];
        [attacMutableAttibuteString addAttribute:NSLinkAttributeName value:@"more://" range:NSMakeRange(0, attacMutableAttibuteString.length)];
        [memoAttributeString appendAttributedString:attacMutableAttibuteString];
        
        contentSize = [memoAttributeString boundingRectWithSize:CGSizeMake(self.textView.bounds.size.width, 1000)
                                                                  options:NSStringDrawingTruncatesLastVisibleLine
                                                                        | NSStringDrawingUsesLineFragmentOrigin
                                                                        | NSStringDrawingUsesFontLeading
                                                                  context:nil].size;
    }
    
    CGFloat textViewHeight = 69; // 显示文字3行69够了
    if (contentSize.height > 40) {
        textViewHeight = (!showMore ? MAX(contentSize.height, 69) : 69) + (self.textView.bounds.size.width-contentSize.width) ; // 但是如果是3行英文可能还不够,需要去一个最大值
    }
    self.textView.textContainer.maximumNumberOfLines = 3;
    
    if (showMore && contentSize.height > textViewHeight) {
        textViewHeight = contentSize.height + (self.textView.bounds.size.width-contentSize.width) + 8;
        if (contentSize.height > 23*10) {
            textViewHeight = 230 + (self.textView.bounds.size.width-contentSize.width);
        }
        self.textView.textContainer.maximumNumberOfLines = 10;
    }
    
    self.textView.attributedText = memoAttributeString;
    self.textView.frame = CGRectMake(15, self.textView.frame.origin.y, self.textView.bounds.size.width, textViewHeight);
    self.showMore = !_showMore;
}

每行显示的文本


/// @param text : 原文本内容
/// @param maxLines : 最多显示多少行
/// @param largeLines : 全部内容显示多少行
/// @param attStr : 需要显示的富文本
/// @param rect : 显示的区域
/// @return 用于显示的文本
- (NSString *)getSeparatedLinesFromText:(NSString *)text maxLines:(NSInteger)maxLines largeLines:(NSInteger)largeLines attStr:(NSMutableAttributedString *)attStr rect:(CGRect)rect {
    
//    NSString *text = [textV text];
//    UIFont *font = [textV font];
//    CGRect rect = [textV bounds];
//    CTFontRef myFont =   CTFontCreateWithName((__bridge CFStringRef)([font fontName]), [font pointSize], NULL);
//
//    /* 在 Core Text 中使用 NSAttributedString 而不是NSString,NSAttributedString 是一个非常强大的 NSString 派生类,
//     它允许你对文本应用格式化属性。 现在我们还没有用到格式化,这里仅仅使用纯文本。 */
//    NSMutableAttributedString *attStr = [[NSMutableAttributedString alloc] initWithString:text];
//    [attStr addAttribute:(NSString *)kCTFontAttributeName value:(__bridge id)myFont range:NSMakeRange(0, attStr.length)];
    
    
    /* CTFramesetter 是使用 Core Text 绘制时最重要的类。它管理您的字体引用和文本绘制帧。
     目前您需要了解 CTFramesetterCreateWithAttributedString 通过应用属性化文本创建 CTFramesetter 。
     本节中,在 framesetter 之后通过一个所选的文本范围(这里我们选择整个文本)与需要绘制到的矩形路径创建一个帧。*/
    CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)attStr);
    
    /* 这里你需要创建一个用于绘制文本的路径区域。Mac 上的 Core Text 支持矩形图形等不同形状,
     但在 iOS 上只支持矩形。在这个示例中,你将通过 self.bounds 使用整个视图矩形区域创建 CGPath 引用。  */
    CGMutablePathRef path = CGPathCreateMutable();
    
    CGPathAddRect(path, NULL, CGRectMake(0,0,rect.size.width,MAXFLOAT));
    
    //CTFrameDraw 将 frame 描述到设备上下文。
    CTFrameRef frame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, 0), path, NULL);
    //需要多少行
    NSArray *lines = (__bridge NSArray *)CTFrameGetLines(frame);
    
    //不需要添加“...” 直接返回
    if (lines.count <= maxLines) return text;
    if (lines.count <= maxLines && maxLines == largeLines) return text;
    
    //获取到需要添加...的那一行内容
    NSInteger indexLine = maxLines - 1;
    CTLineRef lineRef = (__bridge CTLineRef )lines[indexLine];
    CFRange lineRange = CTLineGetStringRange(lineRef);
    NSRange range = NSMakeRange(lineRange.location, lineRange.length);
    NSString *lineString = [text substringWithRange:range];
    
    //移除最后几个字节的文本
    NSInteger replaceLength = 0;
    while (replaceLength < 8) {
        NSString *lastChar = [lineString substringWithRange:NSMakeRange(lineString.length-1, 1)];
        if ([self isChinese:lastChar]) {
            replaceLength += 2;
        } else {
            replaceLength += 1;
        }
        lineString = [lineString stringByReplacingCharactersInRange:NSMakeRange(lineString.length-1, 1) withString:@""];
    }
    lineString = [NSString stringWithFormat:@"%@...",lineString]; // 添加...
    
    NSString *memoStr = @"";
    for (int i=0; i < maxLines-1; i++) {
        lineRef = (__bridge CTLineRef )lines[i];
        lineRange = CTLineGetStringRange(lineRef);
        range = NSMakeRange(lineRange.location, lineRange.length);
        memoStr = [NSString stringWithFormat:@"%@%@",memoStr,[text substringWithRange:range]];
    }
    memoStr = [NSString stringWithFormat:@"%@%@",memoStr,lineString];
    
    CFRelease(frame); // Core Frame 下的对象需要自己释放
    CFRelease(path);
    CFRelease(frameSetter);
    
    return memoStr;
}

判断是否是中文

// 判断是否是中文
- (BOOL)isChinese:(NSString *)c {
    int strlength = 0;
    char *p = (char *)[c cStringUsingEncoding:NSUnicodeStringEncoding];
    for (int i = 0; i < [c lengthOfBytesUsingEncoding:NSUnicodeStringEncoding]; i++) {
        if (*p) {
            p++;
            strlength++;
        } else
            p++;
    }
    return (strlength/2 == 1) ? YES : NO;
}

属性设置

#pragma mark - lazy load -

- (UITextView *)textView {
    if (!_textView) {
        _textView = [UITextView new];
        _textView.delegate = self;
        _textView.editable = NO;    // 禁止可编辑
        _textView.selectable = YES; // 允许点击事件
        _textView.scrollEnabled = NO; // 禁止滑动,如果允许滑动,那下面设置 contentInset 和 textContainerInset 就多余了
        _textView.bounces = NO;
        _textView.contentInset = UIEdgeInsetsMake(4, 0, 0, -10); // 设置内容边距 ,设置为-10是为了配合 textContainerInset ,因为有时候因为右边边距不够换行 而造成计算的行数和内容与实际显示的内容不符
        _textView.textContainerInset = UIEdgeInsetsMake(4, 0, 0, 0); // 设置文本边距
        _textView.font = [UIFont systemFontOfSize:16 weight:UIFontWeightRegular];
        _textView.textContainer.lineBreakMode = NSLineBreakByWordWrapping; // 设置为 NSLineBreakByWordWrapping 是为了后面在合适的位置添加“更多”文本
        [_textView.textContainer setLineFragmentPadding:0.01]; // 设置左边距为0,不然显示跟计算的不正确
        // [_textView.layoutManager setAllowsNonContiguousLayout:YES]; // 设置
        _textView.linkTextAttributes = @{
            NSForegroundColorAttributeName : [UIColor orangeColor],
            NSFontAttributeName:[UIFont systemFontOfSize:16 weight:UIFontWeightRegular],
        }; // 设置link的属性
    }
    return _textView;
}

- (NSString *)topicString {
    if (!_topicString) {
        _topicString = @"#这个是标题#";
    }
    return _topicString;
}

- (NSString *)textString {
    if (!_textString) {
        _textString = @"今天是jason的生日,我准备送她一部iPhone手机,顺便吟诗一首:君不见黄河之水天上来,奔流到海不复回。high hope!君不见高堂明镜悲白发,千里冰丝暮成雪。shr!仰天大笑出门去,我辈岂是蓬蒿人。buibuibui!床前明月光,月初钱包光。仰天大笑出门去,我辈岂是蓬蒿人。buibuibui!床前明月光,月初钱包光。";
    }
    return _textString;
}

代理方法

#pragma mark - UITextViewDelegate -

- (BOOL)textView:(UITextView *)textView shouldInteractWithURL:(NSURL *)URL inRange:(NSRange)characterRange {
    
    if ([[URL scheme] isEqualToString:@"topic"]) {
        NSLog(@"shouldInteractWithURL - topic");
        [self showTestText:_showMore];
        return NO;
    } else if ([[URL scheme] isEqualToString:@"more"]) {
        [self showTestText:_showMore];
        return NO;
    }
    return NO;
}

这里面代码片段组合起来就是一个完整的代码,里面的一些数据设置,如果与自己不一样,可以尝试去调整一下。

相关文章

网友评论

      本文标题:iOS 文本添加“更多”样式

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