iOS 中文斜体和粗体以及NSAttributedString转换HTML

最近做了一个需求 发布器(UITextView)支持文字的粗体斜体删除线以及添加颜色等样式,类似于


image.png

需求分析:

  1. 选中区域在修改UITextView的attributedString;
  2. 转换HTML显示在UILabel/DTCoreText中;
  3. HTML转换attributedString显示在UITextView中;

实现

  • 在加粗/删除线/文字颜色等样式操作的时候直接修改即可实现没有任何难度,贴出一个方法:
    _focusTextView为发布器主UITextView
- (void)setFocusSelectionBold:(BOOL)isBold
{
    modifyingTextStyle = YES;//标记正在修改样式
    
    NSRange selectedRange = self._focusTextView.selectedRange;
    NSMutableAttributedString *string = [self._focusTextView.attributedText mutableCopy];
    if (isBold){
        [string addAttribute:NSFontAttributeName value:[UIFont fontWithName:@"PingFangSC-Semibold" size:17] range:selectedRange];
    } else {
        [string addAttribute:NSFontAttributeName value:[UIFont fontWithName:@"PingFangSC-Regular" size:17] range:selectedRange];
    }
    self._focusTextView.attributedText = string;
    self._focusTextView.selectedRange = selectedRange; // 重置选中
    [self._focusTextView.delegate textViewDidChange:self._focusTextView];//更新代理
    modifyingTextStyle = NO;//标记修改样式结束
}

其他的不一一列举 都是调用API的模板代码

问题

主要问题集中在斜体
在实现斜体的时候发现了一个问题,中文要支持斜体需要字体支持斜体,可以打开苹果所有设备(Mac/iPhone/iPad...)上的笔记(Notes.app),输入中文和英文然后再斜体,可以看到
在苹果所有设备上没有字体支持斜体

image.png

于是找到部门设计师 最简单的方式就是添加字体然后直接设置那个字体的FamilyName就可以搞定,可是设计师反馈,为了UI统一性要换就全部换了,为了一个斜体这个要部门会议讨论更换整个App的字体,如果显示的时候只为斜体适配这样没有斜体的用PingfangSC有斜体的用斜体字体 这样也没有办法达到UI的统一性,最终这个方法就放弃了;

于是开始谷歌,各种焦头烂额的StackFlow和Google之后有了如下的方案:

    1. UIFont的动态字体,用字体描述类可以手动实现斜体功能,给出代码,字体倾斜角度请与设计师联调:
CGAffineTransform matrix = CGAffineTransformMake(1, 0, tanf(15 * (CGFloat)M_PI / 180), 1, 0, 0);
UIFontDescriptor *desc = [UIFontDescriptor fontDescriptorWithName:[UIFont systemFontOfSize:kFontSizeText].fontName matrix:matrix];
return [UIFont fontWithDescriptor:desc size:kFontSizeText];

在选中区域内替换此Font,实现了斜体,可是我们的需求是要富文本转成HTML去展示,HTML可不认这个仿射变换,HTML只认得标签;

  • 但是思路是对的在改变斜体的时候添加一个标记,接着在转换HTMl的时候识别出标记手动添加一个<i>标签,在UIFont中有一个标签叫NSObliquenessAttributeName, Oblique的意思是倾斜,可以实现斜体的功能,具体角度请与设计师沟通,所以有了如下方法
- (void)setFocusSelectionItalic:(BOOL)isItalic {
    modifyingTextStyle = YES;
    
    NSRange selectedRange = self._focusTextView.selectedRange;
    NSMutableAttributedString *string = [self._focusTextView.attributedText mutableCopy];
    
    NSMutableDictionary<NSAttributedStringKey, id> *attributes = [NSMutableDictionary dictionaryWithCapacity:2];
    if ([self focusSelectionContainsBold]) {
        attributes[NSFontAttributeName] = [self _currentFocusTextParagraph].boldTextFont;
    } else {
        attributes[NSFontAttributeName] = [self _currentFocusTextParagraph].textFont;
    }
    
    if (isItalic) {
        attributes[NSObliquenessAttributeName] = @0.5;
    } else {
        attributes[NSObliquenessAttributeName] = @0;
    }
    [string addAttributes:attributes range:selectedRange];
    
    self._focusTextView.attributedText = string;
    self._focusTextView.selectedRange = selectedRange; // reset select range after set attributedText
    
    [self._focusTextView.delegate textViewDidChange:self._focusTextView];
    
    modifyingTextStyle = NO;
}

代码解释:
获取到选择区域的内容判断是否有加粗,有加粗则实现加粗与否的操作,因为设置了斜体会覆盖TextView的attributedText的值,所以加粗斜体需要都包含进去;
设置倾斜角度
然后运行,完美实现


image.png

转换HTML

NSAttributedString *convertAttributeToHTML = [attributedText copy];
        /* 用两个数组临时存储标记的位置 然后再 */
        NSMutableArray *needReplaceRanges = [NSMutableArray array];
        NSMutableArray *needReplaceFonts = [NSMutableArray array];
      //遍历找到那个标记的font
        [attributedText enumerateAttributesInRange:NSMakeRange(0, attributedText.length) options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired usingBlock:^(NSDictionary<NSAttributedStringKey,id> * _Nonnull attrs, NSRange range, BOOL * _Nonnull stop) {
            CGFloat oblique = [attrs[NSObliquenessAttributeName] floatValue];
            if (oblique) {
                UIFont *oldFont = attrs[NSFontAttributeName];
                //找到了之后替换字体
                UIFontDescriptor *fontDescriptor = oldFont.fontDescriptor;
                UIFontDescriptorSymbolicTraits fontDescriptorSymbolicTraits = fontDescriptor.symbolicTraits;
                BOOL isBold = (fontDescriptorSymbolicTraits & UIFontDescriptorTraitBold) != 0;
                UIFont *newFont = [self.class generateSpecialFontWithBold:isBold withItatic:YES fontSize:oldFont.pointSize];
                [needReplaceRanges addObject:[NSValue valueWithRange:range]];
                [needReplaceFonts addObject:newFont];
            }
        }];
        NSMutableAttributedString *newAttributedString;
        if (needReplaceRanges.count) {
            newAttributedString = [attributedText mutableCopy];
            for (NSUInteger i = 0; i < needReplaceRanges.count; i++) {
                NSRange range;[((NSValue *)needReplaceRanges[i]) getValue:&range];
                UIFont *font = needReplaceFonts[i];
                [newAttributedString addAttribute:NSFontAttributeName value:font range:range];
            }
        }
        if (newAttributedString.length) {
            convertAttributeToHTML = [newAttributedString copy];
        }
    
    /* 使用系统的富文本转换HTML, 请勿使用第三方的分类转换!HTML语言代码是不严谨的 格式会影响到最后的生成*/
    NSDictionary *documentAttributes = @{NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType};
    NSData *htmlData = [convertAttributeToHTML dataFromRange:NSMakeRange(0, convertAttributeToHTML.length) documentAttributes:documentAttributes error:NULL];
    NSString *htmlString = [[NSString alloc] initWithData:htmlData encoding:NSUTF8StringEncoding];
//加粗和斜体的字体
+ (UIFont *)generateSpecialFontWithBold:(BOOL)bold withItatic:(BOOL)italic fontSize:(CGFloat)fontSize {
    UIFont *font = [self regularFontWithSize:fontSize];
    UIFontDescriptorSymbolicTraits symbolicTraits = 0;
    if (italic) {
        symbolicTraits |= UIFontDescriptorTraitItalic;
    }
    if (bold) {
        symbolicTraits |= UIFontDescriptorTraitBold;
    }
    UIFont *specialFont = [UIFont fontWithDescriptor:[[font fontDescriptor] fontDescriptorWithSymbolicTraits:symbolicTraits] size:font.pointSize];
    return specialFont;
}

这样会生成一个HTML的字符串,拿着字符串进行格式化和转化
HTMl是这样的:

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta http-equiv="Content-Style-Type" content="text/css">
<title></title>
<meta name="Generator" content="Cocoa HTML Writer">
<style type="text/css">
    p.p1 {margin: 0.0px 0.0px 0.0px 0.0px; font: 17.0px '.SF UI Text'; color: #333333}
    span.s1 {font-family: '.SFUIText-SemiboldItalic'; font-weight: bold; font-style: italic; font-size: 17.00pt}
    span.s2 {font-family: 'PingFangSC-Regular'; font-weight: normal; font-style: normal; font-size: 17.00pt; color: #fdaa25}
</style>
</head>
<body>
    <p class="p1">
        <span class="s1">斜</span>
        <span class="s2">体</span>
    </p>
</body>
</html>

这段代码直接可以贴到WebView里直接展示。
至此,已完成了这个需求。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,397评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,585评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,521评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,645评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,489评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,366评论 1 274
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,787评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,432评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,732评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,775评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,550评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,393评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,802评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,036评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,324评论 1 253
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,757评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,958评论 2 336

推荐阅读更多精彩内容