iOS多行文本的展開(kāi)/全文和收起(UITextView)

2021.04.25更新:創(chuàng)建了文章。

媒體/列表類的多行文本展示問(wèn)題

在iOS的開(kāi)發(fā)過(guò)程中,我們?cè)谶M(jìn)行文本展示的功能實(shí)現(xiàn)時(shí),經(jīng)常會(huì)遇到文本過(guò)長(zhǎng)的情況,如果我們使用的是UITableView,那么文本在Cell中展示時(shí)如果全部展示完全的話,那么可能出現(xiàn)整屏只能展示一個(gè)Cell的情況。此時(shí)我們需要對(duì)多行文本進(jìn)行分割,在前部分的末尾加上“展開(kāi)/全文”的按鈕,用戶在點(diǎn)擊此按鈕后,Cell再展開(kāi)進(jìn)行全部文本的展示,展開(kāi)后在全部文本的末尾處,還需要一個(gè)“收起”的按鈕,用戶點(diǎn)擊后Cell再次回到收起的狀態(tài)。


soul APP動(dòng)畫(huà)演示.gif

如何實(shí)現(xiàn)

1.模型準(zhǔn)備

對(duì)于展示數(shù)據(jù)的模型,我們需要添加除了content(內(nèi)容)外至少3個(gè)額外屬性

//文字內(nèi)容的實(shí)際高度
@property (nonatomic, assign) CGFloat titleActualH;

//文字內(nèi)容的最大高度,具體的數(shù)值是 一行文本的高度*期望的最大顯示行數(shù)
@property (nonatomic, assign) CGFloat titleMaxH;

//內(nèi)容是否展開(kāi)(默認(rèn)不設(shè)置,都是NO,收起狀態(tài))
@property (nonatomic, assign) BOOL isOpen;

模型初始化時(shí),在content屬性的set方法中,對(duì)添加的屬性進(jìn)行賦值

- (void)setContent:(NSString *)content {
    _content = content;
    if ([NSString isEmptyString:content]) {
        self.titleActualH = 0;
        self.titleMaxH = 0;
    } else {
        NSUInteger numCount = 5; //這是cell收起狀態(tài)下期望展示的最大行數(shù)
        NSString *str = @"這是一行用來(lái)計(jì)算高度的文本"; //這行文本也可以為一個(gè)字,但不能太長(zhǎng)
        CGFloat W = kScreenWidth-30; //這里是文本展示的寬度
        self.titleActualH = [content boundingRectWithSize:CGSizeMake(W, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:kRegularFont(14)} context:nil].size.height;
        self.titleMaxH = [str boundingRectWithSize:CGSizeMake(W, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:kRegularFont(14)} context:nil].size.height*numCount;
    }
}

2.Cell賦值

cell中用來(lái)展示文本的控件選用UITextView,再用富文本的方式進(jìn)行內(nèi)容的填充以及點(diǎn)擊的響應(yīng)。PS:此處也可以用UILabel+UIButton的方式,但是實(shí)現(xiàn)起來(lái)太麻煩,是一種很低效的解決方案。
在cell的賦值方法中,根據(jù)文本實(shí)際高度和最大高度的比較,動(dòng)態(tài)的現(xiàn)實(shí)“收起”按鈕(文本使用富文本的方式實(shí)現(xiàn))。

- (void)setupCellData:(LWYMyFavouriteModel *)model {
    
    NSString *suffixStr = @""; //添加的后綴按鈕文本(收起或展開(kāi))
    NSString *contentStr = model.content;
    CGFloat H = model.titleActualH; //文本的高度,默認(rèn)為實(shí)際高度
    
    if (model.titleActualH > model.titleMaxH) {
        //文本實(shí)際高度>最大高度,需要添加后綴
        if (model.isOpen) {
            //文本已經(jīng)展開(kāi),此時(shí)后綴為“收起”按鈕
            suffixStr = @"收起";
            contentStr = [NSString stringWithFormat:@"%@ %@", contentStr, suffixStr];
            H = model.titleActualH;
        } else {
            //文本已經(jīng)收起,此時(shí)后綴為“展開(kāi)/全文”按鈕
            //需要對(duì)文本進(jìn)行截取,將“...展開(kāi)”添加到我們限制的展示文字的末尾
            NSUInteger numCount = 5; //這是cell收起狀態(tài)下期望展示的最大行數(shù)
            CGFloat W = kScreenWidth-30; //這里是文本展示的寬度
            NSString *tempStr = [self stringByTruncatingString:contentStr suffixStr:@"...展開(kāi)" font:kRegularFont(14) forLength:W*numCount];
            contentStr = tempStr;
            suffixStr = @"展開(kāi)";
            H = model.titleMaxH;
        }
    }

    NSMutableAttributedString *attStr = [[NSMutableAttributedString alloc] initWithString:contentStr attributes:@{NSFontAttributeName:kRegularFont(14)}];
    self.contentTextView.linkTextAttributes = @{};
    
    //給富文本的后綴添加點(diǎn)擊事件
    if(![NSString isEmptyString:suffixStr]){
        NSRange range3 = [contentStr rangeOfString:suffixStr];
        [attStr addAttribute:NSForegroundColorAttributeName value:[UIColor systemBlueColor] range:range3];//[UIColor colorWithHexString:@"#000000"]
        NSString *valueString3 = [[NSString stringWithFormat:@"didOpenClose://%@", suffixStr] stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLFragmentAllowedCharacterSet]];
        [attStr addAttribute:NSLinkAttributeName value:valueString3 range:range3];
    }
    self.contentTextView.attributedText = attStr;
}

/// 將文本按長(zhǎng)度度截取并加上指定后綴
/// @param str 文本
/// @param suffixStr 指定后綴
/// @param font 文本字體
/// @param length 文本長(zhǎng)度
- (NSString*)stringByTruncatingString:(NSString *)str suffixStr:(NSString *)suffixStr font:(UIFont *)font forLength:(CGFloat)length {
    if (!str) return nil;
    if (str  && [str isKindOfClass:[NSString class]]) {
        for (int i=(int)[str length] - (int)[suffixStr length]; i< [str length];i = i - (int)[suffixStr length]){
            NSString *subStr = [str substringToIndex:i];
            CGSize size = [subStr sizeWithAttributes:@{NSFontAttributeName:font}];
            if(size.width < length){
                NSString *tempStr = [NSString stringWithFormat:suffixStr, subStr];
                CGSize size1 = [tempStr sizeWithAttributes:@{NSFontAttributeName:font}];
                if(size1.width < length){
                    str = tempStr;
                    break;
                }
            }
        }
    }
    return str;
}

3.在UITextView的代理方法中響應(yīng)點(diǎn)擊事件

Cell中的代碼

- (BOOL)textView:(UITextView *)textView shouldInteractWithURL:(NSURL *)URL inRange:(NSRange)characterRange {
    if ([[URL scheme] isEqualToString:@"didOpenClose"]) {
        //點(diǎn)擊了“展開(kāi)”或”收起“,通過(guò)代理或者block回調(diào),讓持有tableView的控制器去刷新單行Cell
        if (self.openCloseBlock) {
            self.openCloseBlock();
        }
        return NO;
    }
    return YES;
}

控制器中的代碼(此處是用block實(shí)現(xiàn))

//返回Cell的高度
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    LWYMyFavouriteModel *model = self.dataArray[indexPath.section];
    CGFloat otherH = 100; //除了文本內(nèi)容外其余的高度(根據(jù)項(xiàng)目需求而定)
    if (model.titleActualH > model.titleMaxH) {
        if (model.isOpen) {
            return model.titleActualH+otherH;
        } else {
            return model.titleMaxH+otherH;
        }
    } else {
        return model.titleActualH+otherH;
    }
}

//返回Cell
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    LWYMyCollectionCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cellID" forIndexPath:indexPath];
    LWYMyFavouriteModel *model = self.dataArray[indexPath.section];
    [cell setupCellData:model];
    kWeakSelf(self);
    [cell setOpenCloseBlock:^{ //Cell點(diǎn)擊了“展開(kāi)”或“收起”
        NSMutableArray *tempArr = weakself.dataArray;
        for (int i = 0; i < weakself.dataArray.count; i++) {
            LWYMyFavouriteModel *subModel = weakself.dataArray[i];
            if (subModel.favouriteId == model.favouriteId) {
                //刷新數(shù)據(jù)源中對(duì)應(yīng)的數(shù)據(jù)
                model.isOpen = !model.isOpen;
                [tempArr replaceObjectAtIndex:i withObject:model];
                weakself.dataArray = [NSMutableArray arrayWithArray:tempArr];
                
                //刷新指定的行
                NSIndexSet * indexSet = [[NSIndexSet alloc] initWithIndex:i];
                [weakself.tableView reloadSections:indexSet withRowAnimation:UITableViewRowAnimationAutomatic];
                break;
            }
        }
    }];
    return cell;
}

總結(jié):以上就是多行文本的展開(kāi)和收起的核心流程,怎么樣是不是很簡(jiǎn)單呢

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

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

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