iOS 文本添加“更多”樣式

在網(wǎng)上找了很多有關(guān)計算文本文字和文本高度的方法,但是顯示在textview上時,計算的文本每一行的文字和實際顯示的不一樣,在此記錄一下這幾天踩的坑。主要還是在textview的設(shè)置上有一些影響因素

在這兒也分享一篇對我有幫助的文章 UITextView輸入時高度自適應(yīng)&文本邊距的設(shè)置

先看任務(wù)

/**
 * 目的:給文本添加更多,點擊更多,顯示更多內(nèi)容
 *
 * 操作:一段文本,首次顯示,最多出現(xiàn)3行。若超過3行,則出現(xiàn)更多按鈕,點擊更多,顯示全部內(nèi)容。
 * 如果內(nèi)容超過10行,則顯示10行,其余內(nèi)容"..."處理。顯示更多內(nèi)容后,“更多”按鈕變?yōu)椤笆掌稹保? *
 * 思考:
 * 1、如果小于3行,直接顯示內(nèi)容
 * 2、如果大于3行,顯示3行,則需要顯示“更多按鈕”
 * 3、如果大于3行,小于10行,顯示最多10行,直接添加“收起”按鈕即可
 * 4、如果大于10行,顯示最多10行,需要添加“...”和“收起”按鈕
 */

效果


更多.png
收起.png

相關(guān)控件

#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];
}

關(guān)鍵方法

/**
 * 目的:給文本添加更多,點擊更多,顯示更多內(nèi)容
 *
 * 操作:一段文本,首次顯示,最多出現(xiàn)3行。若超過3行,則出現(xiàn)更多按鈕,點擊更多,顯示全部內(nèi)容。
 * 如果內(nèi)容超過10行,則顯示10行,其余內(nèi)容"..."處理。顯示更多內(nèi)容后,“更多”按鈕變?yōu)椤笆掌稹保? *
 * 思考:
 * 1、如果小于3行,直接顯示內(nèi)容
 * 2、如果大于3行,顯示3行,則需要顯示“更多按鈕”
 * 3、如果大于3行,小于10行,顯示最多10行,直接添加“收起”按鈕即可
 * 4、如果大于10行,顯示最多10行,需要添加“...”和“收起”按鈕
 */
- (void)showTestText:(BOOL)showMore {
    
    NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
    [paragraphStyle setLineSpacing:4]; // 設(shè)置行間距
    
    // 需要顯示的富文本,用這個文本去計算每行需要顯示的內(nèi)容和需要的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];
    
    // 內(nèi)容富文本
    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];
    
    // 根據(jù)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];
    // 然后再 根據(jù)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 : 原文本內(nèi)容
/// @param maxLines : 最多顯示多少行
/// @param largeLines : 全部內(nèi)容顯示多少行
/// @param attStr : 需要顯示的富文本
/// @param rect : 顯示的區(qū)域
/// @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 派生類,
//     它允許你對文本應(yīng)用格式化屬性。 現(xiàn)在我們還沒有用到格式化,這里僅僅使用純文本。 */
//    NSMutableAttributedString *attStr = [[NSMutableAttributedString alloc] initWithString:text];
//    [attStr addAttribute:(NSString *)kCTFontAttributeName value:(__bridge id)myFont range:NSMakeRange(0, attStr.length)];
    
    
    /* CTFramesetter 是使用 Core Text 繪制時最重要的類。它管理您的字體引用和文本繪制幀。
     目前您需要了解 CTFramesetterCreateWithAttributedString 通過應(yīng)用屬性化文本創(chuàng)建 CTFramesetter 。
     本節(jié)中,在 framesetter 之后通過一個所選的文本范圍(這里我們選擇整個文本)與需要繪制到的矩形路徑創(chuàng)建一個幀。*/
    CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)attStr);
    
    /* 這里你需要創(chuàng)建一個用于繪制文本的路徑區(qū)域。Mac 上的 Core Text 支持矩形圖形等不同形狀,
     但在 iOS 上只支持矩形。在這個示例中,你將通過 self.bounds 使用整個視圖矩形區(qū)域創(chuàng)建 CGPath 引用。  */
    CGMutablePathRef path = CGPathCreateMutable();
    
    CGPathAddRect(path, NULL, CGRectMake(0,0,rect.size.width,MAXFLOAT));
    
    //CTFrameDraw 將 frame 描述到設(shè)備上下文。
    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;
    
    //獲取到需要添加...的那一行內(nèi)容
    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];
    
    //移除最后幾個字節(jié)的文本
    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;
}

屬性設(shè)置

#pragma mark - lazy load -

- (UITextView *)textView {
    if (!_textView) {
        _textView = [UITextView new];
        _textView.delegate = self;
        _textView.editable = NO;    // 禁止可編輯
        _textView.selectable = YES; // 允許點擊事件
        _textView.scrollEnabled = NO; // 禁止滑動,如果允許滑動,那下面設(shè)置 contentInset 和 textContainerInset 就多余了
        _textView.bounces = NO;
        _textView.contentInset = UIEdgeInsetsMake(4, 0, 0, -10); // 設(shè)置內(nèi)容邊距 ,設(shè)置為-10是為了配合 textContainerInset ,因為有時候因為右邊邊距不夠換行 而造成計算的行數(shù)和內(nèi)容與實際顯示的內(nèi)容不符
        _textView.textContainerInset = UIEdgeInsetsMake(4, 0, 0, 0); // 設(shè)置文本邊距
        _textView.font = [UIFont systemFontOfSize:16 weight:UIFontWeightRegular];
        _textView.textContainer.lineBreakMode = NSLineBreakByWordWrapping; // 設(shè)置為 NSLineBreakByWordWrapping 是為了后面在合適的位置添加“更多”文本
        [_textView.textContainer setLineFragmentPadding:0.01]; // 設(shè)置左邊距為0,不然顯示跟計算的不正確
        // [_textView.layoutManager setAllowsNonContiguousLayout:YES]; // 設(shè)置
        _textView.linkTextAttributes = @{
            NSForegroundColorAttributeName : [UIColor orangeColor],
            NSFontAttributeName:[UIFont systemFontOfSize:16 weight:UIFontWeightRegular],
        }; // 設(shè)置link的屬性
    }
    return _textView;
}

- (NSString *)topicString {
    if (!_topicString) {
        _topicString = @"#這個是標題#";
    }
    return _topicString;
}

- (NSString *)textString {
    if (!_textString) {
        _textString = @"今天是jason的生日,我準備送她一部iPhone手機,順便吟詩一首:君不見黃河之水天上來,奔流到海不復回。high hope!君不見高堂明鏡悲白發(fā),千里冰絲暮成雪。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;
}

這里面代碼片段組合起來就是一個完整的代碼,里面的一些數(shù)據(jù)設(shè)置,如果與自己不一樣,可以嘗試去調(diào)整一下。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容