最近做了一个需求 发布器(UITextView)支持文字的粗体斜体删除线以及添加颜色等样式,类似于
需求分析:
- 选中区域在修改UITextView的attributedString;
- 转换HTML显示在UILabel/DTCoreText中;
- 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),输入中文和英文然后再斜体,可以看到
在苹果所有设备上没有字体支持斜体
于是找到部门设计师 最简单的方式就是添加字体然后直接设置那个字体的FamilyName就可以搞定,可是设计师反馈,为了UI统一性要换就全部换了,为了一个斜体这个要部门会议讨论更换整个App的字体,如果显示的时候只为斜体适配这样没有斜体的用PingfangSC有斜体的用斜体字体 这样也没有办法达到UI的统一性,最终这个方法就放弃了;
于是开始谷歌,各种焦头烂额的StackFlow和Google之后有了如下的方案:
- 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的值,所以加粗斜体需要都包含进去;
设置倾斜角度
然后运行,完美实现
转换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里直接展示。
至此,已完成了这个需求。